From 5c1da5bf55484eefdfc484fd3cec49660e6ba23b Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Wed, 23 Sep 2026 18:16:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=94=AF=E4=BB=98=E7=9B=B8?= =?UTF-8?q?=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/integration/payment/wechat_v3.go | 17 ++- .../payment/wechat_v3_native_test.go | 105 ++++++++++++++++++ 2 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 internal/integration/payment/wechat_v3_native_test.go diff --git a/internal/integration/payment/wechat_v3.go b/internal/integration/payment/wechat_v3.go index 1f824bc..0401f36 100644 --- a/internal/integration/payment/wechat_v3.go +++ b/internal/integration/payment/wechat_v3.go @@ -39,15 +39,22 @@ func (a *wechatV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentReq bm.SetBodyMap("amount", func(amount gopay.BodyMap) { amount.Set("total", req.Amount).Set("currency", strings.ToUpper(req.Currency)) }) - mergeGoPayExtras(bm, req.Extra, - "appid", "mchid", "description", "out_trade_no", "notify_url", "amount", "payer", - "trade_type", "pay_type", "method", "openid", "open_id", "auth_code", "authcode", "barcode", - ) - tradeType := normalizeWechatV3TradeType(firstAny(req.Extra, "trade_type", "pay_type", "method")) if tradeType == "" { tradeType = normalizeWechatV3TradeType(firstAny(c, "trade_type", "pay_type", "method")) } + if tradeType == "native" || tradeType == "qr" || tradeType == "qrcode" { + for _, key := range []string{"time_expire", "attach", "goods_tag", "support_fapiao", "detail", "scene_info", "settle_info"} { + if value, ok := req.Extra[key]; ok { + bm[key] = value + } + } + } else { + mergeGoPayExtras(bm, req.Extra, + "appid", "mchid", "description", "out_trade_no", "notify_url", "amount", "payer", + "trade_type", "pay_type", "method", "openid", "open_id", "auth_code", "authcode", "barcode", + ) + } switch tradeType { case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "miniprogram", "mini_program", "applet": if openID := firstAny(req.Extra, "openid", "open_id"); openID != "" { diff --git a/internal/integration/payment/wechat_v3_native_test.go b/internal/integration/payment/wechat_v3_native_test.go new file mode 100644 index 0000000..19820f1 --- /dev/null +++ b/internal/integration/payment/wechat_v3_native_test.go @@ -0,0 +1,105 @@ +package payment + +import ( + "context" + "encoding/json" + "io" + bizpayment "kra/internal/biz/payment" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + gopayWechatV3 "github.com/go-pay/gopay/wechat/v3" +) + +func TestWechatV3NativeOnlySendsSupportedExtras(t *testing.T) { + platformKey, _, platformPublicPEM := testRSAKeyPair(t) + _, merchantPrivatePEM, _ := testRSAKeyPair(t) + const ( + platformSerial = "PLATFORM-SERIAL" + timestamp = "1787068800" + nonce = "native-response-nonce" + ) + response := []byte(`{"code_url":"weixin://wxpay/bizpayurl?pr=test"}`) + signature := testRSA2Sign(t, platformKey, []byte(timestamp+"\n"+nonce+"\n"+string(response)+"\n")) + var requestBody map[string]any + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/v3/pay/transactions/native" { + t.Errorf("request path = %q", r.URL.Path) + } + body, err := io.ReadAll(r.Body) + if err != nil { + t.Errorf("read request: %v", err) + return + } + if err := json.Unmarshal(body, &requestBody); err != nil { + t.Errorf("decode request: %v", err) + return + } + w.Header().Set(gopayWechatV3.HeaderTimestamp, timestamp) + w.Header().Set(gopayWechatV3.HeaderNonce, nonce) + w.Header().Set(gopayWechatV3.HeaderSerial, platformSerial) + w.Header().Set(gopayWechatV3.HeaderSignature, signature) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(response) + })) + defer server.Close() + + for _, test := range []struct { + name, method, configMethod string + }{ + {"native", "native", "jsapi"}, + {"qr alias", "qr", "jsapi"}, + {"qrcode alias", "qrcode", "jsapi"}, + {"config fallback", "", "native"}, + } { + t.Run(test.name, func(t *testing.T) { + requestBody = nil + extra := map[string]any{ + "appid": "wrong-app", "mchid": "wrong-merchant", + "amount": map[string]any{"total": 999}, "payer": map[string]any{"openid": "wrong"}, + "openid": "wrong", "trade_type": test.method, "unexpected": "not-a-native-field", + "time_expire": "2026-09-24T12:00:00+08:00", "attach": "order metadata", + "goods_tag": "tag", "support_fapiao": true, + "detail": map[string]any{"cost_price": 123}, + "scene_info": map[string]any{"payer_client_ip": "127.0.0.1"}, + "settle_info": map[string]any{"profit_sharing": false}, + } + result, err := (&wechatV3Adapter{}).Create(context.Background(), &bizpayment.PaymentRequest{ + TradeNo: "T-NATIVE", Subject: "subject", Amount: 123, Currency: "cny", + NotifyURL: "https://merchant.test/payment/callback", Extra: extra, + }, map[string]any{ + "app_id": "wx-test", "merchant_id": "mch-test", "serial_no": "merchant-serial", + "private_key": merchantPrivatePEM, "api_v3_key": "01234567890123456789012345678901", + "platform_cert": platformPublicPEM, "platform_serial_no": platformSerial, + "trade_type": test.configMethod, "create_url": server.URL, + }) + if err != nil { + t.Fatal(err) + } + if result.Status != "created" || requestBody == nil { + t.Fatalf("result = %+v, request = %#v", result, requestBody) + } + for _, key := range []string{"unexpected", "payer", "openid", "trade_type", "method"} { + if _, exists := requestBody[key]; exists { + t.Errorf("native request unexpectedly includes %s", key) + } + } + for key, want := range map[string]any{ + "appid": "wx-test", "mchid": "mch-test", "description": "subject", + "out_trade_no": "T-NATIVE", "notify_url": "https://merchant.test/payment/callback", + "amount": map[string]any{"total": float64(123), "currency": "CNY"}, + } { + if !reflect.DeepEqual(requestBody[key], want) { + t.Errorf("%s = %#v, want %#v", key, requestBody[key], want) + } + } + for _, key := range []string{"time_expire", "attach", "goods_tag", "support_fapiao", "detail", "scene_info", "settle_info"} { + if _, exists := requestBody[key]; !exists { + t.Errorf("native request missing %s", key) + } + } + }) + } +}