同步更新订单状态,校验退款金额小于等于订单实付金额
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m16s
This commit is contained in:
@@ -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 := ""
|
||||
|
||||
Reference in New Issue
Block a user