Files
junhong_cmp_fiber/internal/service/agent_recharge/service.go
2026-08-18 16:15:46 +08:00

692 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package agent_recharge 提供代理预充值的业务逻辑服务
// 包含充值订单创建、线下确认、支付回调处理、列表查询等功能
package agent_recharge
import (
"context"
"fmt"
"math/rand"
"strings"
"time"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"gorm.io/gorm"
agentrechargeapp "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/config"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
)
// OperationPasswordServiceInterface 全局操作密码服务接口
type OperationPasswordServiceInterface interface {
Verify(ctx context.Context, inputPassword string) error
}
// WechatConfigServiceInterface 支付配置服务接口
type WechatConfigServiceInterface interface {
GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
}
// Service 代理预充值业务服务
// 负责代理钱包充值订单的创建、线下确认、回调处理等业务逻辑
type Service struct {
db *gorm.DB
agentRechargeStore *postgres.AgentRechargeStore
agentWalletStore *postgres.AgentWalletStore
agentWalletPosting *walletapp.PostingService
offlineCreation *agentrechargeapp.OfflineCreationService
shopStore *postgres.ShopStore
wechatConfigService WechatConfigServiceInterface
operationPasswordService OperationPasswordServiceInterface
redis *redis.Client
logger *zap.Logger
rechargeAudit agentrechargeapp.RechargeAuditWriter
}
// New 创建代理预充值服务实例
func New(
db *gorm.DB,
agentRechargeStore *postgres.AgentRechargeStore,
agentWalletStore *postgres.AgentWalletStore,
shopStore *postgres.ShopStore,
wechatConfigService WechatConfigServiceInterface,
operationPasswordService OperationPasswordServiceInterface,
rdb *redis.Client,
logger *zap.Logger,
) *Service {
return &Service{
db: db,
agentRechargeStore: agentRechargeStore,
agentWalletStore: agentWalletStore,
shopStore: shopStore,
wechatConfigService: wechatConfigService,
operationPasswordService: operationPasswordService,
redis: rdb,
logger: logger,
}
}
// SetAgentWalletPostingService 注入代理主钱包统一入账用例。
func (s *Service) SetAgentWalletPostingService(service *walletapp.PostingService) {
s.agentWalletPosting = service
}
// SetOfflineCreationService 注入员工线下代充值审批申请用例。
func (s *Service) SetOfflineCreationService(service *agentrechargeapp.OfflineCreationService) {
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) {
userID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
if req.PaymentMethod != constants.RechargeMethodOffline {
return nil, errors.New(errors.CodeInvalidStatus, "在线充值必须通过代理在线创建用例处理")
}
if userType != constants.UserTypePlatform && userType != constants.UserTypeSuperAdmin {
return nil, errors.New(errors.CodeForbidden, "线下充值仅平台管理员可操作")
}
if req.ShopID == nil || *req.ShopID == 0 {
return nil, errors.New(errors.CodeInvalidParam, "线下充值必须指定目标店铺")
}
if len(req.PaymentVoucherKey) == 0 {
return nil, errors.New(errors.CodeInvalidParam, "线下充值必须上传支付凭证")
}
if req.Amount < constants.AgentRechargeMinAmount || req.Amount > constants.AgentRechargeMaxAmount {
return nil, errors.New(errors.CodeInvalidParam, "充值金额超出允许范围")
}
rechargeNo := s.generateRechargeNo()
return s.createOffline(ctx, req, userID, userType, rechargeNo)
}
func (s *Service) createOffline(
ctx context.Context,
req *dto.CreateAgentRechargeRequest,
userID uint,
userType int,
rechargeNo string,
) (*dto.AgentRechargeResponse, error) {
if s.offlineCreation == nil {
return nil, errors.New(errors.CodeServiceUnavailable, "员工线下代充值审批能力未配置")
}
result, err := s.offlineCreation.Execute(ctx, agentrechargeapp.CreateOfflineCommand{
SubmitterAccountID: userID,
SubmitterUserType: userType,
ShopID: *req.ShopID,
RechargeNo: rechargeNo,
Amount: req.Amount,
PaymentVoucherKeys: req.PaymentVoucherKey,
Remark: req.Remark,
})
if err != nil {
return nil, err
}
resp := toResponse(result.Record, result.ShopName)
resp.SubmitterName = result.SubmitterName
resp.ApprovalProvider = constants.IntegrationProviderWeCom
resp.ApprovalStatus = &result.ApprovalStatus
resp.ApprovalStatusName = constants.GetApprovalStatusName(result.ApprovalStatus)
s.logger.Info("创建员工线下代充值审批申请成功",
zap.Uint("recharge_id", result.Record.ID),
zap.String("recharge_no", result.Record.RechargeNo),
zap.Uint("approval_instance_id", *result.Record.ApprovalInstanceID),
zap.Int64("amount", result.Record.Amount),
zap.Uint("shop_id", result.Record.ShopID),
zap.Uint("submitter_id", result.Record.UserID),
)
return resp, nil
}
// OfflinePay 线下充值确认
// POST /api/admin/agent-recharges/:id/offline-pay
func (s *Service) OfflinePay(ctx context.Context, id uint, req *dto.AgentOfflinePayRequest) (*dto.AgentRechargeResponse, error) {
if !legacyOfflineRechargePayEnabled() {
return nil, errors.New(errors.CodeInvalidStatus, "线下充值人工确认入口已停用,请查看企业微信审批状态")
}
userID := middleware.GetUserIDFromContext(ctx)
userType := middleware.GetUserTypeFromContext(ctx)
// 仅平台账号可操作
if userType != constants.UserTypePlatform && userType != constants.UserTypeSuperAdmin {
return nil, errors.New(errors.CodeForbidden, "仅平台管理员可确认线下充值")
}
// 验证全局操作密码
if err := s.operationPasswordService.Verify(ctx, req.OperationPassword); err != nil {
return nil, err
}
record, err := s.agentRechargeStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeNotFound, "充值记录不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败")
}
if record.PaymentMethod != constants.RechargeMethodOffline {
return nil, errors.New(errors.CodeInvalidParam, "该订单非线下充值,不支持此操作")
}
if record.ApprovalInstanceID != nil {
return nil, errors.New(errors.CodeInvalidStatus, "该线下充值申请由企业微信审批决定,不能人工确认")
}
if record.Status != constants.RechargeStatusPending {
return nil, errors.New(errors.CodeInvalidParam, "该订单状态不允许确认支付")
}
now := time.Now()
err = s.db.Transaction(func(tx *gorm.DB) error {
// 条件更新充值记录状态
result := tx.Model(&model.AgentRechargeRecord{}).
Where("id = ? AND status = ?", record.ID, constants.RechargeStatusPending).
Updates(map[string]interface{}{
"status": constants.RechargeStatusCompleted,
"paid_at": now,
"completed_at": now,
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新充值记录状态失败")
}
if result.RowsAffected == 0 {
return errors.New(errors.CodeInvalidParam, "充值记录状态已变更")
}
if s.agentWalletPosting == nil {
return errors.New(errors.CodeInternalError, "代理主钱包入账能力未配置")
}
requestID := ""
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
requestID = *value
}
_, 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,
})
if err != nil {
return err
}
return s.appendCreditedAudit(ctx, tx, record, nil, constants.RechargeStatusCompleted, "线下充值确认已入账")
})
if err != nil {
return nil, err
}
shop, _ := s.shopStore.GetByID(ctx, record.ShopID)
shopName := ""
if shop != nil {
shopName = shop.ShopName
}
// 更新本地对象以反映最新状态
record.Status = constants.RechargeStatusCompleted
record.PaidAt = &now
record.CompletedAt = &now
resp := toResponse(record, shopName)
resp.SubmitterName = s.loadSubmitterNameBestEffort(ctx, record.UserID)
approvalSummaries, err := s.loadApprovalSummaries(ctx, []*model.AgentRechargeRecord{record})
if err != nil {
return nil, err
}
applyApprovalSummary(resp, approvalSummaries, record.ApprovalInstanceID)
return resp, nil
}
func legacyOfflineRechargePayEnabled() bool {
cfg := config.Get()
return cfg == nil || cfg.Approval.LegacyOfflineRechargePayEnabled
}
// HandlePaymentCallback 处理支付回调
// 幂等处理status != 待支付则直接返回成功
func (s *Service) HandlePaymentCallback(ctx context.Context, rechargeNo string, paymentMethod string, paymentTransactionID string, paidAmount int64) error {
paymentTransactionID = strings.TrimSpace(paymentTransactionID)
record, err := s.agentRechargeStore.GetByRechargeNo(ctx, rechargeNo)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "充值订单不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值订单失败")
}
if err := validateAgentRechargePayment(record, paymentMethod, paymentTransactionID, paidAmount); err != nil {
return err
}
// 已完成订单仅在第三方交易号一致时按同一回调幂等成功。
if record.Status != constants.RechargeStatusPending {
if record.Status == constants.RechargeStatusCompleted && record.PaymentTransactionID != nil && *record.PaymentTransactionID == strings.TrimSpace(paymentTransactionID) {
s.logger.Info("代理充值支付回调幂等命中", zap.String("recharge_no", rechargeNo), zap.Int("status", record.Status))
return nil
}
return errors.New(errors.CodeInvalidStatus, "充值订单状态不允许确认支付")
}
now := time.Now()
err = s.db.Transaction(func(tx *gorm.DB) error {
// 条件更新WHERE status = 1
result := tx.Model(&model.AgentRechargeRecord{}).
Where("id = ? AND status = ? AND payment_method <> ? AND amount = ?", record.ID, constants.RechargeStatusPending, constants.RechargeMethodOffline, paidAmount).
Updates(map[string]interface{}{
"status": constants.RechargeStatusCompleted,
"payment_transaction_id": paymentTransactionID,
"paid_at": now,
"completed_at": now,
})
if result.Error != nil {
return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新充值记录状态失败")
}
if result.RowsAffected == 0 {
var current model.AgentRechargeRecord
if err := tx.WithContext(ctx).Unscoped().
Where("id = ?", record.ID).First(&current).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "复核充值记录终态失败")
}
if current.Status == constants.RechargeStatusCompleted && current.PaymentTransactionID != nil && *current.PaymentTransactionID == paymentTransactionID {
return nil
}
return errors.New(errors.CodeConflict, "充值订单已被其他请求处理")
}
if s.agentWalletPosting == nil {
return errors.New(errors.CodeInternalError, "代理主钱包入账能力未配置")
}
requestID := ""
if value := middleware.GetRequestIDFromContext(ctx); value != nil {
requestID = *value
}
_, 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,
})
if err != nil {
return err
}
return s.appendCreditedAudit(ctx, tx, record, nil, constants.RechargeStatusCompleted, "代理充值支付回调已入账")
})
if err != nil {
return err
}
s.logger.Info("代理充值支付回调处理成功",
zap.String("recharge_no", rechargeNo),
zap.Int64("amount", record.Amount),
zap.Uint("shop_id", record.ShopID),
)
return nil
}
func validateAgentRechargePayment(record *model.AgentRechargeRecord, paymentMethod, paymentTransactionID string, paidAmount int64) error {
if record == nil || strings.TrimSpace(paymentTransactionID) == "" || paidAmount <= 0 || paidAmount != record.Amount {
return errors.New(errors.CodeInvalidParam, "代理充值支付回调金额或交易号不匹配")
}
if record.PaymentMethod == constants.RechargeMethodOffline || record.PaymentConfigID == nil || record.PaymentChannel == nil {
return errors.New(errors.CodeInvalidStatus, "线下充值订单不能通过支付回调确认")
}
channel := strings.TrimSpace(*record.PaymentChannel)
switch strings.TrimSpace(paymentMethod) {
case model.PaymentMethodWechat:
if record.PaymentMethod != constants.RechargeMethodWechat || (channel != model.ProviderTypeWechat && channel != model.ProviderTypeWechatV2) {
return errors.New(errors.CodeInvalidParam, "代理充值支付渠道不匹配")
}
case model.ProviderTypeFuiou:
if record.PaymentMethod != constants.RechargeMethodWechat || channel != model.ProviderTypeFuiou {
return errors.New(errors.CodeInvalidParam, "代理充值支付渠道不匹配")
}
default:
return errors.New(errors.CodeInvalidParam, "代理充值支付渠道不受支持")
}
return nil
}
// Reject 驳回代理充值订单
// 仅 status=1待支付的订单可驳回驳回后状态变为 6已驳回为终态
func (s *Service) Reject(ctx context.Context, id uint, rejectionReason string) error {
record, err := s.agentRechargeStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeNotFound, "充值记录不存在")
}
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败")
}
if record.Status != constants.RechargeStatusPending {
return errors.New(errors.CodeInvalidStatus, "仅待支付订单可驳回")
}
if record.ApprovalInstanceID != nil {
return errors.New(errors.CodeInvalidStatus, "该线下充值申请由企业微信审批决定,不能人工驳回")
}
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, "仅待支付订单可驳回")
}
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("代理充值订单驳回成功",
zap.Uint("record_id", id),
zap.String("rejection_reason", rejectionReason),
)
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},
})
}
// TriggerApproval 为历史线下代理充值主动补发企业微信审批。
func (s *Service) TriggerApproval(ctx context.Context, id uint) (*dto.AgentRechargeResponse, error) {
if s.offlineCreation == nil {
return nil, errors.New(errors.CodeServiceUnavailable, "员工线下代充值审批能力未配置")
}
record, err := s.agentRechargeStore.GetByID(ctx, id)
if err != nil {
return nil, errors.New(errors.CodeNotFound, "充值记录不存在")
}
result, err := s.offlineCreation.TriggerHistorical(ctx, record.ID)
if err != nil {
return nil, err
}
resp := toResponse(result.Record, result.ShopName)
resp.SubmitterName = result.SubmitterName
resp.ApprovalProvider = constants.IntegrationProviderWeCom
resp.ApprovalStatus = &result.ApprovalStatus
resp.ApprovalStatusName = constants.GetApprovalStatusName(result.ApprovalStatus)
return resp, nil
}
// GetByID 根据ID查询充值订单详情
// GET /api/admin/agent-recharges/:id
func (s *Service) GetByID(ctx context.Context, id uint) (*dto.AgentRechargeResponse, error) {
record, err := s.agentRechargeStore.GetByID(ctx, id)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录失败")
}
shop, _ := s.shopStore.GetByID(ctx, record.ShopID)
shopName := ""
if shop != nil {
shopName = shop.ShopName
}
resp := toResponse(record, shopName)
resp.SubmitterName = s.loadSubmitterNameBestEffort(ctx, record.UserID)
approvalSummaries, err := s.loadApprovalSummaries(ctx, []*model.AgentRechargeRecord{record})
if err != nil {
return nil, err
}
applyApprovalSummary(resp, approvalSummaries, record.ApprovalInstanceID)
return resp, nil
}
// List 分页查询充值订单列表
// GET /api/admin/agent-recharges
func (s *Service) List(ctx context.Context, req *dto.AgentRechargeListRequest) ([]*dto.AgentRechargeResponse, int64, error) {
page := req.Page
pageSize := req.PageSize
if page == 0 {
page = 1
}
if pageSize == 0 {
pageSize = constants.DefaultPageSize
}
query := middleware.ApplyStrictShopFilter(ctx, s.db.WithContext(ctx).Model(&model.AgentRechargeRecord{}))
if req.ShopID != nil {
query = query.Where("shop_id = ?", *req.ShopID)
}
if req.Status != nil {
query = query.Where("status = ?", *req.Status)
}
switch req.RechargeSource {
case constants.AgentRechargeSourcePlatformOffline:
query = query.Where("payment_method NOT IN ?", []string{constants.RechargeMethodWechat, constants.RechargeMethodAlipay})
case constants.AgentRechargeSourceAgentOnline:
query = query.Where("payment_method IN ?", []string{constants.RechargeMethodWechat, constants.RechargeMethodAlipay})
}
if req.StartDate != "" {
query = query.Where("created_at >= ?", req.StartDate+" 00:00:00")
}
if req.EndDate != "" {
query = query.Where("created_at <= ?", req.EndDate+" 23:59:59")
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录总数失败")
}
var records []*model.AgentRechargeRecord
offset := (page - 1) * pageSize
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询充值记录列表失败")
}
// 批量查询店铺名称
shopIDs := make([]uint, 0, len(records))
for _, r := range records {
shopIDs = append(shopIDs, r.ShopID)
}
shopMap := make(map[uint]string)
if len(shopIDs) > 0 {
shops, err := s.shopStore.GetByIDs(ctx, shopIDs)
if err == nil {
for _, sh := range shops {
shopMap[sh.ID] = sh.ShopName
}
}
}
submitterIDs := make([]uint, 0, len(records))
for _, record := range records {
if record.UserID > 0 {
submitterIDs = append(submitterIDs, record.UserID)
}
}
submitterNames, err := s.loadSubmitterNames(ctx, submitterIDs)
if err != nil {
return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询充值提交人失败")
}
approvalSummaries, err := s.loadApprovalSummaries(ctx, records)
if err != nil {
return nil, 0, err
}
list := make([]*dto.AgentRechargeResponse, 0, len(records))
for _, r := range records {
item := toResponse(r, shopMap[r.ShopID])
item.SubmitterName = submitterNames[r.UserID]
applyApprovalSummary(item, approvalSummaries, r.ApprovalInstanceID)
list = append(list, item)
}
return list, total, nil
}
// generateRechargeNo 生成代理充值订单号
// 格式: ARCH + 14位时间戳 + 6位随机数
func (s *Service) generateRechargeNo() string {
timestamp := time.Now().Format("20060102150405")
randomNum := rand.Intn(1000000)
return fmt.Sprintf("%s%s%06d", constants.AgentRechargeOrderPrefix, timestamp, randomNum)
}
// toResponse 将模型转换为响应 DTO
func toResponse(record *model.AgentRechargeRecord, shopName string) *dto.AgentRechargeResponse {
rechargeSource, rechargeSourceName := constants.GetAgentRechargeSource(record.PaymentMethod)
resp := &dto.AgentRechargeResponse{
ID: record.ID,
RechargeNo: record.RechargeNo,
ShopID: record.ShopID,
ShopName: shopName,
AgentWalletID: record.AgentWalletID,
Amount: record.Amount,
PaymentMethod: record.PaymentMethod,
RechargeSource: rechargeSource,
RechargeSourceName: rechargeSourceName,
PaymentVoucherKey: []string(record.PaymentVoucherKey),
Remark: record.Remark,
Status: record.Status,
StatusName: constants.GetRechargeStatusName(record.Status),
RejectionReason: record.RejectionReason,
SubmitterID: record.UserID,
ApprovalInstanceID: record.ApprovalInstanceID,
CreatedAt: record.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: record.UpdatedAt.Format("2006-01-02 15:04:05"),
}
if record.PaymentChannel != nil {
resp.PaymentChannel = *record.PaymentChannel
}
if record.PaymentConfigID != nil {
resp.PaymentConfigID = record.PaymentConfigID
}
if record.PaymentTransactionID != nil {
resp.PaymentTransactionID = *record.PaymentTransactionID
}
if record.PaidAt != nil {
t := record.PaidAt.Format("2006-01-02 15:04:05")
resp.PaidAt = &t
}
if record.CompletedAt != nil {
t := record.CompletedAt.Format("2006-01-02 15:04:05")
resp.CompletedAt = &t
}
return resp
}
type approvalSummary struct {
Provider string
Status int
}
func (s *Service) loadApprovalSummaries(
ctx context.Context,
records []*model.AgentRechargeRecord,
) (map[uint]approvalSummary, error) {
ids := make([]uint, 0, len(records))
seen := make(map[uint]struct{}, len(records))
for _, record := range records {
if record == nil || record.ApprovalInstanceID == nil || *record.ApprovalInstanceID == 0 {
continue
}
id := *record.ApprovalInstanceID
if _, exists := seen[id]; exists {
continue
}
seen[id] = struct{}{}
ids = append(ids, id)
}
summaries := make(map[uint]approvalSummary, len(ids))
if len(ids) == 0 {
return summaries, nil
}
var instances []model.ApprovalInstance
if err := s.db.WithContext(ctx).Select("id", "provider", "status").Where("id IN ?", ids).Find(&instances).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询充值审批状态失败")
}
for _, instance := range instances {
summaries[instance.ID] = approvalSummary{Provider: instance.Provider, Status: instance.Status}
}
return summaries, nil
}
func applyApprovalSummary(response *dto.AgentRechargeResponse, summaries map[uint]approvalSummary, instanceID *uint) {
if response == nil || instanceID == nil {
return
}
summary, exists := summaries[*instanceID]
if !exists {
return
}
status := summary.Status
response.ApprovalProvider = summary.Provider
response.ApprovalStatus = &status
response.ApprovalStatusName = constants.GetApprovalStatusName(status)
}
func (s *Service) loadSubmitterNames(ctx context.Context, ids []uint) (map[uint]string, error) {
accounts, err := postgres.NewAccountStore(s.db, nil).GetDisplayAccountsByIDs(ctx, ids)
if err != nil {
return nil, err
}
names := make(map[uint]string, len(accounts))
for _, account := range accounts {
names[account.ID] = account.Username
}
return names, nil
}
func (s *Service) loadSubmitterNameBestEffort(ctx context.Context, id uint) string {
names, err := s.loadSubmitterNames(ctx, []uint{id})
if err != nil {
s.logger.Warn("查询充值提交人失败", zap.Uint("submitter_id", id), zap.Error(err))
return ""
}
return names[id]
}