优化结构

This commit is contained in:
Yvan
2026-08-22 12:13:16 +08:00
parent d987244d0e
commit b27a66e9d3
200 changed files with 1794 additions and 1715 deletions
+21 -22
View File
@@ -3,53 +3,52 @@ package payment
import (
"context"
"fmt"
"kra/internal/biz"
"kra/internal/biz/system"
)
// Adapter is the provider boundary used by the payment repository. Provider
// SDK types stay in this package and are normalized to biz.PaymentResult.
type Adapter interface {
Create(context.Context, *biz.PaymentRequest, map[string]any) (*biz.PaymentResult, error)
Query(context.Context, string, map[string]any) (*biz.PaymentResult, error)
Refund(context.Context, *biz.PaymentRefundRequest, map[string]any) (*biz.PaymentResult, error)
Callback(context.Context, *biz.PaymentCallback, map[string]any) (*biz.PaymentResult, error)
Create(context.Context, *system.PaymentRequest, map[string]any) (*system.PaymentResult, error)
Query(context.Context, string, map[string]any) (*system.PaymentResult, error)
Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error)
Callback(context.Context, *system.PaymentCallback, map[string]any) (*system.PaymentResult, error)
}
// New constructs the SDK-backed adapter for a configured provider.
func New(provider string) (Adapter, error) {
switch provider {
case biz.PaymentAlipay:
case system.PaymentAlipay:
return &alipayAdapter{}, nil
case biz.PaymentAlipayV3:
case system.PaymentAlipayV3:
return &alipayV3Adapter{}, nil
case biz.PaymentWechatV2:
case system.PaymentWechatV2:
return &wechatV2Adapter{}, nil
case biz.PaymentWechatV3:
case system.PaymentWechatV3:
return &wechatV3Adapter{}, nil
case biz.PaymentApple:
case system.PaymentApple:
return &appleAdapter{}, nil
case biz.PaymentDouyin:
case system.PaymentDouyin:
return &douyinAdapter{}, nil
case biz.PaymentQQ:
case system.PaymentQQ:
return &qqAdapter{}, nil
case biz.PaymentAllinPay:
case system.PaymentAllinPay:
return &allinpayAdapter{}, nil
case biz.PaymentLakala:
case system.PaymentLakala:
return &lakalaAdapter{}, nil
case biz.PaymentPayPal:
case system.PaymentPayPal:
return &paypalAdapter{}, nil
case biz.PaymentSaobei:
case system.PaymentSaobei:
return &saobeiAdapter{}, nil
case biz.PaymentChinaums:
case system.PaymentChinaums:
return newVendorAdapter(provider, vendorChinaums), nil
case biz.PaymentSFT:
case system.PaymentSFT:
return newVendorAdapter(provider, vendorSFT), nil
case biz.PaymentSuperPay:
case system.PaymentSuperPay:
return newVendorAdapter(provider, vendorSupperPay), nil
case biz.PaymentWechatGame:
case system.PaymentWechatGame:
return newVendorAdapter(provider, vendorWechatGame), nil
case biz.PaymentDouyinGame:
case system.PaymentDouyinGame:
return newVendorAdapter(provider, vendorDouyinGame), nil
default:
return nil, fmt.Errorf("支付渠道 %s 没有适配器", provider)
+2 -3
View File
@@ -1,13 +1,12 @@
package payment
import (
"kra/internal/biz/system"
"testing"
"kra/internal/biz"
)
func TestEverySupportedProviderHasAdapter(t *testing.T) {
for _, provider := range biz.SupportedPaymentProviders {
for _, provider := range system.SupportedPaymentProviders {
t.Run(provider, func(t *testing.T) {
adapter, err := New(provider)
if err != nil {
+25 -26
View File
@@ -7,14 +7,13 @@ import (
"errors"
"fmt"
"io"
"kra/internal/biz/system"
"net/http"
"net/url"
"sort"
"strings"
"time"
"kra/internal/biz"
"github.com/go-pay/gopay"
gopayAlipay "github.com/go-pay/gopay/alipay"
"github.com/go-pay/gopay/pkg/xhttp"
@@ -39,7 +38,7 @@ type alipayResponseBody struct {
io.Closer
}
func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("支付宝下单参数为空")
}
@@ -92,8 +91,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if status == "success" && strings.TrimSpace(response.TradeNo) == "" {
return nil, errors.New("支付宝付款码支付成功响应缺少 trade_no")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: status, TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: status, TradeNo: req.TradeNo,
ProviderTradeNo: strings.TrimSpace(response.TradeNo), Payload: sdk.payload(rsp),
}, nil
case "alipay.trade.precreate":
@@ -113,8 +112,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
}
// Precreate has no Alipay trade number yet. Keep the QR code in Payload
// and query later by the merchant out_trade_no.
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
QueryID: queryID, Payload: sdk.payload(rsp),
}, nil
case "alipay.trade.app.pay":
@@ -125,8 +124,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if strings.TrimSpace(orderString) == "" {
return nil, errors.New("支付宝 APP 支付参数为空")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
Payload: mustMarshalAlipayPayload(map[string]string{"order_string": orderString}),
}, nil
case "alipay.trade.page.pay":
@@ -141,8 +140,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if err != nil {
return nil, err
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}),
}, nil
case "alipay.trade.wap.pay":
@@ -157,8 +156,8 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if err != nil {
return nil, err
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
Payload: mustMarshalAlipayPayload(map[string]string{"pay_url": payURL}),
}, nil
default:
@@ -174,14 +173,14 @@ func (a *alipayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if providerTradeNo == "" {
return nil, errors.New("支付宝创建响应缺少 trade_no")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: req.TradeNo,
ProviderTradeNo: providerTradeNo, Payload: sdk.payload(rsp),
}, nil
}
}
func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("支付宝查单缺少 out_trade_no")
@@ -196,8 +195,8 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]
return nil, err
}
response := rsp.Response
result := &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"),
result := &system.PaymentResult{
Provider: system.PaymentAlipay, Status: normalizePaymentStatus(response.TradeStatus, "pending"),
TradeNo: strings.TrimSpace(response.OutTradeNo), ProviderTradeNo: strings.TrimSpace(response.TradeNo),
Payload: sdk.payload(rsp),
}
@@ -230,7 +229,7 @@ func (a *alipayAdapter) Query(ctx context.Context, tradeNo string, c map[string]
return result, nil
}
func (a *alipayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("支付宝退款请求为空")
}
@@ -258,7 +257,7 @@ func (a *alipayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques
return alipayRefundResult(req, response, sdk.payload(rsp))
}
func alipayRefundResult(req *biz.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*biz.PaymentResult, error) {
func alipayRefundResult(req *system.PaymentRefundRequest, response *gopayAlipay.TradeRefund, payload []byte) (*system.PaymentResult, error) {
if req == nil || response == nil {
return nil, errors.New("支付宝退款响应为空")
}
@@ -298,13 +297,13 @@ func alipayRefundResult(req *biz.PaymentRefundRequest, response *gopayAlipay.Tra
if currency == "" {
currency = "CNY"
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: "created", TradeNo: tradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: "created", TradeNo: tradeNo,
ProviderTradeNo: providerTradeNo, Amount: amount, Currency: currency, Payload: payload,
}, nil
}
func (a *alipayAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("支付宝回调为空")
}
@@ -320,8 +319,8 @@ func (a *alipayAdapter) Callback(_ context.Context, callback *biz.PaymentCallbac
status = "success"
}
payload, _ := json.Marshal(fields)
return &biz.PaymentResult{
Provider: biz.PaymentAlipay, Status: status,
return &system.PaymentResult{
Provider: system.PaymentAlipay, Status: status,
TradeNo: first(fields, "out_trade_no"), ProviderTradeNo: first(fields, "trade_no"),
Payload: payload,
}, nil
@@ -482,7 +481,7 @@ func firstNonEmptyAlipay(values ...string) string {
return ""
}
func populateAlipayBreakdown(result *biz.PaymentResult, response map[string]any) error {
func populateAlipayBreakdown(result *system.PaymentResult, response map[string]any) error {
if result == nil || result.Status != "success" {
return nil
}
+24 -25
View File
@@ -5,14 +5,13 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"net/http"
"net/url"
"os"
"strings"
"time"
"kra/internal/biz"
"github.com/go-pay/gopay"
gopayAlipay "github.com/go-pay/gopay/alipay"
gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3"
@@ -26,7 +25,7 @@ type alipayV3Client struct {
gateway *url.URL
}
func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("支付宝 V3 下单参数为空")
}
@@ -94,8 +93,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if strings.TrimSpace(rsp.QrCode) == "" {
return nil, errors.New("支付宝 V3 预创建响应缺少 qr_code")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
QueryID: outTradeNo, Payload: mustJSON(rsp),
}, nil
case "alipay.trade.app.pay":
@@ -106,8 +105,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if strings.TrimSpace(orderString) == "" {
return nil, errors.New("支付宝 V3 APP 支付参数为空")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
Payload: mustJSON(map[string]string{"order_string": orderString}),
}, nil
case "alipay.trade.page.pay":
@@ -122,8 +121,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if strings.TrimSpace(payURL) == "" {
return nil, errors.New("支付宝 V3 电脑网站支付 URL 为空")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
Payload: mustJSON(map[string]string{"pay_url": payURL}),
}, nil
case "alipay.trade.wap.pay":
@@ -138,8 +137,8 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if strings.TrimSpace(payURL) == "" {
return nil, errors.New("支付宝 V3 手机网站支付 URL 为空")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
Payload: mustJSON(map[string]string{"pay_url": payURL}),
}, nil
default:
@@ -167,14 +166,14 @@ func (a *alipayV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if providerTradeNo == "" {
return nil, errors.New("支付宝 V3 创建响应缺少 trade_no")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: req.TradeNo,
ProviderTradeNo: providerTradeNo, QueryID: outTradeNo, Payload: mustJSON(rsp),
}, nil
}
}
func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("支付宝 V3 查单缺少 out_trade_no")
@@ -197,8 +196,8 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin
if outTradeNo == "" || outTradeNo != tradeNo {
return nil, errors.New("支付宝 V3 查询响应的 out_trade_no 缺失或不匹配")
}
result := &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"),
result := &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(rsp.TradeStatus, "pending"),
TradeNo: outTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.TradeNo),
Currency: alipayV3Currency(c, ""), Payload: mustJSON(rsp),
}
@@ -222,7 +221,7 @@ func (a *alipayV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin
return result, nil
}
func (a *alipayV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("支付宝 V3 退款请求为空")
}
@@ -273,14 +272,14 @@ func (a *alipayV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ
if refundedAmount != req.Amount {
return nil, errors.New("支付宝 V3 退款响应金额不匹配")
}
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "created", TradeNo: tradeNo,
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "created", TradeNo: tradeNo,
ProviderTradeNo: providerTradeNo, Amount: refundedAmount,
Currency: alipayV3Currency(c, req.Currency), Payload: mustJSON(rsp),
}, nil
}
func (a *alipayV3Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *alipayV3Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("支付宝 V3 回调为空")
}
@@ -306,13 +305,13 @@ func (a *alipayV3Adapter) Callback(_ context.Context, callback *biz.PaymentCallb
return nil, errors.New("支付宝 V3 回调缺少 out_trade_no 或 trade_no")
}
payload, _ := json.Marshal(fields)
return &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"),
return &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: normalizePaymentStatus(fields["trade_status"], "pending"),
TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload,
}, nil
}
func alipayV3TradePayResult(req *biz.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*biz.PaymentResult, error) {
func alipayV3TradePayResult(req *system.PaymentRequest, rsp *gopayAlipayV3.TradePayRsp) (*system.PaymentResult, error) {
if req == nil || rsp == nil {
return nil, errors.New("支付宝 V3 付款码支付响应为空")
}
@@ -331,8 +330,8 @@ func alipayV3TradePayResult(req *biz.PaymentRequest, rsp *gopayAlipayV3.TradePay
if amount != req.Amount {
return nil, errors.New("支付宝 V3 付款码支付响应金额不匹配")
}
result := &biz.PaymentResult{
Provider: biz.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo,
result := &system.PaymentResult{
Provider: system.PaymentAlipayV3, Status: "success", TradeNo: outTradeNo,
ProviderTradeNo: providerTradeNo, Amount: amount,
Currency: strings.ToUpper(strings.TrimSpace(req.Currency)), Payload: mustJSON(rsp),
}
+10 -11
View File
@@ -9,6 +9,7 @@ import (
"encoding/json"
"encoding/pem"
"io"
"kra/internal/biz/system"
"math/big"
"net/http"
"net/http/httptest"
@@ -17,8 +18,6 @@ import (
"testing"
"time"
"kra/internal/biz"
gopayAlipay "github.com/go-pay/gopay/alipay"
gopayAlipayV3 "github.com/go-pay/gopay/alipay/v3"
)
@@ -136,8 +135,8 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) {
if tc.method != "" {
extra["method"] = tc.method
}
result, err := (&alipayV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000,
result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAlipayV3, TradeNo: tradeNo, Subject: "subject", Amount: 1000,
Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify", Extra: extra,
}, config)
if err != nil {
@@ -146,7 +145,7 @@ func TestAlipayV3CreateRESTMethodsUseGoPay(t *testing.T) {
if requestBody == nil || requestBody["out_trade_no"] != tradeNo || requestBody["notify_url"] == "" {
t.Fatalf("request body = %#v", requestBody)
}
if result.Provider != biz.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade {
if result.Provider != system.PaymentAlipayV3 || result.Status != tc.wantStatus || result.TradeNo != tradeNo || result.ProviderTradeNo != tc.wantTrade {
t.Fatalf("result = %+v", result)
}
if tc.method == "barcode" {
@@ -169,8 +168,8 @@ func TestAlipayV3GeneratedPaymentMethodsUseGoPay(t *testing.T) {
config["gateway_url"] = "https://merchant-gateway.example/gateway.do"
for _, method := range []string{"app", "page", "wap"} {
t.Run(method, func(t *testing.T) {
result, err := (&alipayV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject",
result, err := (&alipayV3Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAlipayV3, TradeNo: "T-V3-" + strings.ToUpper(method), Subject: "subject",
Amount: 1000, Currency: "CNY", NotifyURL: "https://merchant.example/alipay-v3/notify",
ReturnURL: "https://merchant.example/alipay-v3/return", Extra: map[string]any{"method": method},
}, config)
@@ -273,8 +272,8 @@ func TestAlipayV3RefundValidatesResponse(t *testing.T) {
defer server.Close()
config := cloneAlipayV3Config(baseConfig)
config["api_base_url"] = server.URL
result, err := (&alipayV3Adapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
Provider: biz.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND",
result, err := (&alipayV3Adapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
Provider: system.PaymentAlipayV3, TradeNo: "T-V3-REFUND", ProviderTradeNo: "P-V3-REFUND",
RefundNo: "R-V3-REFUND", Amount: 123, Currency: "CNY",
}, config)
if tc.wantError {
@@ -316,14 +315,14 @@ func TestAlipayV3CallbackUsesGoPayCertificateVerification(t *testing.T) {
for key, value := range fields {
values.Set(key, value)
}
callback := &biz.PaymentCallback{
callback := &system.PaymentCallback{
Body: []byte(values.Encode()), Headers: map[string]string{"Content-Type": "application/x-www-form-urlencoded"},
}
result, err := (&alipayV3Adapter{}).Callback(context.Background(), callback, config)
if err != nil {
t.Fatal(err)
}
if result.Provider != biz.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" {
if result.Provider != system.PaymentAlipayV3 || result.Status != "success" || result.TradeNo != "T-V3-CALLBACK" || result.ProviderTradeNo != "P-V3-CALLBACK" {
t.Fatalf("callback result = %+v", result)
}
callback.Body = []byte(strings.ReplaceAll(values.Encode(), "P-V3-CALLBACK", "P-V3-TAMPERED"))
+11 -12
View File
@@ -11,12 +11,11 @@ import (
"errors"
"fmt"
"io"
"kra/internal/biz/system"
"net/http"
"strings"
"sync"
"kra/internal/biz"
"github.com/go-pay/crypto/xpem"
"github.com/go-pay/crypto/xrsa"
"github.com/go-pay/gopay"
@@ -121,7 +120,7 @@ func (a *allinpayAdapter) client(c map[string]any) (*allinpay.Client, error) {
return client, nil
}
func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *allinpayAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("通联支付下单请求为空")
}
@@ -159,7 +158,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if returnedTradeNo := strings.TrimSpace(rsp.Reqsn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) {
return nil, errors.New("通联支付扫码响应的 reqsn 不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
}
if method == "native" {
if orderType == allinpay.OrderTypeTrxId {
@@ -176,7 +175,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if returnedTradeNo := strings.TrimSpace(rsp.ReqSn); returnedTradeNo != "" && returnedTradeNo != strings.TrimSpace(req.TradeNo) {
return nil, errors.New("通联支付 Native 响应的 reqsn 不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}, nil
}
payType := firstAny(req.Extra, "paytype", "pay_type")
if payType == "" {
@@ -200,7 +199,7 @@ func (a *allinpayAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if err != nil {
return nil, err
}
return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: "created", TradeNo: req.TradeNo, ProviderTradeNo: strings.TrimSpace(rsp.Trxid), QueryID: queryID, Payload: mustJSON(rsp)}, nil
}
func allinpayCreateMethod(extra, config map[string]any) (string, error) {
@@ -221,7 +220,7 @@ func allinpayCreateMethod(extra, config map[string]any) (string, error) {
}
}
func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
orderType, err := allinpayOrderType(c)
if err != nil {
return nil, err
@@ -248,7 +247,7 @@ func (a *allinpayAdapter) Query(ctx context.Context, tradeNo string, c map[strin
if rsp == nil {
return nil, errors.New("通联支付查单响应为空")
}
result := &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)}
result := &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeAllinState(rsp.TrxStatus), TradeNo: strings.TrimSpace(rsp.Reqsn), ProviderTradeNo: strings.TrimSpace(rsp.Trxid), Currency: strings.ToUpper(firstAny(c, "currency")), Payload: mustJSON(rsp)}
if strings.EqualFold(orderType, allinpay.OrderTypeTrxId) {
if result.ProviderTradeNo == "" {
return nil, errors.New("通联支付查单响应缺少 trxid")
@@ -301,7 +300,7 @@ func allinpayCreateQueryID(orderType, transactionID string) (string, error) {
return "", nil
}
func (a *allinpayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *allinpayAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("通联支付退款请求为空")
}
@@ -346,7 +345,7 @@ func (a *allinpayAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ
return allinpayRefundResult(&effective, rsp, firstAny(c, "currency"))
}
func allinpayRefundResult(req *biz.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*biz.PaymentResult, error) {
func allinpayRefundResult(req *system.PaymentRefundRequest, rsp *allinpay.RefundRsp, currency string) (*system.PaymentResult, error) {
if rsp == nil {
return nil, errors.New("通联支付退款响应为空")
}
@@ -369,9 +368,9 @@ func allinpayRefundResult(req *biz.PaymentRefundRequest, rsp *allinpay.RefundRsp
if refundAmount != req.Amount {
return nil, errors.New("通联支付退款响应金额不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentAllinPay, Status: normalizeRefundState(rsp.TrxStatus), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(strings.TrimSpace(currency)), Payload: mustJSON(rsp)}, nil
}
func (a *allinpayAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) {
func (a *allinpayAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) {
return nil, errors.New("通联支付回调没有可复用的 GoPay 验签器,请改用主动查单")
}
+10 -11
View File
@@ -11,6 +11,7 @@ import (
"encoding/base64"
"encoding/json"
"io"
"kra/internal/biz/system"
"net"
"net/http"
"net/http/httptest"
@@ -18,8 +19,6 @@ import (
"strings"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/allinpay"
)
@@ -121,8 +120,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) {
_, _ = w.Write(response)
}))
_, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject",
_, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-TRXID-MISSING", Subject: "subject",
Amount: 100, Currency: "CNY",
}, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId))
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "trxid") {
@@ -132,8 +131,8 @@ func TestAllinPayTrxIDCreateRejectsResponseWithoutTransactionID(t *testing.T) {
func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) {
_, privateKey, publicKey := allinPayTestKeys(t)
_, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject",
_, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-NATIVE-TRXID", Subject: "subject",
Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "native", "expiretime": "20261231235959"},
}, allinPayTestConfig(privateKey, publicKey, allinpay.OrderTypeTrxId))
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "native") || !strings.Contains(strings.ToLower(err.Error()), "trxid") {
@@ -142,8 +141,8 @@ func TestAllinPayNativeCreateRejectsTrxIDLookupMode(t *testing.T) {
}
func TestAllinPayCreateRejectsUnknownMethodBeforeClient(t *testing.T) {
_, err := (&allinpayAdapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject",
_, err := (&allinpayAdapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAllinPay, TradeNo: "MERCHANT-UNKNOWN-METHOD", Subject: "subject",
Amount: 100, Currency: "CNY", Extra: map[string]any{"method": "unsupported"},
}, nil)
if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") {
@@ -249,8 +248,8 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) {
_, _ = w.Write(response)
}))
result, err := (&allinpayAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
Provider: biz.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID,
result, err := (&allinpayAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
Provider: system.PaymentAllinPay, TradeNo: tc.tradeNo, QueryID: tc.queryID,
RefundNo: "REFUND-1", Amount: 40, TotalAmount: 100, Currency: "CNY",
}, allinPayTestConfig(privateKey, publicKey, tc.orderType))
if err != nil {
@@ -264,7 +263,7 @@ func TestAllinPayRefundUsesConfiguredLookupIdentity(t *testing.T) {
}
func TestAllinPayRefundResultBindsRefundIdentity(t *testing.T) {
req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"}
req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 40, Currency: "CNY"}
base := func() *allinpay.RefundRsp {
return &allinpay.RefundRsp{Reqsn: "REFUND-1", Trxid: "ALLIN-REFUND-1", TrxStatus: "SUCCESS", Fee: "40"}
}
+9 -10
View File
@@ -5,18 +5,17 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"math"
"strings"
"kra/internal/biz"
gopayApple "github.com/go-pay/gopay/apple"
"github.com/google/uuid"
)
type appleAdapter struct{}
func (a *appleAdapter) Create(_ context.Context, req *biz.PaymentRequest, _ map[string]any) (*biz.PaymentResult, error) {
func (a *appleAdapter) Create(_ context.Context, req *system.PaymentRequest, _ map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("Apple 内购下单参数为空")
}
@@ -31,9 +30,9 @@ func (a *appleAdapter) Create(_ context.Context, req *biz.PaymentRequest, _ map[
return nil, errors.New("Apple 内购 tradeNo 必须是用于 appAccountToken 的 UUID")
}
payload, _ := json.Marshal(map[string]any{"product_id": productID, "app_account_token": req.TradeNo, "client_only": true, "trade_no": req.TradeNo})
return &biz.PaymentResult{Provider: biz.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentApple, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
}
func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*biz.PaymentResult, error) {
func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[string]any) (*system.PaymentResult, error) {
transactionID = strings.TrimSpace(transactionID)
if transactionID == "" {
return nil, errors.New("Apple 查询缺少 transactionId")
@@ -82,10 +81,10 @@ func (a *appleAdapter) Query(ctx context.Context, transactionID string, c map[st
}
return result, nil
}
func (a *appleAdapter) Refund(context.Context, *biz.PaymentRefundRequest, map[string]any) (*biz.PaymentResult, error) {
func (a *appleAdapter) Refund(context.Context, *system.PaymentRefundRequest, map[string]any) (*system.PaymentResult, error) {
return nil, errors.New("Apple 内购退款由 App Store 管理,服务端不提供主动退款接口")
}
func (a *appleAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *appleAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil {
return nil, errors.New("Apple 回调为空")
}
@@ -140,7 +139,7 @@ func (a *appleAdapter) Callback(_ context.Context, callback *biz.PaymentCallback
if _, parseErr := uuid.Parse(tradeNo); parseErr != nil {
return nil, errors.New("Apple 回调 appAccountToken 不是有效 UUID")
}
return &biz.PaymentResult{Provider: biz.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentApple, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: transactionID, QueryID: transactionID, Payload: payload}, nil
}
func appleClaims(value any) (map[string]any, error) {
@@ -155,7 +154,7 @@ func appleClaims(value any) (map[string]any, error) {
return claims, nil
}
func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*biz.PaymentResult, error) {
func applePaymentResult(transactionID string, claims map[string]any, payload []byte, c map[string]any) (*system.PaymentResult, error) {
revoked, err := appleRevoked(claims)
if err != nil {
return nil, err
@@ -175,7 +174,7 @@ func applePaymentResult(transactionID string, claims map[string]any, payload []b
if _, err := uuid.Parse(tradeNo); err != nil {
return nil, errors.New("Apple 交易 appAccountToken 不是有效 UUID")
}
result := &biz.PaymentResult{Provider: biz.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload}
result := &system.PaymentResult{Provider: system.PaymentApple, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, QueryID: providerTradeNo, Payload: payload}
if status != "success" {
return result, nil
}
+15 -16
View File
@@ -5,10 +5,9 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"strings"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/douyin"
)
@@ -35,7 +34,7 @@ func (a *douyinAdapter) client(c map[string]any) (*douyin.Client, error) {
return client, nil
}
func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *douyinAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("抖音支付下单请求为空")
}
@@ -78,7 +77,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
return nil, errors.New("抖音支付 H5 下单响应缺少 h5_url")
}
payload, _ := json.Marshal(rsp.Response)
return &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
case "native":
rsp, callErr := client.NativeOrder(ctx, bm)
if callErr != nil {
@@ -91,7 +90,7 @@ func (a *douyinAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
return nil, errors.New("抖音支付 Native 下单响应缺少 code_url")
}
payload, _ := json.Marshal(rsp.Response)
return &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentDouyin, Status: "client_pending", TradeNo: req.TradeNo, Payload: payload}, nil
case "jsapi":
rsp, callErr := client.JsapiOrder(ctx, bm)
if callErr != nil {
@@ -126,7 +125,7 @@ func douyinCreateMethod(extra, config map[string]any) (string, error) {
}
}
func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*biz.PaymentResult, error) {
func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string, app bool) (*system.PaymentResult, error) {
if client == nil || strings.TrimSpace(prepayID) == "" {
return nil, errors.New("抖音支付下单响应缺少 prepay_id")
}
@@ -142,15 +141,15 @@ func douyinPrepayResult(client *douyin.Client, appID, tradeNo, prepayID string,
if err != nil {
return nil, fmt.Errorf("生成抖音支付客户端调起参数: %w", err)
}
return &biz.PaymentResult{
Provider: biz.PaymentDouyin,
return &system.PaymentResult{
Provider: system.PaymentDouyin,
Status: "client_pending",
TradeNo: tradeNo,
Payload: mustJSON(params),
}, nil
}
func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("抖音支付查单缺少 out_trade_no")
@@ -178,7 +177,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]
payload = append([]byte(nil), signedBody...)
}
}
result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload}
result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(order.TradeState), TradeNo: order.OutTradeNo, ProviderTradeNo: order.TransactionId, Payload: payload}
if result.TradeNo == "" {
result.TradeNo = tradeNo
} else if result.TradeNo != tradeNo {
@@ -190,7 +189,7 @@ func (a *douyinAdapter) Query(ctx context.Context, tradeNo string, c map[string]
return result, nil
}
func (a *douyinAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *douyinAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("抖音支付退款请求为空")
}
@@ -227,7 +226,7 @@ func (a *douyinAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques
if err != nil {
return nil, err
}
result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)}
result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeRefundState(r.Status), TradeNo: tradeNo, ProviderTradeNo: refundID, Payload: mustJSON(r)}
if r.Amount == nil {
return nil, errors.New("抖音支付退款响应缺少金额")
}
@@ -278,7 +277,7 @@ func validateDouyinRefundIdentity(refund *douyin.Refund, tradeNo, refundNo strin
return refundID, nil
}
func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *douyinAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil {
return nil, errors.New("抖音支付回调为空")
}
@@ -317,7 +316,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb
if err != nil {
return nil, err
}
result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id}
result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: normalizeDouyinState(pay.TradeState), TradeNo: pay.OutTradeNo, ProviderTradeNo: pay.TransactionId, Payload: append([]byte(nil), plain...), EventID: notify.Id}
if err = populateDouyinPaymentAmount(result, pay.Amount, plain); err != nil {
return nil, err
}
@@ -339,7 +338,7 @@ func (a *douyinAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb
} else if strings.EqualFold(refund.RefundStatus, douyin.RefundStatusClosed) || strings.EqualFold(refund.RefundStatus, douyin.RefundStatusAbnormal) {
status = "failed"
}
result := &biz.PaymentResult{Provider: biz.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id}
result := &system.PaymentResult{Provider: system.PaymentDouyin, Status: status, TradeNo: refund.OutTradeNo, ProviderTradeNo: refund.RefundId, Payload: mustJSON(refund), EventID: notify.Id}
if refund.Amount != nil {
result.Amount = int64(refund.Amount.Refund)
result.Currency = strings.ToUpper(firstAny(c, "currency"))
@@ -366,7 +365,7 @@ func validateDouyinMerchantIdentity(merchantID, appID string, c map[string]any,
return nil
}
func populateDouyinPaymentAmount(result *biz.PaymentResult, amount *douyin.AmountInfo, raw []byte) error {
func populateDouyinPaymentAmount(result *system.PaymentResult, amount *douyin.AmountInfo, raw []byte) error {
if result == nil || amount == nil {
return nil
}
@@ -4,11 +4,10 @@ import (
"bytes"
"encoding/json"
"fmt"
"kra/internal/biz/system"
"net/http"
"strings"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/douyin"
)
@@ -16,7 +15,7 @@ import (
// The adapters in this package intentionally keep GoPay request/response
// models below the data boundary. The business layer only sees PaymentResult.
func callbackRequest(callback *biz.PaymentCallback) (*http.Request, error) {
func callbackRequest(callback *system.PaymentCallback) (*http.Request, error) {
request, err := http.NewRequest(http.MethodPost, "http://payment-callback.local", bytes.NewReader(callback.Body))
if err != nil {
return nil, err
+13 -13
View File
@@ -14,13 +14,13 @@ import (
"encoding/pem"
"fmt"
"io"
"kra/internal/biz/system"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"kra/internal/biz"
"kra/pkg/paymentkit"
gopayAlipay "github.com/go-pay/gopay/alipay"
@@ -125,8 +125,8 @@ func TestAlipayTradePayUsesGoPaySDK(t *testing.T) {
}))
defer server.Close()
result, err := (&alipayAdapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000,
result, err := (&alipayAdapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentAlipay, TradeNo: "T-ALIPAY-PAY", Subject: "subject", Amount: 1000,
Currency: "CNY", Extra: map[string]any{"method": "barcode", "auth_code": "BARCODE-1"},
}, map[string]any{
"app_id": "app-test", "private_key": privatePEM, "public_key": publicPEM, "gateway_url": server.URL,
@@ -194,7 +194,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
}))
defer server.Close()
result, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{
result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{
TradeNo: "T-WECHAT", Subject: "subject", Amount: 123, Currency: "CNY", NotifyURL: "http://notify.local",
}, map[string]any{
"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
@@ -220,7 +220,7 @@ func TestWechatV2GoPayCallbackVerificationAndSandboxAmount(t *testing.T) {
t.Fatal("verification mutated source values")
}
callbackBody := paymentutil.XMLEncode(callbackValues)
callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &biz.PaymentCallback{Body: callbackBody}, map[string]any{
callbackResult, err := (&wechatV2Adapter{}).Callback(context.Background(), &system.PaymentCallback{Body: callbackBody}, map[string]any{
"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
})
if err != nil {
@@ -267,8 +267,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) {
}))
defer server.Close()
result, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
result, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
Currency: "CNY", NotifyURL: "http://notify.local",
Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-CODE-1"},
}, map[string]any{
@@ -287,8 +287,8 @@ func TestWechatV2MicropayUsesGoPaySDKAndValidatesIdentities(t *testing.T) {
}
func TestWechatV2MicropayRejectsMissingAuthCodeAndUnknownMode(t *testing.T) {
_, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1,
_, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO-MISSING", Subject: "subject", Amount: 1,
Currency: "CNY", NotifyURL: "http://notify.local", Extra: map[string]any{"method": "micropay"},
}, map[string]any{"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": "wechat-secret"})
if err == nil || !strings.Contains(err.Error(), "auth_code") {
@@ -348,8 +348,8 @@ func TestWechatV2MicropayRejectsInvalidSuccessResponse(t *testing.T) {
}))
defer server.Close()
_, err := (&wechatV2Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
_, err := (&wechatV2Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentWechatV2, TradeNo: "T-WECHAT-MICRO", Subject: "subject", Amount: 123,
Currency: "CNY", Extra: map[string]any{"method": "micropay", "auth_code": "AUTH-CODE-1"},
}, map[string]any{
"app_id": "wx-test", "merchant_id": "mch-test", "mch_key": key,
@@ -520,7 +520,7 @@ func TestDouyinPrepayResultUsesGoPayClientSigning(t *testing.T) {
if err = json.Unmarshal(result.Payload, &payload); err != nil {
t.Fatal(err)
}
if result.Provider != biz.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" {
if result.Provider != system.PaymentDouyin || result.TradeNo != "T-DOUYIN" || strings.TrimSpace(fmt.Sprint(payload["sign"])) == "" && strings.TrimSpace(fmt.Sprint(payload["paySign"])) == "" {
t.Fatalf("app=%v result=%+v payload=%#v", app, result, payload)
}
if app {
@@ -581,7 +581,7 @@ func TestWechatV3GoPayCallbackVerificationAndDecryption(t *testing.T) {
t.Fatal(err)
}
signature := testRSA2Sign(t, platformKey, []byte(timestamp+"\n"+headerNonce+"\n"+string(body)+"\n"))
callback := &biz.PaymentCallback{
callback := &system.PaymentCallback{
Body: body,
Headers: map[string]string{
gopayWechatV3.HeaderTimestamp: timestamp,
@@ -12,14 +12,13 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"kra/internal/biz/system"
"net"
"net/http"
"net/http/httptest"
"strings"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/allinpay"
"github.com/go-pay/gopay/douyin"
@@ -117,7 +116,7 @@ func TestLakalaKeepsQueryAndFinalTradeIdentitiesSeparate(t *testing.T) {
t.Fatalf("query result = %+v", result)
}
refund, err := (&lakalaAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
refund, err := (&lakalaAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
TradeNo: "MERCHANT-LAKALA-1", QueryID: created.QueryID,
RefundNo: "REFUND-LAKALA-1", Amount: 40, Currency: "JPY",
}, config)
@@ -217,8 +216,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) {
if tc.configMethod != "" {
config["method"] = tc.configMethod
}
result, err := adapter.Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100,
result, err := adapter.Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentLakala, TradeNo: tc.tradeNo, Subject: "subject", Amount: 100,
Currency: "JPY", Extra: extra,
}, config)
if err != nil {
@@ -239,8 +238,8 @@ func TestLakalaCreateRoutesSupportedMethods(t *testing.T) {
}
func TestLakalaCreateRejectsUnknownMethod(t *testing.T) {
_, err := (&lakalaAdapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100,
_, err := (&lakalaAdapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentLakala, TradeNo: "L-UNKNOWN", Subject: "subject", Amount: 100,
Currency: "JPY", Extra: map[string]any{"method": "unsupported"},
}, map[string]any{"partner_code": "PART", "credential_code": "credential", "channel": "Wechat"})
if err == nil || !strings.Contains(err.Error(), "不支持的下单方式") {
+14 -15
View File
@@ -4,10 +4,9 @@ import (
"context"
"errors"
"fmt"
"kra/internal/biz/system"
"strings"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/lakala"
)
@@ -18,7 +17,7 @@ func (a *lakalaAdapter) client(c map[string]any) (*lakala.Client, error) {
return lakala.NewClient(text(c, "partner_code"), text(c, "credential_code"), !strings.EqualFold(text(c, "environment"), "sandbox"))
}
func (a *lakalaAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *lakalaAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("拉卡拉支付下单请求为空")
}
@@ -75,7 +74,7 @@ func lakalaCreateMethod(extra, config map[string]any) string {
return strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(method)))
}
func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*biz.PaymentResult, error) {
func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*system.PaymentResult, error) {
if rsp == nil || !lakalaCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) {
return nil, fmt.Errorf("拉卡拉支付下单失败: %v", rsp)
}
@@ -86,10 +85,10 @@ func lakalaCreateResult(tradeNo string, rsp *lakala.PaymentRsp) (*biz.PaymentRes
if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo {
return nil, errors.New("拉卡拉支付下单响应的商户订单号不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo, QueryID: orderID, Payload: mustJSON(rsp)}, nil
}
func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*biz.PaymentResult, error) {
func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*system.PaymentResult, error) {
if rsp == nil || !lakalaRetailCreateResponseOK(rsp.ReturnCode, rsp.ResultCode) {
return nil, fmt.Errorf("拉卡拉线下支付下单失败: %v", rsp)
}
@@ -100,15 +99,15 @@ func lakalaRetailCreateResult(tradeNo string, rsp *lakala.RetailPayRsp) (*biz.Pa
if partnerOrderID := strings.TrimSpace(rsp.PartnerOrderId); partnerOrderID != "" && partnerOrderID != tradeNo {
return nil, errors.New("拉卡拉线下支付下单响应的商户订单号不匹配")
}
result := &biz.PaymentResult{
Provider: biz.PaymentLakala, Status: "created", TradeNo: tradeNo,
result := &system.PaymentResult{
Provider: system.PaymentLakala, Status: "created", TradeNo: tradeNo,
QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency),
Payload: mustJSON(rsp),
}
return result, nil
}
func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
client, err := a.client(c)
if err != nil {
return nil, err
@@ -132,7 +131,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]
if returnedTradeNo == "" {
return nil, errors.New("拉卡拉支付查单响应缺少商户订单号")
}
result := &biz.PaymentResult{Provider: biz.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)}
result := &system.PaymentResult{Provider: system.PaymentLakala, Status: status, TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.ChannelOrderId), QueryID: orderID, Amount: int64(rsp.TotalFee), Currency: strings.ToUpper(rsp.Currency), Payload: mustJSON(rsp)}
if result.Currency == "" {
result.Currency = strings.ToUpper(firstAny(c, "currency", "default_currency"))
}
@@ -150,7 +149,7 @@ func (a *lakalaAdapter) Query(ctx context.Context, tradeNo string, c map[string]
return result, nil
}
func (a *lakalaAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *lakalaAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("拉卡拉支付退款请求为空")
}
@@ -182,7 +181,7 @@ func (a *lakalaAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques
return lakalaRefundResult(&effective, rsp)
}
func lakalaRefundResult(req *biz.PaymentRefundRequest, rsp *lakala.RefundRsp) (*biz.PaymentResult, error) {
func lakalaRefundResult(req *system.PaymentRefundRequest, rsp *lakala.RefundRsp) (*system.PaymentResult, error) {
if rsp == nil || !lakalaRefundResponseOK(rsp.ReturnCode, rsp.ResultCode) {
return nil, fmt.Errorf("拉卡拉支付退款失败: %v", rsp)
}
@@ -205,10 +204,10 @@ func lakalaRefundResult(req *biz.PaymentRefundRequest, rsp *lakala.RefundRsp) (*
} else if requestCurrency != "" && currency != requestCurrency {
return nil, errors.New("拉卡拉支付退款响应币种不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentLakala, Status: normalizeLakalaRefundState(rsp.ResultCode), TradeNo: req.TradeNo, ProviderTradeNo: providerRefundID, Amount: int64(rsp.Amount), Currency: currency, Payload: mustJSON(rsp)}, nil
}
func (a *lakalaAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *lakalaAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil {
return nil, errors.New("拉卡拉支付回调为空")
}
@@ -223,5 +222,5 @@ func (a *lakalaAdapter) Callback(_ context.Context, callback *biz.PaymentCallbac
if err := lakala.VerifySign(notify, text(c, "partner_code"), text(c, "credential_code")); err != nil {
return nil, err
}
return &biz.PaymentResult{Provider: biz.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil
return &system.PaymentResult{Provider: system.PaymentLakala, Status: "success", TradeNo: notify.PartnerOrderId, ProviderTradeNo: strings.TrimSpace(notify.ChannelOrderId), QueryID: strings.TrimSpace(notify.OrderId), Amount: int64(notify.TotalFee), Currency: strings.ToUpper(notify.Currency), Payload: mustJSON(notify)}, nil
}
+3 -4
View File
@@ -1,10 +1,9 @@
package payment
import (
"kra/internal/biz/system"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay/lakala"
)
@@ -59,7 +58,7 @@ func TestLakalaQueryStateMapping(t *testing.T) {
}
func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) {
req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"}
req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"}
base := func() *lakala.RefundRsp {
return &lakala.RefundRsp{
ErrorCode: lakala.ErrorCode{ReturnCode: "SUCCESS", ResultCode: "SUCCESS"},
@@ -99,7 +98,7 @@ func TestLakalaRefundResultBindsRefundIdentity(t *testing.T) {
}
func TestLakalaRefundResultMapsProviderState(t *testing.T) {
req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"}
req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 50, Currency: "JPY"}
for _, tc := range []struct {
code string
want string
+5 -6
View File
@@ -4,11 +4,10 @@ import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"kra/internal/biz/system"
"net/url"
"strings"
"testing"
"kra/internal/biz"
)
func TestWechatV2SignOfficialStyle(t *testing.T) {
@@ -95,7 +94,7 @@ func TestAlipaySignContentSortedAndExcludesSign(t *testing.T) {
}
func TestAlipayBreakdownWithPointPayment(t *testing.T) {
result := &biz.PaymentResult{Status: "success", Amount: 1000}
result := &system.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",
})
@@ -108,7 +107,7 @@ func TestAlipayBreakdownWithPointPayment(t *testing.T) {
}
func TestWechatV2BreakdownWithCoupon(t *testing.T) {
result := &biz.PaymentResult{Status: "success", Amount: 1000}
result := &system.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)
@@ -119,7 +118,7 @@ func TestWechatV2BreakdownWithCoupon(t *testing.T) {
}
func TestWechatV3BreakdownWithPromotion(t *testing.T) {
result := &biz.PaymentResult{Status: "success", Amount: 1000}
result := &system.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)}},
@@ -133,7 +132,7 @@ func TestWechatV3BreakdownWithPromotion(t *testing.T) {
}
func TestVendorBreakdownUsesConfiguredUnits(t *testing.T) {
result := &biz.PaymentResult{Status: "success", Amount: 1000, Currency: "CNY"}
result := &system.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",
}}
+18 -19
View File
@@ -5,12 +5,11 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"math"
"strconv"
"strings"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/paypal"
)
@@ -32,7 +31,7 @@ func (a *paypalAdapter) client(c map[string]any) (*paypal.Client, error) {
return client, nil
}
func (a *paypalAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *paypalAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("PayPal 下单参数为空")
}
@@ -75,7 +74,7 @@ func (a *paypalAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
return paypalCreateResult(req, order, c)
}
func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*biz.PaymentResult, error) {
func (a *paypalAdapter) Query(ctx context.Context, orderID string, c map[string]any) (*system.PaymentResult, error) {
orderID = strings.TrimSpace(orderID)
if orderID == "" {
return nil, errors.New("PayPal 查单缺少 order ID")
@@ -154,7 +153,7 @@ func paypalAutoCaptureEnabled(c map[string]any) bool {
return true
}
func (a *paypalAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *paypalAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("PayPal 退款请求为空")
}
@@ -202,7 +201,7 @@ func (a *paypalAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques
return paypalRefundResult(req, rsp.Response, c)
}
func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*biz.PaymentResult, error) {
func paypalRefundResult(req *system.PaymentRefundRequest, refund *paypal.PaymentCaptureRefund, c map[string]any) (*system.PaymentResult, error) {
if req == nil || refund == nil {
return nil, errors.New("PayPal 退款响应为空")
}
@@ -216,8 +215,8 @@ func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCap
return nil, errors.New("PayPal 退款响应缺少 refund ID")
}
currency := strings.ToUpper(strings.TrimSpace(req.Currency))
result := &biz.PaymentResult{
Provider: biz.PaymentPayPal,
result := &system.PaymentResult{
Provider: system.PaymentPayPal,
Status: normalizePayPalRefundState(refund.Status),
TradeNo: req.TradeNo,
ProviderTradeNo: providerRefundID,
@@ -247,7 +246,7 @@ func paypalRefundResult(req *biz.PaymentRefundRequest, refund *paypal.PaymentCap
return result, nil
}
func paypalCreateResult(req *biz.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*biz.PaymentResult, error) {
func paypalCreateResult(req *system.PaymentRequest, order *paypal.OrderDetail, c map[string]any) (*system.PaymentResult, error) {
if req == nil || order == nil {
return nil, errors.New("PayPal 下单响应为空")
}
@@ -278,7 +277,7 @@ func paypalCreateResult(req *biz.PaymentRequest, order *paypal.OrderDetail, c ma
return result, nil
}
func (a *paypalAdapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *paypalAdapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("PayPal 回调为空")
}
@@ -326,7 +325,7 @@ func (a *paypalAdapter) Callback(ctx context.Context, callback *biz.PaymentCallb
return result, nil
}
func paypalApplicationContext(req *biz.PaymentRequest, c map[string]any) map[string]any {
func paypalApplicationContext(req *system.PaymentRequest, c map[string]any) map[string]any {
applicationContext := map[string]any{}
if configured, ok := req.Extra["application_context"].(map[string]any); ok {
for key, value := range configured {
@@ -353,7 +352,7 @@ func paypalApplicationContext(req *biz.PaymentRequest, c map[string]any) map[str
return applicationContext
}
func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo string, c map[string]any) (*system.PaymentResult, error) {
if order == nil {
return nil, errors.New("PayPal 订单结果为空")
}
@@ -365,8 +364,8 @@ func paypalOrderResult(order *paypal.OrderDetail, queryID, fallbackTradeNo strin
tradeNo = fallbackTradeNo
}
capture := paypalOrderCapture(order)
result := &biz.PaymentResult{
Provider: biz.PaymentPayPal,
result := &system.PaymentResult{
Provider: system.PaymentPayPal,
Status: paypalOrderCaptureState(order),
TradeNo: tradeNo,
QueryID: queryID,
@@ -499,7 +498,7 @@ func paypalOrderAmount(order *paypal.OrderDetail, c map[string]any) (int64, stri
return total, currency, found, nil
}
func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *biz.PaymentRefundRequest) (string, error) {
func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *system.PaymentRefundRequest) (string, error) {
if req == nil {
return "", errors.New("PayPal 退款请求为空")
}
@@ -534,7 +533,7 @@ func paypalRefundCaptureID(ctx context.Context, client *paypal.Client, req *biz.
return capture.Id, nil
}
func paypalWebhookResult(event *paypal.WebhookEvent) (*biz.PaymentResult, error) {
func paypalWebhookResult(event *paypal.WebhookEvent) (*system.PaymentResult, error) {
var resource map[string]any
if err := json.Unmarshal(event.Resource, &resource); err != nil {
return nil, fmt.Errorf("PayPal 回调 resource 无效: %w", err)
@@ -568,8 +567,8 @@ func paypalWebhookResult(event *paypal.WebhookEvent) (*biz.PaymentResult, error)
if tradeNo == "" || queryID == "" {
return nil, errors.New("PayPal 回调缺少商户订单号或 order ID")
}
return &biz.PaymentResult{
Provider: biz.PaymentPayPal,
return &system.PaymentResult{
Provider: system.PaymentPayPal,
Status: status,
TradeNo: tradeNo,
ProviderTradeNo: providerTradeNo,
@@ -588,7 +587,7 @@ func paypalPurchaseUnitTradeNo(resource map[string]any) string {
return ""
}
func paypalCallbackHeader(callback *biz.PaymentCallback, name string) string {
func paypalCallbackHeader(callback *system.PaymentCallback, name string) string {
for key, value := range callback.Headers {
if strings.EqualFold(key, name) {
return strings.TrimSpace(value)
+11 -12
View File
@@ -3,13 +3,12 @@ package payment
import (
"context"
"encoding/json"
"kra/internal/biz/system"
"net/http"
"net/http/httptest"
"strings"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay/paypal"
)
@@ -52,7 +51,7 @@ func TestPayPalCreateAndQueryKeepOrderAndCaptureIDsSeparate(t *testing.T) {
adapter := &paypalAdapter{}
config := paypalTestConfig(server.URL)
created, err := adapter.Create(context.Background(), &biz.PaymentRequest{
created, err := adapter.Create(context.Background(), &system.PaymentRequest{
TradeNo: "LOCAL-1",
Subject: "Order one",
Amount: 1099,
@@ -143,7 +142,7 @@ func TestPayPalCreateRejectsMismatchedResponseIdentityAndAmount(t *testing.T) {
}))
defer server.Close()
_, err := (&paypalAdapter{}).Create(context.Background(), &biz.PaymentRequest{
_, err := (&paypalAdapter{}).Create(context.Background(), &system.PaymentRequest{
TradeNo: "LOCAL-1", Subject: "Order one", Amount: 1099, Currency: "USD",
}, paypalTestConfig(server.URL))
if err == nil || !strings.Contains(err.Error(), tc.want) {
@@ -243,8 +242,8 @@ func TestPayPalRefundUsesPersistedCaptureID(t *testing.T) {
config := paypalTestConfig(server.URL)
config["provider_trade_no"] = "WRONG-CONFIG-CAPTURE"
result, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1",
result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", ProviderTradeNo: "CAPTURE-1",
QueryID: "ORDER-1", RefundNo: "REFUND-1", Amount: 123, TotalAmount: 1099, Currency: "USD",
}, config)
if err != nil {
@@ -287,8 +286,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) {
config := paypalTestConfig(server.URL)
config["capture_id"] = "WRONG-CONFIG-CAPTURE"
result, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1",
result, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", QueryID: "ORDER-1",
RefundNo: "REFUND-2", Amount: 123, TotalAmount: 1099, Currency: "USD",
}, config)
if err != nil {
@@ -302,8 +301,8 @@ func TestPayPalRefundResolvesCaptureFromPersistedQueryID(t *testing.T) {
func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T) {
config := paypalTestConfig("https://paypal.invalid")
config["capture_id"] = "CONFIG-CAPTURE"
_, err := (&paypalAdapter{}).Refund(context.Background(), &biz.PaymentRefundRequest{
Provider: biz.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD",
_, err := (&paypalAdapter{}).Refund(context.Background(), &system.PaymentRefundRequest{
Provider: system.PaymentPayPal, TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD",
}, config)
if err == nil {
t.Fatal("Refund() accepted a static config capture ID")
@@ -311,7 +310,7 @@ func TestPayPalRefundRejectsStaticCaptureIDWithoutDurableIdentity(t *testing.T)
}
func TestPayPalRefundResultBindsRefundIdentity(t *testing.T) {
req := &biz.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"}
req := &system.PaymentRefundRequest{TradeNo: "LOCAL-1", RefundNo: "REFUND-1", Amount: 123, Currency: "USD"}
base := func() *paypal.PaymentCaptureRefund {
return &paypal.PaymentCaptureRefund{
Id: "PAYPAL-REFUND-1", InvoiceId: "REFUND-1", Status: "PENDING",
@@ -368,7 +367,7 @@ func TestPayPalCallbackUsesGoPayWebhookVerification(t *testing.T) {
}`)
config := paypalTestConfig(server.URL)
config["webhook_id"] = "HOOK-1"
result, err := (&paypalAdapter{}).Callback(context.Background(), &biz.PaymentCallback{
result, err := (&paypalAdapter{}).Callback(context.Background(), &system.PaymentCallback{
Body: body,
Headers: map[string]string{
"Paypal-Auth-Algo": "SHA256withRSA",
+10 -11
View File
@@ -5,10 +5,9 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"strings"
"kra/internal/biz"
"github.com/go-pay/gopay"
gopayQQ "github.com/go-pay/gopay/qq"
)
@@ -61,7 +60,7 @@ func verifyQQResponse(apiKey, signType string, response any) error {
return nil
}
func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *qqAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("QQ 支付下单请求为空")
}
@@ -104,7 +103,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[s
if status == "pending" && strings.EqualFold(rsp.ResultCode, "SUCCESS") && strings.TrimSpace(rsp.TransactionId) != "" {
status = "success"
}
return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: firstNonEmptyQQ(rsp.OutTradeNo, req.TradeNo), ProviderTradeNo: strings.TrimSpace(rsp.TransactionId), Payload: mustJSON(rsp)}, nil
}
bm.Set("trade_type", tradeType)
mergeGoPayExtras(bm, req.Extra, "nonce_str", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", "trade_type", "mch_id", "sign_type")
@@ -118,7 +117,7 @@ func (a *qqAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[s
if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
return nil, fmt.Errorf("QQ 支付下单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
}
result := &biz.PaymentResult{Provider: biz.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}
result := &system.PaymentResult{Provider: system.PaymentQQ, Status: "created", TradeNo: req.TradeNo, Payload: mustJSON(rsp)}
return result, nil
}
@@ -153,7 +152,7 @@ func firstNonEmptyQQ(values ...string) string {
return ""
}
func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("QQ 支付查单缺少 out_trade_no")
@@ -178,7 +177,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any)
if rsp == nil || !qqOK(rsp.ReturnCode, rsp.ResultCode) {
return nil, fmt.Errorf("QQ 支付查单失败: %s", first(rspMap(rsp), "return_msg", "ret_msg", "err_code_des"))
}
result := &biz.PaymentResult{Provider: biz.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)}
result := &system.PaymentResult{Provider: system.PaymentQQ, Status: normalizeQQState(rsp.TradeState), TradeNo: rsp.OutTradeNo, ProviderTradeNo: rsp.TransactionId, Payload: mustJSON(rsp)}
if result.TradeNo == "" {
result.TradeNo = tradeNo
} else if strings.TrimSpace(result.TradeNo) != tradeNo {
@@ -205,7 +204,7 @@ func (a *qqAdapter) Query(ctx context.Context, tradeNo string, c map[string]any)
return result, nil
}
func (a *qqAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *qqAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("QQ 支付退款请求为空")
}
@@ -269,7 +268,7 @@ func (a *qqAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c
if returnedTradeNo == "" {
returnedTradeNo = tradeNo
}
return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentQQ, Status: "pending", TradeNo: returnedTradeNo, ProviderTradeNo: strings.TrimSpace(rsp.RefundId), Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
}
func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundResponse) error {
@@ -290,7 +289,7 @@ func validateQQRefundIdentity(tradeNo, refundNo string, rsp *gopayQQ.RefundRespo
return nil
}
func (a *qqAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *qqAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil {
return nil, errors.New("QQ 支付回调为空")
}
@@ -319,5 +318,5 @@ func (a *qqAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c
status = "success"
}
payload, _ := json.Marshal(values)
return &biz.PaymentResult{Provider: biz.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentQQ, Status: status, TradeNo: values["out_trade_no"], ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
}
+3 -3
View File
@@ -6,12 +6,12 @@ import (
"encoding/json"
"fmt"
"io"
"kra/internal/biz/system"
"net"
"net/http"
"net/http/httptest"
"testing"
"kra/internal/biz"
"kra/pkg/paymentkit"
"github.com/go-pay/gopay"
@@ -84,14 +84,14 @@ func TestQQCreateKeepsClientPaymentDataOutOfDurableIdentities(t *testing.T) {
_, _ = w.Write(paymentutil.XMLEncode(encoded))
}))
result, err := (&qqAdapter{}).Create(context.Background(), &biz.PaymentRequest{
result, err := (&qqAdapter{}).Create(context.Background(), &system.PaymentRequest{
TradeNo: "MERCHANT-QQ-1", Subject: "subject", Amount: 100,
Currency: "CNY", ClientIP: "127.0.0.1", NotifyURL: "https://merchant.example/qq/notify",
}, map[string]any{"mch_id": "QQ-MERCHANT", "api_key": "qq-api-key"})
if err != nil {
t.Fatal(err)
}
if result.Provider != biz.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" {
if result.Provider != system.PaymentQQ || result.Status != "created" || result.TradeNo != "MERCHANT-QQ-1" {
t.Fatalf("create result = %+v", result)
}
if result.QueryID != "" || result.ProviderTradeNo != "" {
@@ -2,11 +2,10 @@ package payment
import (
"context"
"kra/internal/biz/system"
"strings"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay"
gopayQQ "github.com/go-pay/gopay/qq"
"github.com/go-pay/gopay/saobei"
@@ -16,12 +15,12 @@ import (
func TestWechatV2RefundRejectsInvalidRequestBeforeClient(t *testing.T) {
for _, tc := range []struct {
name string
req *biz.PaymentRefundRequest
req *system.PaymentRefundRequest
want string
}{
{name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"},
{name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"},
{name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
{name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1, TotalAmount: 1}, want: "out_trade_no"},
{name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1, TotalAmount: 1}, want: "out_refund_no"},
{name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := (&wechatV2Adapter{}).Refund(context.Background(), tc.req, nil)
@@ -61,12 +60,12 @@ func TestWechatV2RefundIdentityValidation(t *testing.T) {
func TestQQRefundRejectsInvalidRequestBeforeClient(t *testing.T) {
for _, tc := range []struct {
name string
req *biz.PaymentRefundRequest
req *system.PaymentRefundRequest
want string
}{
{name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"},
{name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"},
{name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"},
{name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "out_trade_no"},
{name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "out_refund_no"},
{name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := (&qqAdapter{}).Refund(context.Background(), tc.req, nil)
@@ -106,12 +105,12 @@ func TestQQRefundIdentityValidation(t *testing.T) {
func TestSaobeiRefundRejectsInvalidRequestBeforeClient(t *testing.T) {
for _, tc := range []struct {
name string
req *biz.PaymentRefundRequest
req *system.PaymentRefundRequest
want string
}{
{name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"},
{name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"},
{name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"},
{name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1", Amount: 1}, want: "商户订单号"},
{name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " ", Amount: 1}, want: "退款单号"},
{name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 0}, want: "金额"},
} {
t.Run(tc.name, func(t *testing.T) {
clientCreated := false
@@ -134,7 +133,7 @@ func TestSaobeiRefundRejectsMismatchedRefundNumber(t *testing.T) {
adapter := newSaobeiTestAdapter(&saobeiClientStub{refund: func(context.Context, gopay.BodyMap) (*saobei.RefundRsp, error) {
return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "OTHER", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "25"}, nil
}})
_, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil)
_, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 25}, nil)
if err == nil || !strings.Contains(err.Error(), "terminal_trace") {
t.Fatalf("Refund() error = %v", err)
}
@@ -144,7 +143,7 @@ func TestSaobeiBarcodeCreateTreatsPayingAsPending(t *testing.T) {
adapter := newSaobeiTestAdapter(&saobeiClientStub{barcodePay: func(context.Context, gopay.BodyMap) (*saobei.BarcodePayRsp, error) {
return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: saobei.ResultCodePaying}, OutTradeNo: "SAOBEI-ORDER-1"}, nil
}})
result, err := adapter.Create(context.Background(), &biz.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil)
result, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-1", Amount: 10, Extra: map[string]any{"method": "barcode", "auth_no": "AUTH-1"}}, nil)
if err != nil {
t.Fatalf("Create() error = %v", err)
}
@@ -1,11 +1,10 @@
package payment
import (
"kra/internal/biz/system"
"strings"
"testing"
"kra/internal/biz"
gopayAlipay "github.com/go-pay/gopay/alipay"
"github.com/go-pay/gopay/douyin"
)
@@ -29,7 +28,7 @@ func TestWechatV2ResponseRequiresBusinessSuccessCode(t *testing.T) {
func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) {
amount := &douyin.AmountInfo{Total: 100, PayerTotal: 0, Currency: "CNY", PayerCurrency: "CNY"}
result := &biz.PaymentResult{}
result := &system.PaymentResult{}
if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"payer_total":0,"currency":"CNY","payer_currency":"CNY"}}`)); err != nil {
t.Fatal(err)
}
@@ -37,7 +36,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) {
t.Fatalf("explicit zero payer_total was not preserved: %+v", result)
}
result = &biz.PaymentResult{}
result = &system.PaymentResult{}
if err := populateDouyinPaymentAmount(result, amount, []byte(`{"amount":{"total":100,"currency":"CNY"}}`)); err != nil {
t.Fatal(err)
}
@@ -45,7 +44,7 @@ func TestPopulateDouyinPaymentAmountUsesSignedFieldPresence(t *testing.T) {
t.Fatalf("missing payer_total was treated as authoritative: %+v", result)
}
if err := populateDouyinPaymentAmount(&biz.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil {
if err := populateDouyinPaymentAmount(&system.PaymentResult{}, &douyin.AmountInfo{Total: 100, PayerTotal: 101}, []byte(`{"amount":{"total":100,"payer_total":101}}`)); err == nil {
t.Fatal("accepted payer_total greater than total")
}
}
@@ -74,7 +73,7 @@ func TestValidateDouyinMerchantIdentity(t *testing.T) {
}
func TestAlipayRefundResultValidatesIdentityAndAmount(t *testing.T) {
req := &biz.PaymentRefundRequest{
req := &system.PaymentRefundRequest{
TradeNo: "T-ALIPAY", ProviderTradeNo: "P-ALIPAY", RefundNo: "R-ALIPAY",
Amount: 123, Currency: "CNY",
}
+2 -2
View File
@@ -4,14 +4,14 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"net/url"
"strings"
"kra/internal/biz"
"kra/pkg/paymentkit"
)
func callbackFields(callback *biz.PaymentCallback) map[string]string {
func callbackFields(callback *system.PaymentCallback) map[string]string {
fields := map[string]string{}
if callback == nil {
return fields
+2 -3
View File
@@ -1,13 +1,12 @@
package payment
import (
"kra/internal/biz/system"
"testing"
"kra/internal/biz"
)
func TestCallbackFieldsAcceptsCaseInsensitiveContentType(t *testing.T) {
callback := &biz.PaymentCallback{
callback := &system.PaymentCallback{
Headers: map[string]string{"content-type": "Application/X-WWW-Form-Urlencoded"},
Body: []byte("out_trade_no=ORDER-1&sign=signature"),
}
+9 -10
View File
@@ -4,11 +4,10 @@ import (
"context"
"errors"
"fmt"
"kra/internal/biz/system"
"strings"
"time"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/saobei"
)
@@ -35,7 +34,7 @@ func (a *saobeiAdapter) client(c map[string]any) (saobeiClient, error) {
return saobei.NewClient(text(c, "inst_no"), text(c, "key"), text(c, "merchant_no"), text(c, "terminal_id"), text(c, "access_token"), !strings.EqualFold(text(c, "environment"), "sandbox"))
}
func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *saobeiAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("扫呗支付下单请求为空")
}
@@ -70,7 +69,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
status = "pending"
}
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
}
rsp, err := client.MiniPay(ctx, bm)
if err != nil {
@@ -82,7 +81,7 @@ func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c m
if strings.TrimSpace(rsp.OutTradeNo) == "" {
return nil, errors.New("扫呗支付下单响应缺少 out_trade_no")
}
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
}
func saobeiCreateMethod(extra, config map[string]any) (string, error) {
@@ -101,7 +100,7 @@ func saobeiCreateMethod(extra, config map[string]any) (string, error) {
}
}
func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("扫呗支付查单缺少 out_trade_no")
@@ -141,7 +140,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]
if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
status = "pending"
}
result := &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))}
result := &system.PaymentResult{Provider: system.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))}
if rsp.TotalFee != "" {
result.Amount, err = parseIntegerAmount(rsp.TotalFee)
if err != nil {
@@ -162,7 +161,7 @@ func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]
return result, err
}
func (a *saobeiAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *saobeiAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("扫呗支付退款请求为空")
}
@@ -215,9 +214,9 @@ func (a *saobeiAdapter) Refund(ctx context.Context, req *biz.PaymentRefundReques
if refundAmount != req.Amount {
return nil, errors.New("扫呗支付退款响应金额不匹配")
}
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
return &system.PaymentResult{Provider: system.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
}
func (a *saobeiAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) {
func (a *saobeiAdapter) Callback(_ context.Context, _ *system.PaymentCallback, _ map[string]any) (*system.PaymentResult, error) {
return nil, errors.New("扫呗支付回调没有可复用的 GoPay 验签器,请改用主动查单")
}
+9 -10
View File
@@ -2,10 +2,9 @@ package payment
import (
"context"
"kra/internal/biz/system"
"testing"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/saobei"
)
@@ -66,7 +65,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) {
adapter := newSaobeiTestAdapter(&saobeiClientStub{miniPay: func(context.Context, gopay.BodyMap) (*saobei.MiniPayRsp, error) {
return &saobei.MiniPayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-1"}, nil
}})
result, err := adapter.Create(context.Background(), &biz.PaymentRequest{
result, err := adapter.Create(context.Background(), &system.PaymentRequest{
TradeNo: "LOCAL-1",
Amount: 100,
Extra: map[string]any{"sub_appid": "APP-1", "open_id": "OPEN-1"},
@@ -85,7 +84,7 @@ func TestSaobeiCreateKeepsQueryAndProviderTradeIDsSeparate(t *testing.T) {
request = bm
return &saobei.BarcodePayRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, OutTradeNo: "SAOBEI-ORDER-2", ChannelTradeNo: "CHANNEL-2"}, nil
}})
result, err := adapter.Create(context.Background(), &biz.PaymentRequest{
result, err := adapter.Create(context.Background(), &system.PaymentRequest{
TradeNo: "LOCAL-2",
Amount: 200,
Extra: map[string]any{"method": "barcode", "auth_code": "AUTH-2"},
@@ -117,7 +116,7 @@ func TestSaobeiCreateRejectsMissingPlatformOrderID(t *testing.T) {
if method != "" {
extra["method"] = method
}
if _, err := adapter.Create(context.Background(), &biz.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil {
if _, err := adapter.Create(context.Background(), &system.PaymentRequest{TradeNo: "LOCAL-MISSING", Amount: 1, Extra: extra}, nil); err == nil {
t.Fatal("Create() accepted response without out_trade_no")
}
})
@@ -131,7 +130,7 @@ func TestSaobeiCreateRejectsUnknownMethodBeforeClient(t *testing.T) {
return &saobeiClientStub{}, nil
}}
_, err := adapter.Create(context.Background(), &biz.PaymentRequest{
_, err := adapter.Create(context.Background(), &system.PaymentRequest{
TradeNo: "LOCAL-UNKNOWN-METHOD", Amount: 1, Extra: map[string]any{"method": "unsupported"},
}, nil)
if err == nil {
@@ -230,7 +229,7 @@ func TestSaobeiRefundPrefersPersistedQueryID(t *testing.T) {
return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-1", OutTradeNo: "SAOBEI-ORDER-1", OutRefundNo: "SAOBEI-REFUND-1", RefundFee: "50"}, nil
}})
result, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{
result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{
TradeNo: "LOCAL-1", QueryID: "SAOBEI-ORDER-1", ProviderTradeNo: "CHANNEL-1",
RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "cny",
}, map[string]any{"refund_extra": map[string]any{"out_refund_no": "MUST-NOT-BE-SENT"}})
@@ -258,7 +257,7 @@ func TestSaobeiRefundFallsBackToMerchantTradeNo(t *testing.T) {
return &saobei.RefundRsp{RspBase: saobei.RspBase{ReturnCode: "01", ResultCode: "01"}, TerminalTrace: "LOCAL-REFUND-2", OutRefundNo: "SAOBEI-REFUND-2", RefundFee: "25"}, nil
}})
_, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{
_, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{
TradeNo: "LOCAL-2", RefundNo: "LOCAL-REFUND-2", Amount: 25, Currency: "CNY",
}, nil)
if err != nil {
@@ -282,7 +281,7 @@ func TestSaobeiRefundTreatsPayingAsPending(t *testing.T) {
}, nil
}})
result, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{
result, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{
TradeNo: "LOCAL-3", RefundNo: "LOCAL-REFUND-3", Amount: 25, Currency: "CNY",
}, nil)
if err != nil {
@@ -303,7 +302,7 @@ func TestSaobeiRefundRejectsMismatchedAmount(t *testing.T) {
}, nil
}})
if _, err := adapter.Refund(context.Background(), &biz.PaymentRefundRequest{
if _, err := adapter.Refund(context.Background(), &system.PaymentRefundRequest{
TradeNo: "LOCAL-1", RefundNo: "LOCAL-REFUND-1", Amount: 50, Currency: "CNY",
}, nil); err == nil {
t.Fatal("Refund() accepted a mismatched refund amount")
+9 -9
View File
@@ -10,11 +10,11 @@ import (
"errors"
"fmt"
"io"
"kra/internal/biz/system"
"net/http"
"strings"
"time"
"kra/internal/biz"
"kra/pkg/paymentkit"
)
@@ -37,7 +37,7 @@ func newVendorAdapter(provider string, profile vendorProfile) Adapter {
return &vendorPaymentAdapter{provider: provider, profile: profile}
}
func (a *vendorPaymentAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *vendorPaymentAdapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("配置驱动支付下单请求为空")
}
@@ -45,16 +45,16 @@ func (a *vendorPaymentAdapter) Create(ctx context.Context, req *biz.PaymentReque
mergeMap(payload, req.Extra, "merchant_id", "app_id", "trade_no", "subject", "amount", "currency", "notify_url", "client_ip", "timestamp", "nonce")
return a.call(ctx, "create_url", payload, req.TradeNo, c)
}
func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *vendorPaymentAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
return a.call(ctx, "query_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": tradeNo, "timestamp": time.Now().Unix(), "nonce": nonce()}, tradeNo, c)
}
func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *vendorPaymentAdapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("支付退款请求为空")
}
return a.call(ctx, "refund_url", map[string]any{"merchant_id": text(c, "merchant_id"), "app_id": text(c, "app_id"), "trade_no": req.TradeNo, "refund_no": req.RefundNo, "amount": req.Amount, "total_amount": req.TotalAmount, "currency": req.Currency, "timestamp": time.Now().Unix(), "nonce": nonce()}, req.TradeNo, c)
}
func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("配置驱动支付回调为空")
}
@@ -98,9 +98,9 @@ func (a *vendorPaymentAdapter) Callback(_ context.Context, callback *biz.Payment
status = "success"
}
payload, _ := json.Marshal(fields)
return &biz.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil
return &system.PaymentResult{Provider: a.provider, Status: status, TradeNo: tradeNo, ProviderTradeNo: providerTradeNo, Payload: payload}, nil
}
func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, payload map[string]any, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
endpoint := text(c, endpointKey)
if endpoint == "" {
return nil, fmt.Errorf("%s 未配置 %s", a.provider, endpointKey)
@@ -131,7 +131,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay
if response.StatusCode >= 300 {
return nil, fmt.Errorf("%s HTTP %d", a.provider, response.StatusCode)
}
result := &biz.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)}
result := &system.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)}
if endpointKey != "query_url" {
return result, nil
}
@@ -167,7 +167,7 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay
return result, nil
}
func populateVendorBreakdown(result *biz.PaymentResult, object map[string]any, c map[string]any) error {
func populateVendorBreakdown(result *system.PaymentResult, object map[string]any, c map[string]any) error {
if result == nil || result.Status != "success" {
return nil
}
+17 -18
View File
@@ -8,13 +8,12 @@ import (
"encoding/json"
"errors"
"fmt"
"kra/internal/biz/system"
"net/http"
"net/url"
"strings"
"time"
"kra/internal/biz"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/xhttp"
gopayWechat "github.com/go-pay/gopay/wechat"
@@ -49,7 +48,7 @@ func wechatV2Sign(values map[string]string, key, signType string) string {
return gopayWechat.GetReleaseSign(key, wechatV2SignType(signType), body)
}
func (a *wechatV2Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV2Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("微信支付 v2 下单请求为空")
}
@@ -99,15 +98,15 @@ func (a *wechatV2Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c
if err = verifyWechatV2Response(resultValues, client.ApiKey, values["sign_type"]); err != nil {
return nil, err
}
return &biz.PaymentResult{
Provider: biz.PaymentWechatV2,
return &system.PaymentResult{
Provider: system.PaymentWechatV2,
Status: "created",
TradeNo: req.TradeNo,
Payload: wechatV2Payload(resultValues),
}, nil
}
func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *biz.PaymentRequest, c map[string]any, values map[string]string) (*biz.PaymentResult, error) {
func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWechat.Client, req *system.PaymentRequest, c map[string]any, values map[string]string) (*system.PaymentResult, error) {
authCode := firstAny(req.Extra, "auth_code", "authcode", "barcode", "pay_code", "payment_code")
if authCode == "" {
authCode = firstAny(c, "auth_code", "authcode", "barcode", "pay_code", "payment_code")
@@ -146,14 +145,14 @@ func (a *wechatV2Adapter) createMicropay(ctx context.Context, client *gopayWecha
return nil, errors.New("微信支付 v2 付款码支付响应的 out_trade_no 不匹配")
}
if strings.EqualFold(resultValues["err_code"], "USERPAYING") {
return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil
return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: "pending", TradeNo: req.TradeNo, Payload: wechatV2Payload(resultValues)}, nil
}
providerTradeNo := strings.TrimSpace(resultValues["transaction_id"])
if providerTradeNo == "" {
return nil, errors.New("微信支付 v2 付款码支付响应缺少 transaction_id")
}
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo,
result := &system.PaymentResult{
Provider: system.PaymentWechatV2, Status: "success", TradeNo: req.TradeNo,
ProviderTradeNo: providerTradeNo, Payload: wechatV2Payload(resultValues),
}
amount := strings.TrimSpace(resultValues["total_fee"])
@@ -202,7 +201,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) {
}
}
func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("微信支付 v2 查单缺少 out_trade_no")
@@ -227,8 +226,8 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin
if err = verifyWechatV2Response(values, client.ApiKey, body.GetString("sign_type")); err != nil {
return nil, err
}
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV2,
result := &system.PaymentResult{
Provider: system.PaymentWechatV2,
Status: normalizePaymentStatus(values["trade_state"], "pending"),
TradeNo: first(values, "out_trade_no"),
Payload: wechatV2Payload(values),
@@ -259,7 +258,7 @@ func (a *wechatV2Adapter) Query(ctx context.Context, tradeNo string, c map[strin
return result, nil
}
func (a *wechatV2Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV2Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("微信支付 v2 退款请求为空")
}
@@ -306,8 +305,8 @@ func (a *wechatV2Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ
if err = validateWechatV2RefundIdentity(tradeNo, refundNo, rsp); err != nil {
return nil, err
}
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV2,
result := &system.PaymentResult{
Provider: system.PaymentWechatV2,
Status: "created",
TradeNo: tradeNo,
ProviderTradeNo: strings.TrimSpace(rsp.RefundId),
@@ -352,7 +351,7 @@ func validateWechatV2RefundIdentity(tradeNo, refundNo string, rsp *gopayWechat.R
return nil
}
func (a *wechatV2Adapter) Callback(_ context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV2Adapter) Callback(_ context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil || len(callback.Body) == 0 {
return nil, errors.New("微信支付 v2 回调为空")
}
@@ -376,7 +375,7 @@ func (a *wechatV2Adapter) Callback(_ context.Context, callback *biz.PaymentCallb
status = "success"
}
payload, _ := json.Marshal(values)
return &biz.PaymentResult{Provider: biz.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
return &system.PaymentResult{Provider: system.PaymentWechatV2, Status: status, TradeNo: first(values, "out_trade_no"), ProviderTradeNo: values["transaction_id"], Payload: payload}, nil
}
func newWechatV2Client(c map[string]any, endpoint string) (*gopayWechat.Client, error) {
@@ -467,7 +466,7 @@ func wechatV2Payload(values map[string]string) []byte {
return []byte(gopayWechat.GenerateXml(toGoPayBodyMap(values)))
}
func populateWechatV2Breakdown(result *biz.PaymentResult, values map[string]string) error {
func populateWechatV2Breakdown(result *system.PaymentResult, values map[string]string) error {
if result == nil || result.Status != "success" {
return nil
}
+19 -20
View File
@@ -8,20 +8,19 @@ import (
"encoding/pem"
"errors"
"fmt"
"kra/internal/biz/system"
"net/http"
"net/url"
"strings"
"time"
"kra/internal/biz"
"github.com/go-pay/gopay"
gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
)
type wechatV3Adapter struct{}
func (a *wechatV3Adapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV3Adapter) Create(ctx context.Context, req *system.PaymentRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("微信支付 v3 下单请求为空")
}
@@ -124,7 +123,7 @@ func normalizeWechatV3TradeType(value string) string {
return strings.NewReplacer(".", "", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
}
func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz.PaymentResult, error) {
func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*system.PaymentResult, error) {
if rsp == nil {
return nil, errors.New("微信支付 v3 付款码响应为空")
}
@@ -140,8 +139,8 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz.
} else if returnedTradeNo != tradeNo {
return nil, errors.New("微信支付 v3 付款码响应的商户订单号不匹配")
}
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV3,
result := &system.PaymentResult{
Provider: system.PaymentWechatV3,
Status: normalizePaymentStatus(order.TradeState, "pending"),
TradeNo: tradeNo,
ProviderTradeNo: strings.TrimSpace(order.TransactionId),
@@ -180,7 +179,7 @@ func wechatV3CodePayResult(tradeNo string, rsp *gopayWechatV3.CodePayRsp) (*biz.
return result, nil
}
func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*system.PaymentResult, error) {
tradeNo = strings.TrimSpace(tradeNo)
if tradeNo == "" {
return nil, errors.New("微信支付 v3 查单缺少 out_trade_no")
@@ -205,8 +204,8 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin
order := rsp.Response
payload := wechatV3ResponsePayload(rsp.SignInfo, order)
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV3,
result := &system.PaymentResult{
Provider: system.PaymentWechatV3,
Status: normalizePaymentStatus(order.TradeState, "pending"),
TradeNo: order.OutTradeNo,
ProviderTradeNo: order.TransactionId,
@@ -237,7 +236,7 @@ func (a *wechatV3Adapter) Query(ctx context.Context, tradeNo string, c map[strin
return result, nil
}
func (a *wechatV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV3Adapter) Refund(ctx context.Context, req *system.PaymentRefundRequest, c map[string]any) (*system.PaymentResult, error) {
if req == nil {
return nil, errors.New("微信支付 v3 退款请求为空")
}
@@ -280,8 +279,8 @@ func (a *wechatV3Adapter) Refund(ctx context.Context, req *biz.PaymentRefundRequ
if err != nil {
return nil, err
}
result := &biz.PaymentResult{
Provider: biz.PaymentWechatV3,
result := &system.PaymentResult{
Provider: system.PaymentWechatV3,
Status: normalizeRefundState(refund.Status),
TradeNo: tradeNo,
ProviderTradeNo: refundID,
@@ -337,7 +336,7 @@ func validateWechatV3RefundIdentity(refund *gopayWechatV3.RefundOrderResponse, t
return refundID, nil
}
func (a *wechatV3Adapter) Callback(ctx context.Context, callback *biz.PaymentCallback, c map[string]any) (*biz.PaymentResult, error) {
func (a *wechatV3Adapter) Callback(ctx context.Context, callback *system.PaymentCallback, c map[string]any) (*system.PaymentResult, error) {
if callback == nil {
return nil, errors.New("微信支付 v3 回调为空")
}
@@ -396,8 +395,8 @@ func (a *wechatV3Adapter) Callback(ctx context.Context, callback *biz.PaymentCal
return nil, errors.New("微信支付 v3 回调 appid 不匹配")
}
plain, _ := json.Marshal(pay)
return &biz.PaymentResult{
Provider: biz.PaymentWechatV3,
return &system.PaymentResult{
Provider: system.PaymentWechatV3,
Status: status,
TradeNo: pay.OutTradeNo,
ProviderTradeNo: pay.TransactionId,
@@ -491,22 +490,22 @@ func wechatV3PlatformSerial(certificate string) (string, error) {
return strings.ToUpper(cert.SerialNumber.Text(16)), nil
}
func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*biz.PaymentResult, error) {
func wechatV3CreateResult(tradeNo string, code int, signInfo *gopayWechatV3.SignInfo, response any, errResponse gopayWechatV3.ErrResponse, rawError string) (*system.PaymentResult, error) {
if code != gopayWechatV3.Success {
return nil, wechatV3APIError("下单", code, errResponse, rawError)
}
if response == nil {
return nil, errors.New("微信支付 v3 下单响应缺少预支付信息")
}
return &biz.PaymentResult{
Provider: biz.PaymentWechatV3,
return &system.PaymentResult{
Provider: system.PaymentWechatV3,
Status: "created",
TradeNo: tradeNo,
Payload: wechatV3ResponsePayload(signInfo, response),
}, nil
}
func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*biz.PaymentResult, error) {
func wechatV3PrepayCreateResult(client *gopayWechatV3.ClientV3, appID, tradeType, tradeNo string, rsp *gopayWechatV3.PrepayRsp) (*system.PaymentResult, error) {
if rsp == nil {
return nil, errors.New("微信支付 v3 下单响应为空")
}
@@ -553,7 +552,7 @@ func wechatV3ResponsePayload(signInfo *gopayWechatV3.SignInfo, response any) []b
return payload
}
func populateWechatV3Breakdown(result *biz.PaymentResult, object map[string]any) error {
func populateWechatV3Breakdown(result *system.PaymentResult, object map[string]any) error {
if result == nil || result.Status != "success" {
return nil
}
@@ -4,13 +4,12 @@ import (
"context"
"encoding/json"
"io"
"kra/internal/biz/system"
"net/http"
"net/http/httptest"
"strings"
"testing"
"kra/internal/biz"
gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
)
@@ -52,8 +51,8 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) {
}))
defer server.Close()
result, err := (&wechatV3Adapter{}).Create(context.Background(), &biz.PaymentRequest{
Provider: biz.PaymentWechatV3,
result, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{
Provider: system.PaymentWechatV3,
TradeNo: "T-WECHAT-CODEPAY",
Subject: "subject",
Amount: 123,
@@ -86,7 +85,7 @@ func TestWechatV3CodePayUsesGoPaySDK(t *testing.T) {
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 != "" {
if result.Provider != system.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 {
@@ -109,7 +108,7 @@ func TestWechatV3CodePayAliasesRequireAuthCode(t *testing.T) {
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{
_, err := (&wechatV3Adapter{}).Create(context.Background(), &system.PaymentRequest{
TradeNo: "T-WECHAT-CODEPAY", Subject: "subject", Amount: 1, Currency: "CNY",
Extra: map[string]any{"method": method},
}, config)
@@ -2,11 +2,10 @@ package payment
import (
"context"
"kra/internal/biz/system"
"strings"
"testing"
"kra/internal/biz"
gopayDouyin "github.com/go-pay/gopay/douyin"
gopayWechatV3 "github.com/go-pay/gopay/wechat/v3"
)
@@ -14,12 +13,12 @@ import (
func TestWechatV3RefundRejectsBlankIdentityBeforeClient(t *testing.T) {
for _, tc := range []struct {
name string
req *biz.PaymentRefundRequest
req *system.PaymentRefundRequest
want string
}{
{name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"},
{name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"},
{name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
{name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"},
{name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"},
{name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := (&wechatV3Adapter{}).Refund(context.Background(), tc.req, nil)
@@ -68,12 +67,12 @@ func TestWechatV3RefundIdentityValidation(t *testing.T) {
func TestDouyinRefundRejectsBlankIdentityBeforeClient(t *testing.T) {
for _, tc := range []struct {
name string
req *biz.PaymentRefundRequest
req *system.PaymentRefundRequest
want string
}{
{name: "blank trade number", req: &biz.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"},
{name: "blank refund number", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"},
{name: "invalid amount", req: &biz.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
{name: "blank trade number", req: &system.PaymentRefundRequest{TradeNo: " ", RefundNo: "R1"}, want: "out_trade_no"},
{name: "blank refund number", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: " "}, want: "out_refund_no"},
{name: "invalid amount", req: &system.PaymentRefundRequest{TradeNo: "T1", RefundNo: "R1", Amount: 2, TotalAmount: 1}, want: "金额"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := (&douyinAdapter{}).Refund(context.Background(), tc.req, nil)