收口审计治理与套餐任务进展
Constraint: 在线热修前必须保存当前迭代分支全部有效代码进展 Confidence: medium Scope-risk: broad Directive: 后续修改需保持审计事件与业务事务边界一致 Tested: git diff --cached --check Not-tested: 未运行全量测试,提交用于切换分支前保存既有工作
This commit is contained in:
@@ -53,6 +53,7 @@ type Service struct {
|
||||
operationPasswordService OperationPasswordServiceInterface
|
||||
redis *redis.Client
|
||||
logger *zap.Logger
|
||||
rechargeAudit agentrechargeapp.RechargeAuditWriter
|
||||
}
|
||||
|
||||
// New 创建代理预充值服务实例
|
||||
@@ -90,6 +91,11 @@ func (s *Service) SetOfflineCreationService(service *agentrechargeapp.OfflineCre
|
||||
s.offlineCreation = service
|
||||
}
|
||||
|
||||
// SetRechargeAudit 注入代理充值统一审计 Writer。
|
||||
func (s *Service) SetRechargeAudit(writer agentrechargeapp.RechargeAuditWriter) {
|
||||
s.rechargeAudit = writer
|
||||
}
|
||||
|
||||
// Create 创建代理充值订单
|
||||
// POST /api/admin/agent-recharges
|
||||
func (s *Service) Create(ctx context.Context, req *dto.CreateAgentRechargeRequest) (*dto.AgentRechargeResponse, error) {
|
||||
@@ -215,33 +221,22 @@ func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOffline
|
||||
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
|
||||
requestID = *value
|
||||
}
|
||||
if _, err := s.agentWalletPosting.PostInTx(ctx, tx, walletapp.PostingCommand{
|
||||
_, err := s.agentWalletPosting.PostInTx(ctx, tx, walletapp.PostingCommand{
|
||||
ShopID: record.ShopID, WalletID: record.AgentWalletID, Amount: record.Amount,
|
||||
ReferenceType: constants.ReferenceTypeTopup, ReferenceID: record.ID,
|
||||
TransactionType: constants.AgentTransactionTypeRecharge, UserID: userID, Creator: userID,
|
||||
Remark: "线下充值确认", RequestID: requestID, CorrelationID: record.RechargeNo,
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.appendCreditedAudit(ctx, tx, record, nil, constants.RechargeStatusCompleted, "线下充值确认已入账")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 异步记录审计日志
|
||||
go s.auditService.LogOperation(ctx, &model.AccountOperationLog{
|
||||
OperatorID: userID,
|
||||
OperatorType: userType,
|
||||
OperationType: "offline_recharge_confirm",
|
||||
OperationDesc: fmt.Sprintf("确认线下充值,充值单号: %s,金额: %d分", record.RechargeNo, record.Amount),
|
||||
RequestID: middleware.GetRequestIDFromContext(ctx),
|
||||
IPAddress: middleware.GetIPFromContext(ctx),
|
||||
UserAgent: middleware.GetUserAgentFromContext(ctx),
|
||||
})
|
||||
|
||||
shop, _ := s.shopStore.GetByID(ctx, record.ShopID)
|
||||
shopName := ""
|
||||
if shop != nil {
|
||||
@@ -325,16 +320,16 @@ func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string,
|
||||
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
|
||||
requestID = *value
|
||||
}
|
||||
if _, err := s.agentWalletPosting.PostInTx(ctx, tx, walletapp.PostingCommand{
|
||||
_, err := s.agentWalletPosting.PostInTx(ctx, tx, walletapp.PostingCommand{
|
||||
ShopID: record.ShopID, WalletID: record.AgentWalletID, Amount: record.Amount,
|
||||
ReferenceType: constants.ReferenceTypeTopup, ReferenceID: record.ID,
|
||||
TransactionType: constants.AgentTransactionTypeRecharge, UserID: record.UserID, Creator: record.UserID,
|
||||
Remark: "在线支付充值", RequestID: requestID, CorrelationID: record.RechargeNo,
|
||||
}); err != nil {
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.appendCreditedAudit(ctx, tx, record, nil, constants.RechargeStatusCompleted, "代理充值支付回调已入账")
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -391,11 +386,30 @@ func (s *Service) Reject(ctx context.Context, id uint, rejectionReason string) e
|
||||
return errors.New(errors.CodeInvalidStatus, "该线下充值申请由企业微信审批决定,不能人工驳回")
|
||||
}
|
||||
|
||||
if err := s.agentRechargeStore.UpdateStatusWithRejection(ctx, id, rejectionReason); err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
if s.rechargeAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "代理充值统一审计接缝未配置")
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
result := tx.Model(&model.AgentRechargeRecord{}).
|
||||
Where("id = ? AND status = ?", record.ID, constants.RechargeStatusPending).
|
||||
Updates(map[string]any{"status": constants.RechargeStatusRejected, "rejection_reason": strings.TrimSpace(rejectionReason)})
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, result.Error, "驳回充值订单失败")
|
||||
}
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New(errors.CodeInvalidStatus, "仅待支付订单可驳回")
|
||||
}
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "驳回充值订单失败")
|
||||
after := *record
|
||||
after.Status = constants.RechargeStatusRejected
|
||||
reason := strings.TrimSpace(rejectionReason)
|
||||
after.RejectionReason = &reason
|
||||
return s.rechargeAudit.WriteAgentRecharge(ctx, tx, agentrechargeapp.RechargeAudit{
|
||||
ActionCode: constants.AuditActionAgentRechargeClosed, Summary: "驳回代理充值申请",
|
||||
Record: &after, BeforeData: map[string]any{"status": record.Status},
|
||||
AfterData: map[string]any{"status": after.Status, "rejection_reason": reason},
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("代理充值订单驳回成功",
|
||||
@@ -405,6 +419,29 @@ func (s *Service) Reject(ctx context.Context, id uint, rejectionReason string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) appendCreditedAudit(ctx context.Context, tx *gorm.DB, record *model.AgentRechargeRecord, payment *model.Payment, status int, summary string) error {
|
||||
if s.rechargeAudit == nil {
|
||||
return errors.New(errors.CodeInvalidStatus, "代理充值统一审计接缝未配置")
|
||||
}
|
||||
var wallet model.AgentWallet
|
||||
if err := tx.WithContext(ctx).First(&wallet, record.AgentWalletID).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值钱包审计快照失败")
|
||||
}
|
||||
var transaction model.AgentWalletTransaction
|
||||
if err := tx.WithContext(ctx).Where("reference_type = ? AND reference_id = ? AND transaction_type = ? AND status = ?",
|
||||
constants.ReferenceTypeTopup, record.ID, constants.AgentTransactionTypeRecharge, constants.TransactionStatusSuccess).
|
||||
First(&transaction).Error; err != nil {
|
||||
return errors.Wrap(errors.CodeDatabaseError, err, "查询代理充值入账流水审计快照失败")
|
||||
}
|
||||
after := *record
|
||||
after.Status = status
|
||||
return s.rechargeAudit.WriteAgentRecharge(ctx, tx, agentrechargeapp.RechargeAudit{
|
||||
ActionCode: constants.AuditActionAgentRechargeCredited, Summary: summary,
|
||||
Record: &after, Payment: payment, Wallet: &wallet, Transaction: &transaction,
|
||||
BeforeData: map[string]any{"status": record.Status}, AfterData: map[string]any{"status": status},
|
||||
})
|
||||
}
|
||||
|
||||
// GetByID 根据ID查询充值订单详情
|
||||
// GET /api/admin/agent-recharges/:id
|
||||
func (s *Service) GetByID(ctx context.Context, id uint) (*dto.AgentRechargeResponse, error) {
|
||||
|
||||
Reference in New Issue
Block a user