205 lines
7.4 KiB
Go
205 lines
7.4 KiB
Go
package payment
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"kra/app/system/internal/biz"
|
|
|
|
gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
|
|
)
|
|
|
|
func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) {
|
|
platformKey, _, platformPublicPEM := testRSAKeyPair(t)
|
|
_, merchantPrivatePEM, _ := testRSAKeyPair(t)
|
|
const (
|
|
platformSerial = "PLATFORM-SERIAL"
|
|
timestamp = "1787068800"
|
|
nonce = "codepay-response-nonce"
|
|
)
|
|
response := []byte(`{"appid":"wx-test","mchid":"mch-test","out_trade_no":"T-WECHAT-CODEPAY","transaction_id":"WX-CODEPAY-1","trade_type":"MICROPAY","trade_state":"SUCCESS","amount":{"total":123,"payer_total":100,"currency":"CNY","payer_currency":"CNY"},"promotion_detail":[{"coupon_id":"COUPON-1","amount":23}]}`)
|
|
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.Method != http.MethodPost || r.URL.Path != "/v3/pay/transactions/codepay" {
|
|
t.Errorf("request = %s %s", r.Method, r.URL.Path)
|
|
}
|
|
if strings.TrimSpace(r.Header.Get("Authorization")) == "" {
|
|
t.Error("missing GoPay authorization header")
|
|
}
|
|
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()
|
|
|
|
result, err := (&wechatV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{
|
|
Provider: biz.PaymentWechatV3,
|
|
TradeNo: "T-WECHAT-CODEPAY",
|
|
Subject: "subject",
|
|
Amount: 123,
|
|
Currency: "CNY",
|
|
NotifyURL: "https://merchant.test/payment/callback",
|
|
Extra: map[string]any{
|
|
"method": "codepay",
|
|
"auth_code": "AUTH-CODE-1",
|
|
"payer": map[string]any{"auth_code": "OVERRIDE"},
|
|
},
|
|
}, 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,
|
|
"create_url": server.URL,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
payer, _ := requestBody["payer"].(map[string]any)
|
|
amount, _ := requestBody["amount"].(map[string]any)
|
|
if payer["auth_code"] != "AUTH-CODE-1" || requestBody["out_trade_no"] != "T-WECHAT-CODEPAY" {
|
|
t.Fatalf("request identity = %#v", requestBody)
|
|
}
|
|
if amount["total"] != float64(123) || amount["currency"] != "CNY" {
|
|
t.Fatalf("request amount = %#v", amount)
|
|
}
|
|
if result.Provider != biz.PaymentWechatV3 || result.Status != "success" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "WX-CODEPAY-1" || result.QueryID != "" {
|
|
t.Fatalf("result identity = %+v", result)
|
|
}
|
|
if result.Amount != 123 || result.Currency != "CNY" || result.PayerCurrency != "CNY" || !result.AmountBreakdownKnown || result.PayerPaidAmount != 100 || result.CashPaidAmount != 100 || result.DiscountAmount != 23 {
|
|
t.Fatalf("result amount = %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayAliasesRequireAuthCode(t *testing.T) {
|
|
_, merchantPrivatePEM, _ := testRSAKeyPair(t)
|
|
_, _, platformPublicPEM := testRSAKeyPair(t)
|
|
config := 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": "platform-serial",
|
|
}
|
|
|
|
for _, method := range []string{"micropay", "micro_pay", "micro-pay", "codepay", "code_pay", "code-pay", "barcode", "barcode_pay", "facepay", "face_pay"} {
|
|
t.Run(method, func(t *testing.T) {
|
|
_, err := (&wechatV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{
|
|
TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 1, Currency: "CNY",
|
|
Extra: map[string]any{"method": method},
|
|
}, config)
|
|
if err == nil || !strings.Contains(err.Error(), "auth_code") {
|
|
t.Fatalf("method %q error = %v", method, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayResultRejectsInvalidIdentity(t *testing.T) {
|
|
_, err := wechatV3CodePayResult("T-WECHAT-CODEPAY", &gopayWechatV3.CodePayRsp{
|
|
Code: gopayWechatV3.Success,
|
|
Response: &gopayWechatV3.CodePay{
|
|
OutTradeNo: "OTHER-ORDER",
|
|
TradeState: "SUCCESS",
|
|
Amount: &gopayWechatV3.Amount{Total: 1, Currency: "CNY"},
|
|
},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "商户订单号不匹配") {
|
|
t.Fatalf("identity mismatch error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayResultRequiresMerchantOrderNumber(t *testing.T) {
|
|
_, err := wechatV3CodePayResult("T-WECHAT-CODEPAY", &gopayWechatV3.CodePayRsp{
|
|
Code: gopayWechatV3.Success,
|
|
Response: &gopayWechatV3.CodePay{
|
|
TradeState: "SUCCESS",
|
|
TransactionId: "WX-CODEPAY-1",
|
|
Amount: &gopayWechatV3.Amount{Total: 1, Currency: "CNY"},
|
|
},
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "out_trade_no") {
|
|
t.Fatalf("missing out_trade_no error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayResultPreservesFullyDiscountedBreakdown(t *testing.T) {
|
|
result, err := wechatV3CodePayResult("T-WECHAT-CODEPAY", &gopayWechatV3.CodePayRsp{
|
|
Code: gopayWechatV3.Success,
|
|
Response: &gopayWechatV3.CodePay{
|
|
OutTradeNo: "T-WECHAT-CODEPAY",
|
|
TransactionId: "WX-CODEPAY-1",
|
|
TradeState: "SUCCESS",
|
|
Amount: &gopayWechatV3.Amount{
|
|
Total: 100, PayerTotal: 0, Currency: "CNY", PayerCurrency: "CNY",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !result.AmountBreakdownKnown || result.PayerPaidAmount != 0 || result.CashPaidAmount != 0 || result.DiscountAmount != 100 || result.PayerCurrency != "CNY" {
|
|
t.Fatalf("fully discounted result = %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayDoesNotInferMissingPayerTotal(t *testing.T) {
|
|
result, err := wechatV3CodePayResult("T-WECHAT-CODEPAY", &gopayWechatV3.CodePayRsp{
|
|
Code: gopayWechatV3.Success,
|
|
SignInfo: &gopayWechatV3.SignInfo{SignBody: `{"out_trade_no":"T-WECHAT-CODEPAY","transaction_id":"WX-CODEPAY-2","trade_state":"SUCCESS","amount":{"total":100,"currency":"CNY"}}`},
|
|
Response: &gopayWechatV3.CodePay{
|
|
OutTradeNo: "T-WECHAT-CODEPAY",
|
|
TransactionId: "WX-CODEPAY-2",
|
|
TradeState: "SUCCESS",
|
|
Amount: &gopayWechatV3.Amount{Total: 100, Currency: "CNY"},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.AmountBreakdownKnown || result.PayerPaidAmount != 0 || result.DiscountAmount != 0 {
|
|
t.Fatalf("missing payer_total was treated as a complete breakdown: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestWechatV3CodePayUserPayingRemainsPending(t *testing.T) {
|
|
result, err := wechatV3CodePayResult("T-WECHAT-CODEPAY", &gopayWechatV3.CodePayRsp{
|
|
Code: gopayWechatV3.Success,
|
|
Response: &gopayWechatV3.CodePay{
|
|
OutTradeNo: "T-WECHAT-CODEPAY",
|
|
TradeState: "USERPAYING",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != "pending" || result.TradeNo != "T-WECHAT-CODEPAY" || result.ProviderTradeNo != "" || result.QueryID != "" {
|
|
t.Fatalf("pending result = %+v", result)
|
|
}
|
|
}
|