package payment import ( "crypto/aes" "crypto/cipher" "encoding/base64" "net/url" "strings" "testing" "kra/internal/biz" ) func TestWechatV2SignOfficialStyle(t *testing.T) { values := map[string]string{ "appid": "wxd930ea5d5a258f4f", "mch_id": "10000100", "device_info": "1000", "body": "test", "nonce_str": "ibuaiVcKdpRxkhJA", } got := wechatV2Sign(values, "192006250b4c09247ec02edce69f6a2d", "MD5") if got != strings.ToUpper(got) || len(got) != 32 { t.Fatalf("invalid WeChat v2 MD5 signature %q", got) } values["sign"] = got if err := verifyWechatV2(values, "192006250b4c09247ec02edce69f6a2d"); err != nil { t.Fatal(err) } } func TestApplePaymentResultSeparatesMerchantAndProviderTradeNumbers(t *testing.T) { result, err := applePaymentResult("1000000000001", map[string]any{ "transactionId": "1000000000001", "appAccountToken": "123e4567-e89b-12d3-a456-426614174000", "currency": "CNY", "price": "1000", }, nil, map[string]any{"price_divisor": "10"}) if err != nil { t.Fatal(err) } if result.TradeNo != "123e4567-e89b-12d3-a456-426614174000" || result.ProviderTradeNo != "1000000000001" || result.QueryID != "1000000000001" { t.Fatalf("unexpected Apple order identifiers: %+v", result) } } func TestApplePaymentResultTreatsZeroRevocationDateAsNotRevoked(t *testing.T) { result, err := applePaymentResult("1000000000001", map[string]any{ "transactionId": "1000000000001", "appAccountToken": "123e4567-e89b-12d3-a456-426614174000", "currency": "CNY", "price": "1000", "revocationDate": float64(0), }, nil, map[string]any{"price_divisor": "10"}) if err != nil || result.Status != "success" || result.Amount != 100 { t.Fatalf("zero revocationDate result = %+v, err = %v", result, err) } result, err = applePaymentResult("1000000000001", map[string]any{ "transactionId": "1000000000001", "appAccountToken": "123e4567-e89b-12d3-a456-426614174000", "currency": "CNY", "price": "1000", "revocationDate": float64(1700000000000), }, nil, map[string]any{"price_divisor": "10"}) if err != nil || result.Status != "failed" { t.Fatalf("positive revocationDate result = %+v, err = %v", result, err) } } func TestWechatV3Decrypt(t *testing.T) { key := "01234567890123456789012345678901" nonce := "0123456789ab" aad := "transaction" plain := []byte(`{"out_trade_no":"T1","trade_state":"SUCCESS"}`) block, err := aes.NewCipher([]byte(key)) if err != nil { t.Fatal(err) } gcm, err := cipher.NewGCM(block) if err != nil { t.Fatal(err) } ciphertext := base64.StdEncoding.EncodeToString(gcm.Seal(nil, []byte(nonce), plain, []byte(aad))) got, err := decryptWechatV3(ciphertext, nonce, aad, key) if err != nil { t.Fatal(err) } if string(got) != string(plain) { t.Fatalf("plaintext = %q", got) } } func TestAlipaySignContentSortedAndExcludesSign(t *testing.T) { values := url.Values{"method": {"alipay.trade.query"}, "app_id": {"123"}, "sign": {"ignored"}, "empty": {""}} if got := alipaySignContent(values); got != "app_id=123&method=alipay.trade.query" { t.Fatalf("sign content = %q", got) } } func TestAlipayBreakdownWithPointPayment(t *testing.T) { result := &biz.PaymentResult{Status: "success", Amount: 1000} err := populateAlipayBreakdown(result, map[string]any{ "total_amount": "10.00", "buyer_pay_amount": "8.00", "point_amount": "2.00", "receipt_amount": "8.00", }) if err != nil { t.Fatal(err) } if !result.AmountBreakdownKnown || result.PayerPaidAmount != 800 || result.CashPaidAmount != 600 || result.PointPaidAmount != 200 || result.DiscountAmount != 200 || result.SettlementAmount != 800 { t.Fatalf("unexpected Alipay breakdown: %+v", result) } } func TestWechatV2BreakdownWithCoupon(t *testing.T) { result := &biz.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV2Breakdown(result, map[string]string{"cash_fee": "900", "coupon_fee": "100", "settlement_total_fee": "950"}) if err != nil { t.Fatal(err) } if !result.AmountBreakdownKnown || result.PayerPaidAmount != 900 || result.DiscountAmount != 100 || result.SettlementAmount != 950 { t.Fatalf("unexpected WeChat v2 breakdown: %+v", result) } } func TestWechatV3BreakdownWithPromotion(t *testing.T) { result := &biz.PaymentResult{Status: "success", Amount: 1000} err := populateWechatV3Breakdown(result, map[string]any{ "amount": map[string]any{"payer_total": float64(800), "settlement_amount": float64(780)}, "promotion_detail": []any{map[string]any{"amount": float64(200)}}, }) if err != nil { t.Fatal(err) } if !result.AmountBreakdownKnown || result.PayerPaidAmount != 800 || result.CashPaidAmount != 800 || result.DiscountAmount != 200 || result.SettlementAmount != 780 { t.Fatalf("unexpected WeChat v3 breakdown: %+v", result) } } func TestVendorBreakdownUsesConfiguredUnits(t *testing.T) { result := &biz.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"} object := map[string]any{"data": map[string]any{ "payer": "8.00", "cash": "6.00", "points": "2.00", "discount": "2.00", "settlement": "7.80", }} config := map[string]any{ "query_amount_scale": "100", "query_payer_paid_amount_field": "data.payer", "query_cash_paid_amount_field": "data.cash", "query_point_paid_amount_field": "data.points", "query_discount_amount_field": "data.discount", "query_settlement_amount_field": "data.settlement", "query_payer_currency_field": "data.currency", } err := populateVendorBreakdown(result, object, config) if err != nil { t.Fatal(err) } if !result.AmountBreakdownKnown || result.PayerPaidAmount != 800 || result.CashPaidAmount != 600 || result.PointPaidAmount != 200 || result.DiscountAmount != 200 || result.SettlementAmount != 780 { t.Fatalf("unexpected vendor breakdown: %+v", result) } }