Files
pay/internal/store/refund_test.go
T

106 lines
3.7 KiB
Go

package store
import (
"testing"
"time"
"github.com/wangjia/pay/internal/model"
)
func seedPaid(t *testing.T, os *OrderStore, outNo string, amount int64) {
t.Helper()
if err := os.CreateOrder(&model.OrderV2{
OutTradeNo: outNo, BizSystem: "pangolin", BizCode: "pro_year",
AmountMinor: amount, Currency: "CNY", Status: model.OrderPaidV2,
}); err != nil {
t.Fatal(err)
}
if err := os.CreateAttempt(&model.Attempt{
OutTradeNo: outNo, Channel: "alipay", ProviderRef: outNo,
AmountMinor: amount, Currency: "CNY", Status: model.AttemptPaid,
}); err != nil {
t.Fatal(err)
}
}
func TestRefundStoreLifecycle(t *testing.T) {
db := model.OpenTestDB(t)
os := NewOrderStore(db)
rs := NewRefundStore(db)
seedPaid(t, os, "PAY-1", 19900)
// PaidAttempt 定位原交易
att, err := os.PaidAttempt("PAY-1")
if err != nil || att.Channel != "alipay" || att.ProviderRef != "PAY-1" {
t.Fatalf("PaidAttempt = %+v, %v", att, err)
}
// 建两笔部分退款(processing)
for _, id := range []string{"rf-1", "rf-2"} {
if err := rs.CreateRefund(&model.Refund{
RefundID: id, OutTradeNo: "PAY-1", AttemptProviderRef: "PAY-1",
AmountMinor: 5000, Currency: "CNY", Status: model.RefundProcessing, InitiatedBy: "business",
}); err != nil {
t.Fatal(err)
}
}
// 非失败之和 = 10000(占额度)
if sum, _ := rs.RefundSum("PAY-1", model.RefundRequested, model.RefundProcessing, model.RefundManualPending, model.RefundSucceeded); sum != 10000 {
t.Fatalf("reserved sum = %d want 10000", sum)
}
// rf-1 翻 succeeded(条件正确)
ok, err := rs.MarkRefundStatus("rf-1", model.RefundProcessing, model.RefundSucceeded, "ali-refund-1", time.Now())
if err != nil || !ok {
t.Fatalf("mark rf-1 = %v, %v", ok, err)
}
// 再翻一次(from 已不符)→ no-op
if ok, _ := rs.MarkRefundStatus("rf-1", model.RefundProcessing, model.RefundSucceeded, "x", time.Now()); ok {
t.Fatal("重复翻转应 no-op")
}
r1, _ := rs.GetRefund("rf-1")
if r1.Status != model.RefundSucceeded || r1.ProviderRefundRef != "ali-refund-1" || r1.CompletedAt == nil {
t.Fatalf("rf-1 = %+v", r1)
}
// 部分退:succeeded 之和 5000 < 19900 → partially_refunded
succ, _ := rs.RefundSum("PAY-1", model.RefundSucceeded)
if flipped, _ := os.ApplyRefundToOrder("PAY-1", succ >= 19900); !flipped {
t.Fatal("ApplyRefundToOrder 应翻转")
}
if o, _ := os.GetOrder("PAY-1"); o.Status != model.OrderPartRefundedV2 {
t.Fatalf("order status = %s want partially_refunded", o.Status)
}
// rf-2 也成功 → 总退 10000 仍 < 19900,保持 partially_refunded(非 fully)
_, _ = rs.MarkRefundStatus("rf-2", model.RefundProcessing, model.RefundSucceeded, "ali-refund-2", time.Now())
succ, _ = rs.RefundSum("PAY-1", model.RefundSucceeded)
_, _ = os.ApplyRefundToOrder("PAY-1", succ >= 19900)
if o, _ := os.GetOrder("PAY-1"); o.Status != model.OrderPartRefundedV2 {
t.Fatalf("order status = %s want partially_refunded", o.Status)
}
}
func TestApplyRefundToOrderFully(t *testing.T) {
db := model.OpenTestDB(t)
os := NewOrderStore(db)
seedPaid(t, os, "PAY-2", 10000)
if _, err := os.ApplyRefundToOrder("PAY-2", true); err != nil {
t.Fatal(err)
}
if o, _ := os.GetOrder("PAY-2"); o.Status != model.OrderRefundedV2 {
t.Fatalf("status = %s want refunded", o.Status)
}
}
func TestListManualPending(t *testing.T) {
db := model.OpenTestDB(t)
rs := NewRefundStore(db)
_ = rs.CreateRefund(&model.Refund{RefundID: "rf-m1", OutTradeNo: "C-1", AmountMinor: 1, Currency: "USDT", Status: model.RefundManualPending})
_ = rs.CreateRefund(&model.Refund{RefundID: "rf-p1", OutTradeNo: "C-2", AmountMinor: 1, Currency: "USDT", Status: model.RefundProcessing})
list, err := rs.ListManualPending(50)
if err != nil || len(list) != 1 || list[0].RefundID != "rf-m1" {
t.Fatalf("manual pending = %+v, %v", list, err)
}
}