// 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 }