feat: 支持从失败状态重新发起退款,添加相关逻辑和测试用例

This commit is contained in:
2025-12-23 13:07:12 +08:00
parent d70a33e4f9
commit 39b541accd
4 changed files with 124 additions and 2 deletions

View File

@@ -512,7 +512,11 @@ func (s *order) AdminOrderDetail(ctx context.Context, tenantID, orderID int64) (
return m, nil
}
// AdminRefundOrder 退款已支付订单(支持强制退款),并立即回收已授予的内容权益
// AdminRefundOrder 发起已支付订单退款(支持强制退款)。
//
// 语义:
// - 该方法只负责将订单从 paid 推进到 refunding并入队异步退款任务
// - 退款入账与权益回收由 job/worker 异步完成(见 ProcessRefundingOrder
func (s *order) AdminRefundOrder(
ctx context.Context,
tenantID, operatorUserID, orderID int64,
@@ -557,7 +561,9 @@ func (s *order) AdminRefundOrder(
out = &orderModel
return nil
}
if orderModel.Status != consts.OrderStatusPaid {
// 允许从 failed 重新发起退款:失败状态表示“上一次异步退款未完成/被标记失败”,可由管理员重试推进到 refunding。
if orderModel.Status != consts.OrderStatusPaid && orderModel.Status != consts.OrderStatusFailed {
return errorx.ErrStatusConflict.WithMsg("订单非已支付状态,无法退款")
}
if orderModel.PaidAt.IsZero() {
@@ -765,6 +771,39 @@ func (s *order) ProcessRefundingOrder(ctx context.Context, params *ProcessRefund
return out, nil
}
// MarkRefundFailed marks an order as failed during async refund processing.
// 仅用于 worker 在判定“不可重试”错误时落终态,避免订单长期停留在 refunding。
func (s *order) MarkRefundFailed(ctx context.Context, tenantID, orderID int64, now time.Time) error {
if tenantID <= 0 || orderID <= 0 {
return errorx.ErrInvalidParameter.WithMsg("tenant_id/order_id must be > 0")
}
if now.IsZero() {
now = time.Now()
}
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var orderModel models.Order
if err := tx.
Clauses(clause.Locking{Strength: "UPDATE"}).
Where("tenant_id = ? AND id = ?", tenantID, orderID).
First(&orderModel).Error; err != nil {
return err
}
// 已退款/已失败都无需变更。
if orderModel.Status == consts.OrderStatusRefunded || orderModel.Status == consts.OrderStatusFailed {
return nil
}
return tx.Table(models.TableNameOrder).
Where("id = ?", orderModel.ID).
Updates(map[string]any{
"status": consts.OrderStatusFailed,
"updated_at": now,
}).Error
})
}
func (s *order) PurchaseContent(ctx context.Context, params *PurchaseContentParams) (*PurchaseContentResult, error) {
if params == nil {
return nil, errorx.ErrInvalidParameter.WithMsg("params is required")