kra-new/internal/integration/payment/refund_boundary_test.go

155 lines
6.1 KiB
Go

package payment
import (
"context"
"strings"
"testing"
"kra/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)
}
}