feat(order): 重构退款全流程并新增申请退款中状态
- 订单状态新增「6 申请退款中」,用户申请退款原子流转并落待审核退款流水 - 退款失败回退订单至已支付并恢复被取消的权益发放任务 - 新增虚拟支付退款查单兜底任务,反查超时订单并自动订正 - 会员包来源额度不叠加,扣减/回退/回收口径统一为只取生效一份 - 退款回收流水按吊销前后有效剩余差值记账,避免无效回收记录 - 同步更新 README 与 Stitch 设计文档
This commit is contained in:
@@ -36,6 +36,7 @@ func (d *BenefitRevokeTaskDao) RecoverStale(ctx context.Context, before int64) e
|
||||
}
|
||||
|
||||
// RefundSettleWithRevokeTask 原子写入退款订单终态、取消未执行发放并创建回收任务。
|
||||
// 前置状态:2=已支付(正常退款)、3=已取消但已付款(竞态兜底)、6=申请退款中(用户已提交退款申请)。
|
||||
func (d *BenefitRevokeTaskDao) RefundSettleWithRevokeTask(ctx context.Context, orderNo string, refundAmount, refundTime int64) (bool, error) {
|
||||
settled := false
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
@@ -46,7 +47,7 @@ func (d *BenefitRevokeTaskDao) RefundSettleWithRevokeTask(ctx context.Context, o
|
||||
if order.OrderStatus == 5 {
|
||||
return nil
|
||||
}
|
||||
if order.OrderStatus != 2 && order.OrderStatus != 3 {
|
||||
if order.OrderStatus != 2 && order.OrderStatus != 3 && order.OrderStatus != 6 {
|
||||
return nil
|
||||
}
|
||||
if err := tx.Model(&mysqlmodel.Order{}).Where("id = ?", order.ID).Updates(map[string]any{"order_status": 5, "refund_time": refundTime, "refund_amount": refundAmount}).Error; err != nil {
|
||||
@@ -55,7 +56,8 @@ func (d *BenefitRevokeTaskDao) RefundSettleWithRevokeTask(ctx context.Context, o
|
||||
if err := tx.Model(&mysqlmodel.BenefitGrantTask{}).Where("order_no = ? AND status IN ?", orderNo, []int8{0, 2}).Update("status", 4).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.OrderStatus == 2 {
|
||||
// 2 / 6 表示权益已按订单发放过,需创建回收任务;3 从未发放,跳过避免误扣其它来源的会员天数/额度。
|
||||
if order.OrderStatus == 2 || order.OrderStatus == 6 {
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&mysqlmodel.BenefitRevokeTask{OrderNo: orderNo, UserID: order.UserID, MaxRetry: 10}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+104
-5
@@ -2,10 +2,12 @@ package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// OrderDao 订单数据访问
|
||||
@@ -91,15 +93,34 @@ func (d *OrderDao) OrderListVirtualUnconfirmed(ctx context.Context, since, until
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// OrderListVirtualRefunding 查询停留在「申请退款中」的虚拟支付订单(updated_at 早于水位),
|
||||
// 供退款结果查单兜底任务反查微信侧真实退款结果:推送丢失/未识别时避免订单永久卡单。
|
||||
// 状态字面量:2=虚拟支付 6=申请退款中(与 service 层 PayChannelVirtual/OrderStatusRefunding 一致)。
|
||||
func (d *OrderDao) OrderListVirtualRefunding(ctx context.Context, beforeUpdatedAt int64, limit int) ([]mysqlmodel.Order, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderListVirtualRefunding")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Order
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("pay_channel = ? AND order_status = ? AND updated_at < ?", 2, 6, beforeUpdatedAt).
|
||||
Order("id ASC").Limit(limit).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// OrderListPaidMissingBenefitTasks 查询存在未建权益任务的已支付订单,修复支付回调与任务落库之间的崩溃窗口。
|
||||
// 除「任务不存在」外,也把「任务被取消(status=4)」视为待修复:订单曾进入退款流程时任务被取消,
|
||||
// 退款失败回退为已支付后需要重新发放,避免「已付款却无权益」(已退款订单状态为 5,不会被本查询命中)。
|
||||
func (d *OrderDao) OrderListPaidMissingBenefitTasks(ctx context.Context, limit int) ([]mysqlmodel.Order, error) {
|
||||
var list []mysqlmodel.Order
|
||||
err := d.db.WithContext(ctx).Where("order_status = ?", 2).
|
||||
Where(`(member_days > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=1)) OR
|
||||
(cos_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=2)) OR
|
||||
(ai_recommend_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=3)) OR
|
||||
(closet_share_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=4)) OR
|
||||
(outfit_share_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=5))`).
|
||||
Where(`(member_days > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=1 AND t.status<>4)) OR
|
||||
(cos_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=2 AND t.status<>4)) OR
|
||||
(ai_recommend_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=3 AND t.status<>4)) OR
|
||||
(closet_share_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=4 AND t.status<>4)) OR
|
||||
(outfit_share_quota > 0 AND NOT EXISTS (SELECT 1 FROM benefit_grant_tasks t WHERE t.order_no=orders.order_no AND t.benefit_type=5 AND t.status<>4))`).
|
||||
Order("id ASC").Limit(limit).Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
@@ -162,6 +183,84 @@ func NewRefundFlowLogDao() *RefundFlowLogDao {
|
||||
return &RefundFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// RefundApplyWithOrderStatus 原子受理退款申请:锁定订单行并校验「2 已支付」,
|
||||
// 将订单流转为「6 申请退款中」,同时新建退款流水或复用上次失败的流水(换新退款单号并重置为待处理)。
|
||||
// allowed=false 表示订单状态不满足(非已支付/已被并发受理)或已有进行中的退款流水,
|
||||
// 此时不做任何写入,调用方应返回「当前状态不允许退款」。
|
||||
// 状态字面量:0=待处理(流水) 2=已支付 3=退款失败(流水) 6=申请退款中(与 service 层常量一致)。
|
||||
func (d *RefundFlowLogDao) RefundApplyWithOrderStatus(ctx context.Context, orderNo, refundNo, reason string, applyTime int64) (*mysqlmodel.RefundFlowLog, bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundApplyWithOrderStatus")
|
||||
defer span.End()
|
||||
|
||||
var flow *mysqlmodel.RefundFlowLog
|
||||
allowed := false
|
||||
// 订单行锁串行化同一订单的并发申请:仅首个请求能把 2 流转为 6,其余请求读到 6 后直接拒绝。
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var order mysqlmodel.Order
|
||||
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("order_no = ?", orderNo).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if order.OrderStatus != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var existing mysqlmodel.RefundFlowLog
|
||||
gerr := tx.Where("order_no = ?", orderNo).First(&existing).Error
|
||||
switch {
|
||||
case gerr != nil && !errors.Is(gerr, gorm.ErrRecordNotFound):
|
||||
return gerr
|
||||
case gerr == nil && existing.Status != 3:
|
||||
// 已有待处理 / 退款中 / 已成功的退款流水:不允许重复申请。
|
||||
return nil
|
||||
case gerr == nil:
|
||||
// 失败重试:复用旧记录,换新退款单号并重置为待处理。
|
||||
if err := tx.Model(&mysqlmodel.RefundFlowLog{}).Where("id = ?", existing.ID).Updates(map[string]any{
|
||||
"refund_no": refundNo,
|
||||
"refund_amount": order.PayAmount,
|
||||
"refund_reason": reason,
|
||||
"status": 0,
|
||||
"apply_time": applyTime,
|
||||
"fail_reason": "",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
existing.RefundNo = refundNo
|
||||
existing.RefundAmount = order.PayAmount
|
||||
existing.RefundReason = reason
|
||||
existing.Status = 0
|
||||
existing.ApplyTime = applyTime
|
||||
existing.FailReason = ""
|
||||
flow = &existing
|
||||
default:
|
||||
created := &mysqlmodel.RefundFlowLog{
|
||||
OrderID: order.ID,
|
||||
OrderNo: order.OrderNo,
|
||||
UserID: order.UserID,
|
||||
RefundNo: refundNo,
|
||||
RefundAmount: order.PayAmount,
|
||||
RefundReason: reason,
|
||||
Status: 0,
|
||||
ApplyTime: applyTime,
|
||||
}
|
||||
if err := tx.Create(created).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
flow = created
|
||||
}
|
||||
|
||||
if err := tx.Model(&mysqlmodel.Order{}).Where("id = ?", order.ID).Update("order_status", 6).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
allowed = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, false, err
|
||||
}
|
||||
return flow, allowed, nil
|
||||
}
|
||||
|
||||
// RefundFlowLogCreate 创建退款流水
|
||||
func (d *RefundFlowLogDao) RefundFlowLogCreate(ctx context.Context, log *mysqlmodel.RefundFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogCreate")
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
)
|
||||
|
||||
// TestRefundApplyWithOrderStatusCreatesFlowAndMarksRefunding 受理成功:订单 2 已支付 -> 6 申请退款中,并新建退款流水。
|
||||
func TestRefundApplyWithOrderStatusCreatesFlowAndMarksRefunding(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &RefundFlowLogDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status", "pay_amount"}).
|
||||
AddRow(8, "ORDER-1", 7, 2, 990))
|
||||
mock.ExpectQuery("SELECT .* FROM `refund_flow_logs`").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
||||
mock.ExpectExec("INSERT INTO `refund_flow_logs`").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectExec("UPDATE `orders` SET").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
flow, allowed, err := dao.RefundApplyWithOrderStatus(context.Background(), "ORDER-1", "RF-NEW", "不想要了", 1234)
|
||||
if err != nil || !allowed {
|
||||
t.Fatalf("allowed=%v err=%v", allowed, err)
|
||||
}
|
||||
if flow == nil || flow.RefundNo != "RF-NEW" || flow.Status != 0 || flow.RefundAmount != 990 || flow.UserID != 7 {
|
||||
t.Fatalf("unexpected flow: %+v", flow)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundApplyWithOrderStatusRejectsNonPaidOrder 订单已处于申请退款中(6)时拒绝受理且不写任何数据。
|
||||
func TestRefundApplyWithOrderStatusRejectsNonPaidOrder(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &RefundFlowLogDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status"}).
|
||||
AddRow(8, "ORDER-1", 7, 6))
|
||||
mock.ExpectCommit()
|
||||
|
||||
flow, allowed, err := dao.RefundApplyWithOrderStatus(context.Background(), "ORDER-1", "RF-NEW", "重复申请", 1234)
|
||||
if err != nil || allowed || flow != nil {
|
||||
t.Fatalf("allowed=%v flow=%v err=%v", allowed, flow, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundApplyWithOrderStatusReusesFailedFlow 上次退款失败:复用旧流水并换新退款单号、重置为待处理。
|
||||
func TestRefundApplyWithOrderStatusReusesFailedFlow(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &RefundFlowLogDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status", "pay_amount"}).
|
||||
AddRow(8, "ORDER-1", 7, 2, 990))
|
||||
mock.ExpectQuery("SELECT .* FROM `refund_flow_logs`").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "refund_no", "status"}).
|
||||
AddRow(5, "ORDER-1", 7, "RF-OLD", 3))
|
||||
mock.ExpectExec("UPDATE `refund_flow_logs` SET").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectExec("UPDATE `orders` SET").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
flow, allowed, err := dao.RefundApplyWithOrderStatus(context.Background(), "ORDER-1", "RF-NEW", "再次申请", 2222)
|
||||
if err != nil || !allowed {
|
||||
t.Fatalf("allowed=%v err=%v", allowed, err)
|
||||
}
|
||||
if flow == nil || flow.ID != 5 || flow.RefundNo != "RF-NEW" || flow.Status != 0 || flow.ApplyTime != 2222 {
|
||||
t.Fatalf("unexpected flow: %+v", flow)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundApplyWithOrderStatusRejectsInProgressFlow 已有待处理/退款中/成功的流水时拒绝重复申请。
|
||||
func TestRefundApplyWithOrderStatusRejectsInProgressFlow(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &RefundFlowLogDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status"}).
|
||||
AddRow(8, "ORDER-1", 7, 2))
|
||||
mock.ExpectQuery("SELECT .* FROM `refund_flow_logs`").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "refund_no", "status"}).
|
||||
AddRow(5, "ORDER-1", "RF-OLD", 0))
|
||||
mock.ExpectCommit()
|
||||
|
||||
flow, allowed, err := dao.RefundApplyWithOrderStatus(context.Background(), "ORDER-1", "RF-NEW", "重复申请", 1234)
|
||||
if err != nil || allowed || flow != nil {
|
||||
t.Fatalf("allowed=%v flow=%v err=%v", allowed, flow, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrderStatusRevertRefundingToPaid 退款失败回退依赖的条件流转:6 -> 2 仅在订单仍为 6 时生效。
|
||||
func TestOrderStatusRevertRefundingToPaid(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
rows int64
|
||||
want bool
|
||||
}{
|
||||
{name: "still_refunding", rows: 1, want: true},
|
||||
{name: "already_settled", rows: 0, want: false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &OrderDao{db: base.db}
|
||||
// gorm 默认把单条写语句包在事务里执行。
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE `orders` SET .* WHERE \\(order_no = \\? AND order_status = \\?\\).*").
|
||||
WillReturnResult(sqlmock.NewResult(0, tc.rows))
|
||||
mock.ExpectCommit()
|
||||
|
||||
ok, err := dao.OrderUpdateStatusByOrderNo(context.Background(), "ORDER-1", 6, 2)
|
||||
if err != nil || ok != tc.want {
|
||||
t.Fatalf("ok=%v err=%v, want %v", ok, err, tc.want)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOrderListVirtualRefunding 退款查单兜底扫描:仅取「虚拟支付 + 申请退款中 + updated_at 早于水位」的订单。
|
||||
func TestOrderListVirtualRefunding(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &OrderDao{db: base.db}
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` WHERE .*pay_channel = \\? AND order_status = \\? AND updated_at < \\?.*").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status", "pay_channel", "updated_at"}).
|
||||
AddRow(8, "ORDER-1", 7, 6, 2, 900))
|
||||
|
||||
orders, err := dao.OrderListVirtualRefunding(context.Background(), 1000, 50)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(orders) != 1 || orders[0].OrderNo != "ORDER-1" || orders[0].OrderStatus != 6 {
|
||||
t.Fatalf("unexpected orders: %+v", orders)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundSettlementAcceptsRefundingOrder 退款成功结算放行状态 6,并按「权益是否已发放」决定是否创建回收任务。
|
||||
func TestRefundSettlementAcceptsRefundingOrder(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
orderStatus int8
|
||||
wantRevoke bool
|
||||
}{
|
||||
{name: "refunding_creates_revoke_task", orderStatus: 6, wantRevoke: true},
|
||||
{name: "cancelled_paid_skips_revoke_task", orderStatus: 3, wantRevoke: false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &BenefitRevokeTaskDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status"}).
|
||||
AddRow(8, "ORDER-1", 7, tc.orderStatus))
|
||||
mock.ExpectExec("UPDATE `orders` SET").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectExec("UPDATE `benefit_grant_tasks` SET").WillReturnResult(sqlmock.NewResult(0, 2))
|
||||
if tc.wantRevoke {
|
||||
mock.ExpectExec("INSERT INTO `benefit_revoke_tasks`").WillReturnResult(sqlmock.NewResult(3, 1))
|
||||
}
|
||||
mock.ExpectCommit()
|
||||
|
||||
settled, err := dao.RefundSettleWithRevokeTask(context.Background(), "ORDER-1", 990, 1234)
|
||||
if err != nil || !settled {
|
||||
t.Fatalf("settled=%v err=%v", settled, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundSettlementSkipsAlreadyRefunded 已退款订单重复回调直接跳过(幂等,不重复回退权益)。
|
||||
func TestRefundSettlementSkipsAlreadyRefunded(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &BenefitRevokeTaskDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status"}).
|
||||
AddRow(8, "ORDER-1", 7, 5))
|
||||
mock.ExpectCommit()
|
||||
|
||||
settled, err := dao.RefundSettleWithRevokeTask(context.Background(), "ORDER-1", 990, 1234)
|
||||
if err != nil || settled {
|
||||
t.Fatalf("settled=%v err=%v", settled, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRefundSettlementRejectsExpiredOrder 未支付流转而来的终态(如已过期 4)不允许结算为已退款。
|
||||
func TestRefundSettlementRejectsExpiredOrder(t *testing.T) {
|
||||
base, mock := newQuotaTestDAO(t)
|
||||
dao := &BenefitRevokeTaskDao{db: base.db}
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `orders` .*FOR UPDATE").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "user_id", "order_status"}).
|
||||
AddRow(8, "ORDER-1", 7, 4))
|
||||
mock.ExpectCommit()
|
||||
|
||||
settled, err := dao.RefundSettleWithRevokeTask(context.Background(), "ORDER-1", 990, 1234)
|
||||
if err != nil || settled {
|
||||
t.Fatalf("settled=%v err=%v", settled, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user