同步更新订单状态,校验退款金额小于等于订单实付金额
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s

This commit is contained in:
2026-04-23 15:40:59 +08:00
parent 37e113bbf4
commit a7f2c4480a

View File

@@ -90,8 +90,14 @@ func (s *Service) Create(ctx context.Context, req *dto.CreateRefundRequest) (*dt
return nil, errors.New(errors.CodeNotFound, "订单不存在")
}
if order.PaymentStatus != model.PaymentStatusPaid {
if order.PaymentStatus == model.PaymentStatusRefunded {
return nil, errors.New(errors.CodeInvalidStatus, "订单已退款,无法重复申请退款")
}
return nil, errors.New(errors.CodeInvalidStatus, "仅已支付订单可申请退款")
}
if err := validateRequestedRefundAmountByOrder(req.RequestedRefundAmount, order); err != nil {
return nil, err
}
// 检查是否存在活跃退款申请(待审批、已通过、已退回状态)
existing, err := s.refundStore.FindActiveByOrderID(ctx, req.OrderID)
@@ -202,24 +208,42 @@ func (s *Service) Approve(ctx context.Context, id uint, req *dto.ApproveRefundRe
approvedAmount = *req.ApprovedRefundAmount
}
// 条件更新确保幂等性
result := s.db.WithContext(ctx).
Model(&model.RefundRequest{}).
Where("id = ? AND status = ?", id, model.RefundStatusPending).
Updates(map[string]any{
"status": model.RefundStatusApproved,
"processor_id": userID,
"processed_at": now,
"approved_refund_amount": approvedAmount,
"remark": req.Remark,
"updater": userID,
"updated_at": now,
})
if result.Error != nil {
return errors.Wrap(errors.CodeInternalError, result.Error, "审批退款申请失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInvalidStatus, "退款申请状态已变更,请刷新后重试")
// 事务内同步更新退款状态和订单支付状态,确保审批成功后订单立即变为已退款
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
result := tx.Model(&model.RefundRequest{}).
Where("id = ? AND status = ?", id, model.RefundStatusPending).
Updates(map[string]any{
"status": model.RefundStatusApproved,
"processor_id": userID,
"processed_at": now,
"approved_refund_amount": approvedAmount,
"remark": req.Remark,
"updater": userID,
"updated_at": now,
})
if result.Error != nil {
return errors.Wrap(errors.CodeInternalError, result.Error, "审批退款申请失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInvalidStatus, "退款申请状态已变更,请刷新后重试")
}
orderResult := tx.Model(&model.Order{}).
Where("id = ? AND payment_status = ?", refund.OrderID, model.PaymentStatusPaid).
Updates(map[string]any{
"payment_status": model.PaymentStatusRefunded,
"updated_at": now,
})
if orderResult.Error != nil {
return errors.Wrap(errors.CodeInternalError, orderResult.Error, "更新订单退款状态失败")
}
if orderResult.RowsAffected == 0 {
return errors.New(errors.CodeInvalidStatus, "订单状态已变更,无法执行退款审批")
}
return nil
}); err != nil {
return err
}
// 事务提交成功后,异步执行佣金回扣和资产重置(失败不影响审批结果)
@@ -302,6 +326,27 @@ func (s *Service) Resubmit(ctx context.Context, id uint, req *dto.ResubmitRefund
return errors.New(errors.CodeUnauthorized, "未授权访问")
}
refund, err := s.refundStore.GetByID(ctx, id)
if err != nil {
return errors.New(errors.CodeInvalidStatus, "仅已退回状态可重新提交")
}
if refund.Status != model.RefundStatusReturned {
return errors.New(errors.CodeInvalidStatus, "仅已退回状态可重新提交")
}
requestedRefundAmount := refund.RequestedRefundAmount
if req.RequestedRefundAmount != nil {
requestedRefundAmount = *req.RequestedRefundAmount
}
order, err := s.orderStore.GetByID(ctx, refund.OrderID)
if err != nil {
return errors.New(errors.CodeNotFound, "订单不存在")
}
if err := validateRequestedRefundAmountByOrder(requestedRefundAmount, order); err != nil {
return err
}
now := time.Now()
updates := map[string]any{
"status": model.RefundStatusPending,
@@ -427,7 +472,7 @@ func (s *Service) deductSingleCommission(ctx context.Context, refund *model.Refu
}
// handleAssetReset 异步处理资产重置
// 包括退款套餐精准失效、停机、世代重置generation+1、钱包重建、订单状态更新
// 包括退款套餐精准失效、停机、世代重置generation+1、钱包重建
// 失败仅记录日志,不影响审批结果
func (s *Service) handleAssetReset(ctx context.Context, refundID uint) {
logger := s.logger
@@ -494,12 +539,7 @@ func (s *Service) handleAssetReset(ctx context.Context, refundID uint) {
return
}
// 4. 更新订单支付状态为已退款
if err := s.db.Model(&model.Order{}).Where("id = ?", order.ID).Update("payment_status", model.PaymentStatusRefunded).Error; err != nil {
logger.Error("资产重置:更新订单状态失败", zap.Uint("order_id", order.ID), zap.Error(err))
}
// 5. 标记退款单资产已重置
// 4. 标记退款单资产已重置
if err := s.db.Model(&model.RefundRequest{}).Where("id = ?", refundID).Update("asset_reset", true).Error; err != nil {
logger.Error("资产重置:更新重置标记失败", zap.Uint("refund_id", refundID), zap.Error(err))
}
@@ -652,6 +692,17 @@ func generateRefundNo() string {
return fmt.Sprintf("RF%s%06d", now.Format("20060102150405"), randomNum)
}
// validateRequestedRefundAmountByOrder 校验申请退款金额不得超过订单实收金额
func validateRequestedRefundAmountByOrder(requestedRefundAmount int64, order *model.Order) error {
if order.ActualPaidAmount == nil {
return errors.New(errors.CodeInternalError, "订单实收金额不能为空")
}
if requestedRefundAmount > *order.ActualPaidAmount {
return errors.New(errors.CodeInvalidParam, "申请退款金额不能大于订单实收金额")
}
return nil
}
// buildRefundResponse 将退款 Model 转换为 DTO 响应
func buildRefundResponse(r *model.RefundRequest) *dto.RefundResponse {
assetType := ""