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