a6b068b070
照 alipay adapter 的 RSA + redirect/qr + 表单请求模式实现哪吒(nzzf.org)聚合支付: SHA256WithRSA + Base64 签名(商户私钥出站签名/平台公钥验响应及回调),Create 走 POST /api/pay/create,VerifyCallback 处理 GET 异步通知(handler 层新增 Query 透传 + nezha 专属纯文本 success/fail 回复),Query 走 POST /api/pay/query。BuildRegistry 按凭证齐备与否决策注册,缺凭证只 log 跳过不 fatal。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
110 lines
4.7 KiB
Go
110 lines
4.7 KiB
Go
// Package providerbuild is the *only* place that imports every concrete
|
|
// provider adapter package alongside internal/provider — it exists precisely
|
|
// so gateway/provider's core stays adapter-neutral (see internal/provider's
|
|
// package doc). Only main calls into this package.
|
|
//
|
|
// It cannot live inside internal/provider itself: each adapter package
|
|
// (alipay/crypto/stripe) imports internal/provider for the shared
|
|
// CreateRequest/Session/Capabilities types, so internal/provider importing
|
|
// them back would be a Go import cycle. This package sits one level up the
|
|
// dependency graph instead: providerbuild -> {provider, alipay, crypto, stripe},
|
|
// nothing imports providerbuild back.
|
|
package providerbuild
|
|
|
|
import (
|
|
"log"
|
|
"strings"
|
|
|
|
sw "github.com/smartwalle/alipay/v3"
|
|
stripeclient "github.com/stripe/stripe-go/v79/client"
|
|
|
|
"github.com/wangjia/pay/config"
|
|
"github.com/wangjia/pay/internal/accounts"
|
|
"github.com/wangjia/pay/internal/provider"
|
|
"github.com/wangjia/pay/internal/provider/alipay"
|
|
"github.com/wangjia/pay/internal/provider/crypto"
|
|
"github.com/wangjia/pay/internal/provider/fake"
|
|
"github.com/wangjia/pay/internal/provider/nezha"
|
|
"github.com/wangjia/pay/internal/provider/stripe"
|
|
)
|
|
|
|
// BuildRegistry 据 enabled 账户装配注册表:有 enabled 账户且凭证齐备的渠道才 Register。
|
|
// 缺凭证只跳过该渠道(log,不 fatal)——允许只上线部分渠道;fake adapter 只在
|
|
// config.C.MockChannelEnabled=true(env MOCK_CHANNEL_ENABLED)时注册,生产默认 false 不受影响。
|
|
func BuildRegistry(accts *accounts.Registry) *provider.Registry {
|
|
reg := provider.NewRegistry()
|
|
|
|
// fake:dev-only 本地联调渠道(见顶部包注释)。SettleCurrencies 覆盖成
|
|
// ["CNY","USDT"]——CNY 排首位对齐 pangolin 下单实际结算币种(pangolin CreateOrder
|
|
// 不传 currency,由渠道 Capabilities().SettleCurrencies[0] 驱动定价,见
|
|
// server/internal/pay/client.go::createOrderReq),USDT 兜底覆盖其它业务方。
|
|
if config.C.MockChannelEnabled {
|
|
fp := fake.New()
|
|
fp.SetSettleCurrencies([]string{"CNY", "USDT"})
|
|
reg.Register(fp)
|
|
log.Println("[providers] fake 已注册(MOCK_CHANNEL_ENABLED=true,dev-only)")
|
|
}
|
|
|
|
// crypto:整渠道一个 adapter,多地址=多账户(P5 地址池路由前取首个 enabled)。
|
|
if len(accts.EnabledFor("crypto", "")) > 0 {
|
|
reg.Register(crypto.New(accts))
|
|
log.Println("[providers] crypto 已注册")
|
|
}
|
|
|
|
// alipay:首个 enabled 账户的 env 凭证 → *alipay.Client。
|
|
if as := accts.EnabledFor("alipay", ""); len(as) > 0 {
|
|
a := as[0]
|
|
appID := accts.Credential(a.AccountID, "APP_ID")
|
|
appPriv := accts.Credential(a.AccountID, "APP_PRIVATE_KEY")
|
|
aliPub := accts.Credential(a.AccountID, "ALIPAY_PUBLIC_KEY")
|
|
prod := accts.Credential(a.AccountID, "PRODUCTION") == "1"
|
|
if appID == "" || appPriv == "" || aliPub == "" {
|
|
log.Printf("[providers] alipay 账户 %s 凭证不全,跳过", a.AccountID)
|
|
} else if c, err := sw.New(appID, appPriv, prod); err != nil {
|
|
log.Printf("[providers] alipay client 构建失败: %v", err)
|
|
} else if err := c.LoadAliPayPublicKey(aliPub); err != nil {
|
|
log.Printf("[providers] alipay 加载公钥失败: %v", err)
|
|
} else {
|
|
reg.Register(alipay.New(c))
|
|
log.Println("[providers] alipay 已注册")
|
|
}
|
|
}
|
|
|
|
// nezha:RSA 聚合支付,首个 enabled 账户的 env 凭证 → *nezha.Provider。notify_url
|
|
// 与 alipay 不同——alipay notify_url 走支付宝开放平台应用级配置(per-request 可省略),
|
|
// 哪吒协议要求每次下单显式携带,这里据 config.C.Server.BaseURL 拼好传入。
|
|
if ns := accts.EnabledFor("nezha", ""); len(ns) > 0 {
|
|
n := ns[0]
|
|
pid := accts.Credential(n.AccountID, "PID")
|
|
priv := accts.Credential(n.AccountID, "PRIVATE_KEY")
|
|
pub := accts.Credential(n.AccountID, "PLATFORM_PUBLIC_KEY")
|
|
if pid == "" || priv == "" || pub == "" {
|
|
log.Printf("[providers] nezha 账户 %s 凭证不全,跳过", n.AccountID)
|
|
} else {
|
|
notifyURL := strings.TrimRight(config.C.Server.BaseURL, "/") + "/api/v2/callback/nezha"
|
|
if np, err := nezha.New(pid, priv, pub, notifyURL); err != nil {
|
|
log.Printf("[providers] nezha client 构建失败: %v", err)
|
|
} else {
|
|
reg.Register(np)
|
|
log.Println("[providers] nezha 已注册")
|
|
}
|
|
}
|
|
}
|
|
|
|
// stripe:首个 enabled 账户的 env 凭证 → *client.API。
|
|
if ss := accts.EnabledFor("stripe", ""); len(ss) > 0 {
|
|
s := ss[0]
|
|
key := accts.Credential(s.AccountID, "SECRET_KEY")
|
|
wh := accts.Credential(s.AccountID, "WEBHOOK_SECRET")
|
|
if key == "" || wh == "" {
|
|
log.Printf("[providers] stripe 账户 %s 凭证不全,跳过", s.AccountID)
|
|
} else {
|
|
sc := stripeclient.New(key, nil)
|
|
reg.Register(stripe.New(sc, wh))
|
|
log.Println("[providers] stripe 已注册")
|
|
}
|
|
}
|
|
|
|
return reg
|
|
}
|