feat: 微信V3 + 支付宝手机站/UA自适应 + 业务对接(签名下单/webhook) + 通用多业务
- 微信支付 V3 Native 渠道 (wechat.go):Native下单/回调AES-GCM解密验签/查单 - 支付宝:手机网站支付 wap.pay + 按 UA 自适应(PC page.pay扫码 / 手机拉App);qr_pay_mode=2 完整扫码收银台 - 业务对接:下单接口扩展(biz_system/biz_ref/return_url)+ HMAC 签名鉴权;支付成功 webhook 主动推送业务方 + 60s 重试 + BizNotifyLog - 通用多业务:config.biz 改 map,加业务只改配置(BIZ_<SYS>_SECRET/_CALLBACK_URL) - seedPlans:四档真实套餐 + promo_first_month(¥1) + test_liandiao(0.01),均挂 biz_code;/products 暴露 biz_code - 删除沙箱 pay.html;对接设计文档入 docs Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UQmqWmV67sXGLrb3U1XXn
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
@@ -11,7 +12,10 @@ type Config struct {
|
||||
Server ServerConfig
|
||||
Database DatabaseConfig
|
||||
AlipaySandbox AlipaySandboxConfig `mapstructure:"alipay_sandbox"`
|
||||
Wechat WechatConfig `mapstructure:"wechat"`
|
||||
QuerySync QuerySyncConfig `mapstructure:"query_sync"`
|
||||
// Biz 通用:任意业务系统(jiu / dudu / …)在 config 的 biz.<name> 下声明即可接入,无需改代码。
|
||||
Biz map[string]BizSystemConfig `mapstructure:"biz"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
@@ -37,6 +41,35 @@ type AlipaySandboxConfig struct {
|
||||
AlipayPublicKey string `mapstructure:"alipay_public_key"` // 支付宝公钥(公钥模式)
|
||||
}
|
||||
|
||||
// WechatConfig 启动时据此 upsert 一个微信商户(V3 Native)。
|
||||
// 密钥(商户私钥 / APIv3 密钥)严禁写进 config.yaml,走环境变量(生产由 Bitwarden 经 rbw 灌入)。
|
||||
type WechatConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
MerchantCode string `mapstructure:"merchant_code"` // 业务标识,如 yanmei-wx(与支付宝商户区分)
|
||||
MerchantName string `mapstructure:"merchant_name"`
|
||||
MchID string `mapstructure:"mch_id"` // 微信支付商户号
|
||||
AppID string `mapstructure:"app_id"` // 绑定的 APPID(公众号/小程序/移动应用;Native 可用商户号绑定的任一)
|
||||
CertSerial string `mapstructure:"cert_serial"` // 商户 API 证书序列号
|
||||
PrivateKey string `mapstructure:"private_key"` // 商户 API 私钥 apiclient_key.pem(走环境变量)
|
||||
APIv3Key string `mapstructure:"apiv3_key"` // APIv3 密钥(回调解密,走环境变量)
|
||||
}
|
||||
|
||||
// BizSystemConfig 单个业务系统的对接配置。secret 双向共用(校验其下单请求签名 + 给回调 payload 签名)。
|
||||
// 密钥/回调按约定从环境变量注入:BIZ_<SYSTEM>_SECRET / BIZ_<SYSTEM>_CALLBACK_URL,严禁写进 config.yaml。
|
||||
type BizSystemConfig struct {
|
||||
CallbackURL string `mapstructure:"callback_url"` // 支付成功回调业务方接收器地址
|
||||
Secret string `mapstructure:"secret"` // HMAC 共享密钥
|
||||
}
|
||||
|
||||
// BizByName 按业务系统名取配置(供下单鉴权 / webhook 回调用)。未声明或无密钥则视为未接入。
|
||||
func (c *Config) BizByName(system string) (BizSystemConfig, bool) {
|
||||
cfg, ok := c.Biz[system]
|
||||
if !ok || cfg.Secret == "" {
|
||||
return BizSystemConfig{}, false
|
||||
}
|
||||
return cfg, true
|
||||
}
|
||||
|
||||
// QuerySyncConfig 兜底主动查单:定时把待支付订单拿去 alipay.trade.query 核对,防回调丢失。
|
||||
type QuerySyncConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
@@ -58,6 +91,8 @@ func Load() {
|
||||
// 敏感项支持环境变量覆盖(部署时不写进 config.yaml)
|
||||
_ = viper.BindEnv("alipay_sandbox.app_private_key", "ALIPAY_APP_PRIVATE_KEY")
|
||||
_ = viper.BindEnv("alipay_sandbox.alipay_public_key", "ALIPAY_PUBLIC_KEY")
|
||||
_ = viper.BindEnv("wechat.private_key", "WECHAT_MCH_PRIVATE_KEY")
|
||||
_ = viper.BindEnv("wechat.apiv3_key", "WECHAT_APIV3_KEY")
|
||||
_ = viper.BindEnv("database.dsn", "DATABASE_DSN")
|
||||
|
||||
viper.SetDefault("server.port", "8080")
|
||||
@@ -68,6 +103,9 @@ func Load() {
|
||||
viper.SetDefault("alipay_sandbox.enabled", false)
|
||||
viper.SetDefault("alipay_sandbox.merchant_code", "yanmei")
|
||||
viper.SetDefault("alipay_sandbox.merchant_name", "演么测试商户")
|
||||
viper.SetDefault("wechat.enabled", false)
|
||||
viper.SetDefault("wechat.merchant_code", "yanmei-wx")
|
||||
viper.SetDefault("wechat.merchant_name", "岩美(北京)技术有限公司")
|
||||
viper.SetDefault("query_sync.enabled", true)
|
||||
viper.SetDefault("query_sync.interval_sec", 30)
|
||||
viper.SetDefault("query_sync.max_age_min", 30)
|
||||
@@ -78,4 +116,17 @@ func Load() {
|
||||
if err := viper.Unmarshal(&C); err != nil {
|
||||
log.Fatalf("[config] 解析配置失败: %v", err)
|
||||
}
|
||||
|
||||
// 各业务系统密钥/回调按约定从环境变量注入(不写进 config.yaml):
|
||||
// BIZ_<SYSTEM>_SECRET / BIZ_<SYSTEM>_CALLBACK_URL(SYSTEM 为大写业务名,如 BIZ_JIU_SECRET)。
|
||||
for name, bc := range C.Biz {
|
||||
up := strings.ToUpper(name)
|
||||
if bc.Secret == "" {
|
||||
bc.Secret = os.Getenv("BIZ_" + up + "_SECRET")
|
||||
}
|
||||
if bc.CallbackURL == "" {
|
||||
bc.CallbackURL = os.Getenv("BIZ_" + up + "_CALLBACK_URL")
|
||||
}
|
||||
C.Biz[name] = bc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,31 @@ alipay_sandbox:
|
||||
app_private_key: "" # 留空!走环境变量 ALIPAY_APP_PRIVATE_KEY
|
||||
alipay_public_key: "" # 留空!走环境变量 ALIPAY_PUBLIC_KEY
|
||||
|
||||
# 启动时据此 upsert 一个微信商户(V3 Native 扫码)。微信无沙箱:enabled=true 即正式收款。
|
||||
# 密钥严禁写进本文件!走环境变量(生产由 Bitwarden 经 rbw 灌入):
|
||||
# WECHAT_MCH_PRIVATE_KEY(商户私钥 apiclient_key.pem) / WECHAT_APIV3_KEY(APIv3 密钥)
|
||||
wechat:
|
||||
enabled: false
|
||||
merchant_code: "yanmei-wx" # 与支付宝商户区分
|
||||
merchant_name: "岩美(北京)技术有限公司"
|
||||
mch_id: "" # 微信支付商户号(入驻后拿到)
|
||||
app_id: "" # 绑定的 APPID
|
||||
cert_serial: "" # 商户 API 证书序列号
|
||||
private_key: "" # 留空!走环境变量 WECHAT_MCH_PRIVATE_KEY
|
||||
apiv3_key: "" # 留空!走环境变量 WECHAT_APIV3_KEY
|
||||
|
||||
# 业务系统对接(通用)。任意业务系统在 biz.<name> 下加一段即可接入,无需改代码。
|
||||
# 契约见 pay-contract 仓(OpenAPI + README)。业务方带签名下单 + 入账后 pay 回调其 webhook。
|
||||
# 每个业务的 secret/callback 按约定走环境变量:BIZ_<NAME 大写>_SECRET / BIZ_<NAME 大写>_CALLBACK_URL,
|
||||
# 严禁写本文件(callback 可写这里,secret 一律走 env)。
|
||||
biz:
|
||||
jiu:
|
||||
callback_url: "https://jiu.51yanmei.com/api/v1/pay/callback" # 支付成功回调 jiu 的接收器
|
||||
secret: "" # 留空!走环境变量 BIZ_JIU_SECRET
|
||||
# dudu: # 将来接入 dudu:加这段 + 设 BIZ_DUDU_SECRET
|
||||
# callback_url: "https://.../api/v1/pay/callback"
|
||||
# secret: ""
|
||||
|
||||
# 兜底主动查单:定时把待支付订单拿去查,防异步回调丢失
|
||||
query_sync:
|
||||
enabled: true
|
||||
|
||||
Reference in New Issue
Block a user