同步更新订单状态,校验退款金额小于等于订单实付金额
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, "订单不存在")
|
return nil, errors.New(errors.CodeNotFound, "订单不存在")
|
||||||
}
|
}
|
||||||
if order.PaymentStatus != model.PaymentStatusPaid {
|
if order.PaymentStatus != model.PaymentStatusPaid {
|
||||||
|
if order.PaymentStatus == model.PaymentStatusRefunded {
|
||||||
|
return nil, errors.New(errors.CodeInvalidStatus, "订单已退款,无法重复申请退款")
|
||||||
|
}
|
||||||
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)
|
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
|
approvedAmount = *req.ApprovedRefundAmount
|
||||||
}
|
}
|
||||||
|
|
||||||
// 条件更新确保幂等性
|
// 事务内同步更新退款状态和订单支付状态,确保审批成功后订单立即变为已退款
|
||||||
result := s.db.WithContext(ctx).
|
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
Model(&model.RefundRequest{}).
|
result := tx.Model(&model.RefundRequest{}).
|
||||||
Where("id = ? AND status = ?", id, model.RefundStatusPending).
|
Where("id = ? AND status = ?", id, model.RefundStatusPending).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"status": model.RefundStatusApproved,
|
"status": model.RefundStatusApproved,
|
||||||
"processor_id": userID,
|
"processor_id": userID,
|
||||||
"processed_at": now,
|
"processed_at": now,
|
||||||
"approved_refund_amount": approvedAmount,
|
"approved_refund_amount": approvedAmount,
|
||||||
"remark": req.Remark,
|
"remark": req.Remark,
|
||||||
"updater": userID,
|
"updater": userID,
|
||||||
"updated_at": now,
|
"updated_at": now,
|
||||||
})
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return errors.Wrap(errors.CodeInternalError, result.Error, "审批退款申请失败")
|
return errors.Wrap(errors.CodeInternalError, result.Error, "审批退款申请失败")
|
||||||
}
|
}
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return errors.New(errors.CodeInvalidStatus, "退款申请状态已变更,请刷新后重试")
|
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, "未授权访问")
|
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()
|
now := time.Now()
|
||||||
updates := map[string]any{
|
updates := map[string]any{
|
||||||
"status": model.RefundStatusPending,
|
"status": model.RefundStatusPending,
|
||||||
@@ -427,7 +472,7 @@ func (s *Service) deductSingleCommission(ctx context.Context, refund *model.Refu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleAssetReset 异步处理资产重置
|
// handleAssetReset 异步处理资产重置
|
||||||
// 包括:退款套餐精准失效、停机、世代重置(generation+1)、钱包重建、订单状态更新
|
// 包括:退款套餐精准失效、停机、世代重置(generation+1)、钱包重建
|
||||||
// 失败仅记录日志,不影响审批结果
|
// 失败仅记录日志,不影响审批结果
|
||||||
func (s *Service) handleAssetReset(ctx context.Context, refundID uint) {
|
func (s *Service) handleAssetReset(ctx context.Context, refundID uint) {
|
||||||
logger := s.logger
|
logger := s.logger
|
||||||
@@ -494,12 +539,7 @@ func (s *Service) handleAssetReset(ctx context.Context, refundID uint) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 更新订单支付状态为已退款
|
// 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. 标记退款单资产已重置
|
|
||||||
if err := s.db.Model(&model.RefundRequest{}).Where("id = ?", refundID).Update("asset_reset", true).Error; err != nil {
|
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))
|
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)
|
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 响应
|
// buildRefundResponse 将退款 Model 转换为 DTO 响应
|
||||||
func buildRefundResponse(r *model.RefundRequest) *dto.RefundResponse {
|
func buildRefundResponse(r *model.RefundRequest) *dto.RefundResponse {
|
||||||
assetType := ""
|
assetType := ""
|
||||||
|
|||||||
Reference in New Issue
Block a user