From a7f2c4480a9e79ef6698f60f5154c792ab4f62f5 Mon Sep 17 00:00:00 2001 From: huang Date: Thu, 23 Apr 2026 15:40:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E7=8A=B6=E6=80=81,=E6=A0=A1=E9=AA=8C=E9=80=80?= =?UTF-8?q?=E6=AC=BE=E9=87=91=E9=A2=9D=E5=B0=8F=E4=BA=8E=E7=AD=89=E4=BA=8E?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=AE=9E=E4=BB=98=E9=87=91=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/service/refund/service.go | 101 ++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 25 deletions(-) diff --git a/internal/service/refund/service.go b/internal/service/refund/service.go index 7ce53fa..4c5aab6 100644 --- a/internal/service/refund/service.go +++ b/internal/service/refund/service.go @@ -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 := ""