package payment import ( "context" "strings" "testing" "kra/app/system/internal/biz" "github.com/go-pay/gopay" gopayQQ "github.com/go-pay/gopay/qq" "github.com/go-pay/gopay/saobei" gopayWechat "github.com/go-pay/gopay/wechat" ) func TestWechatV2RefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string req *biz.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: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&wechatV2Adapter{}).Refund(context.Background(), tc.req, nil) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Fatalf("Refund() error = %v, want %q", err, tc.want) } }) } } func TestWechatV2RefundIdentityValidation(t *testing.T) { valid := func() *gopayWechat.RefundResponse { return &gopayWechat.RefundResponse{OutTradeNo: "T1", OutRefundNo: "R1", RefundId: "WX-R1"} } if err := validateWechatV2RefundIdentity("T1", "R1", valid()); err != nil { t.Fatalf("valid refund response: %v", err) } for _, tc := range []struct { name string edit func(*gopayWechat.RefundResponse) }{ {name: "trade mismatch", edit: func(r *gopayWechat.RefundResponse) { r.OutTradeNo = "OTHER" }}, {name: "refund missing", edit: func(r *gopayWechat.RefundResponse) { r.OutRefundNo = "" }}, {name: "refund mismatch", edit: func(r *gopayWechat.RefundResponse) { r.OutRefundNo = "OTHER" }}, {name: "provider refund missing", edit: func(r *gopayWechat.RefundResponse) { r.RefundId = "" }}, } { t.Run(tc.name, func(t *testing.T) { rsp := valid() tc.edit(rsp) if err := validateWechatV2RefundIdentity("T1", "R1", rsp); err == nil { t.Fatal("accepted invalid refund identity") } }) } } func TestQQRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string req *biz.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: "金额"}, } { t.Run(tc.name, func(t *testing.T) { _, err := (&qqAdapter{}).Refund(context.Background(), tc.req, nil) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Fatalf("Refund() error = %v, want %q", err, tc.want) } }) } } func TestQQRefundIdentityValidation(t *testing.T) { valid := func() *gopayQQ.RefundResponse { return &gopayQQ.RefundResponse{OutTradeNo: "T1", OutRefundNo: "R1", RefundId: "QQ-R1"} } if err := validateQQRefundIdentity("T1", "R1", valid()); err != nil { t.Fatalf("valid refund response: %v", err) } for _, tc := range []struct { name string edit func(*gopayQQ.RefundResponse) }{ {name: "trade mismatch", edit: func(r *gopayQQ.RefundResponse) { r.OutTradeNo = "OTHER" }}, {name: "refund missing", edit: func(r *gopayQQ.RefundResponse) { r.OutRefundNo = "" }}, {name: "refund mismatch", edit: func(r *gopayQQ.RefundResponse) { r.OutRefundNo = "OTHER" }}, {name: "provider refund missing", edit: func(r *gopayQQ.RefundResponse) { r.RefundId = "" }}, } { t.Run(tc.name, func(t *testing.T) { rsp := valid() tc.edit(rsp) if err := validateQQRefundIdentity("T1", "R1", rsp); err == nil { t.Fatal("accepted invalid refund identity") } }) } } func TestSaobeiRefundRejectsInvalidRequestBeforeClient(t *testing.T) { for _, tc := range []struct { name string req *biz.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: "金额"}, } { t.Run(tc.name, func(t *testing.T) { clientCreated := false adapter := &saobeiAdapter{newClient: func(map[string]any) (saobeiClient, error) { clientCreated = true return &saobeiClientStub{}, nil }} _, err := adapter.Refund(context.Background(), tc.req, nil) if err == nil || !strings.Contains(err.Error(), tc.want) { t.Fatalf("Refund() error = %v, want %q", err, tc.want) } if clientCreated { t.Fatal("Refund() initialized client before validating request") } }) } } 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) if err == nil || !strings.Contains(err.Error(), "terminal_trace") { t.Fatalf("Refund() error = %v", err) } } 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) if err != nil { t.Fatalf("Create() error = %v", err) } if result.Status != "pending" { t.Fatalf("Create() status = %q, want pending", result.Status) } }