package refund import ( "context" "strings" "gorm.io/gorm" "gorm.io/gorm/clause" approvalapp "github.com/break/junhong_cmp_fiber/internal/application/approval" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // Handle 幂等处理退款的渠道无关审批终态。 func (s *Service) Handle(ctx context.Context, event approvalapp.TerminalDecisionEvent) error { if s == nil || s.db == nil || event.BusinessType != constants.ApprovalBusinessTypeRefund || event.BusinessID == 0 || event.InstanceID == 0 { return errors.New(errors.CodeInvalidParam, "退款审批终态参数无效") } switch event.Decision { case constants.ApprovalDecisionApproved: return s.applyApprovedDecision(ctx, event) case constants.ApprovalDecisionRejected, constants.ApprovalDecisionCancelled, constants.ApprovalDecisionDeleted: return s.applyClosedDecision(ctx, event) case constants.ApprovalDecisionRevokedAfterApproved: return nil default: return errors.New(errors.CodeInvalidParam, "不支持的退款审批终态") } } func (s *Service) applyApprovedDecision(ctx context.Context, event approvalapp.TerminalDecisionEvent) error { err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var refund model.RefundRequest if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", event.BusinessID).First(&refund).Error; err != nil { if err == gorm.ErrRecordNotFound { return errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在") } return errors.Wrap(errors.CodeDatabaseError, err, "锁定退款申请失败") } if refund.ApprovalInstanceID == nil || *refund.ApprovalInstanceID != event.InstanceID { return errors.New(errors.CodeConflict, "退款申请关联的审批实例不一致") } if refund.Status != model.RefundStatusPending && refund.Status != model.RefundStatusApproved { return errors.New(errors.CodeInvalidStatus, "退款申请状态不允许审批通过") } var order model.Order if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", refund.OrderID).First(&order).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "锁定退款关联订单失败") } approvedAmount := refund.RequestedRefundAmount if err := validateApprovedRefundAmount(approvedAmount, refund.RequestedRefundAmount, &order); err != nil { return err } if refund.Status == model.RefundStatusPending { result := tx.WithContext(ctx).Model(&model.RefundRequest{}). Where("id = ? AND status = ?", refund.ID, model.RefundStatusPending). Updates(map[string]any{ "status": model.RefundStatusApproved, "processed_at": event.OccurredAt, "approved_refund_amount": approvedAmount, "remark": "企业微信审批通过", "updated_at": event.OccurredAt, }) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "完成退款审批申请失败") } if result.RowsAffected != 1 { return errors.New(errors.CodeConflict, "退款申请状态已变化") } } switch order.PaymentStatus { case model.PaymentStatusPaid: result := tx.WithContext(ctx).Model(&model.Order{}). Where("id = ? AND payment_status = ?", order.ID, model.PaymentStatusPaid). Updates(map[string]any{"payment_status": model.PaymentStatusRefunded, "updated_at": event.OccurredAt}) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新订单退款状态失败") } if result.RowsAffected != 1 { return errors.New(errors.CodeConflict, "订单退款状态已变化") } order.PaymentStatus = model.PaymentStatusRefunded case model.PaymentStatusRefunded: default: return errors.New(errors.CodeInvalidStatus, "订单状态不允许完成退款") } return s.refundWalletPayment(ctx, tx, &refund, &order, approvedAmount, event.SubmitterAccountID) }) if err != nil { return err } return s.ensureApprovedPostProcessing(ctx, event.BusinessID) } func (s *Service) applyClosedDecision(ctx context.Context, event approvalapp.TerminalDecisionEvent) error { reason := map[string]string{ constants.ApprovalDecisionRejected: "企业微信审批已拒绝", constants.ApprovalDecisionCancelled: "企业微信审批已撤销", constants.ApprovalDecisionDeleted: "企业微信审批已删除", }[event.Decision] return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { var refund model.RefundRequest if err := tx.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", event.BusinessID).First(&refund).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "锁定退款申请失败") } if refund.ApprovalInstanceID == nil || *refund.ApprovalInstanceID != event.InstanceID { return errors.New(errors.CodeConflict, "退款申请关联的审批实例不一致") } if refund.Status == model.RefundStatusRejected { return nil } if refund.Status != model.RefundStatusPending { return errors.New(errors.CodeInvalidStatus, "退款申请状态不允许结束审批") } result := tx.WithContext(ctx).Model(&model.RefundRequest{}). Where("id = ? AND status = ?", refund.ID, model.RefundStatusPending). Updates(map[string]any{ "status": model.RefundStatusRejected, "processed_at": event.OccurredAt, "reject_reason": strings.TrimSpace(reason), "updated_at": event.OccurredAt, }) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "结束退款审批申请失败") } if result.RowsAffected != 1 { return errors.New(errors.CodeConflict, "退款申请状态已变化") } return nil }) } func (s *Service) ensureApprovedPostProcessing(ctx context.Context, refundID uint) error { s.deductAllCommission(ctx, refundID) s.handleRefundAssetProcessing(ctx, refundID) var refund model.RefundRequest if err := s.db.WithContext(ctx).Select("commission_deducted", "asset_reset").Where("id = ?", refundID).First(&refund).Error; err != nil { return errors.Wrap(errors.CodeDatabaseError, err, "复核退款后处理状态失败") } if !refund.CommissionDeducted || !refund.AssetReset { return errors.New(errors.CodeServiceUnavailable, "退款后处理尚未全部完成,将自动重试") } return nil } var _ approvalapp.BusinessDecisionHandler = (*Service)(nil)