feat(provider): 哪吒支付(nezha)RSA 聚合渠道 provider + 注册 + 测试
照 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
This commit is contained in:
@@ -13,6 +13,7 @@ package providerbuild
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
sw "github.com/smartwalle/alipay/v3"
|
||||
stripeclient "github.com/stripe/stripe-go/v79/client"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -68,6 +70,27 @@ func BuildRegistry(accts *accounts.Registry) *provider.Registry {
|
||||
}
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package providerbuild_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
@@ -21,6 +25,24 @@ import (
|
||||
// 的"缺凭证→跳过、不 fatal、注册表里没有它"分支,以及它在混合场景里不会误伤
|
||||
// 同批次里凭证齐备的其它渠道——这正是 BuildRegistry 装配决策本身要保证的行为。
|
||||
func TestBuildRegistry(t *testing.T) {
|
||||
// nezha 凭证解析(export_test.go 的 base64 DER 兜底路径,见 nezha 包)不像 alipay
|
||||
// 依赖 smartwalle SDK 的特定加载方式,构造一对测试用 RSA 密钥成本很低,故这里同时
|
||||
// 覆盖"凭证齐备→真注册"分支(alipay 因构造成本高特意跳过,见上方注释)。
|
||||
nezhaMerchant, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("gen nezha merchant key: %v", err)
|
||||
}
|
||||
nezhaPlatform, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatalf("gen nezha platform key: %v", err)
|
||||
}
|
||||
nezhaPrivB64 := base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(nezhaMerchant))
|
||||
nezhaPubDER, err := x509.MarshalPKIXPublicKey(&nezhaPlatform.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal nezha platform pub: %v", err)
|
||||
}
|
||||
nezhaPubB64 := base64.StdEncoding.EncodeToString(nezhaPubDER)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
accounts []config.AccountConfig
|
||||
@@ -52,6 +74,26 @@ func TestBuildRegistry(t *testing.T) {
|
||||
// 若 BuildRegistry 对此 fatal/panic,本测试直接挂掉,已是断言的一部分。
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "nezha_enabled_但凭证不全_跳过不fatal_注册表无nezha",
|
||||
accounts: []config.AccountConfig{
|
||||
{AccountID: "nz-1", Channel: "nezha", Enabled: true, CredentialEnvPrefix: "t_nz"},
|
||||
},
|
||||
// 故意不设 T_NZ_PID/_PRIVATE_KEY/_PLATFORM_PUBLIC_KEY。
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "nezha_enabled_且凭证齐备_注册nezha",
|
||||
accounts: []config.AccountConfig{
|
||||
{AccountID: "nz-2", Channel: "nezha", Enabled: true, CredentialEnvPrefix: "t_nz2"},
|
||||
},
|
||||
env: map[string]string{
|
||||
"T_NZ2_PID": "test-pid",
|
||||
"T_NZ2_PRIVATE_KEY": nezhaPrivB64,
|
||||
"T_NZ2_PLATFORM_PUBLIC_KEY": nezhaPubB64,
|
||||
},
|
||||
want: []string{"nezha"},
|
||||
},
|
||||
{
|
||||
name: "stripe_enabled_且凭证齐备_注册stripe",
|
||||
accounts: []config.AccountConfig{
|
||||
|
||||
Reference in New Issue
Block a user