2025-07-13 16:36:20 +08:00
|
|
|
|
package finance
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"net/http"
|
2025-12-12 15:27:15 +08:00
|
|
|
|
"time"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
"tyapi-server/internal/application/finance/dto/commands"
|
|
|
|
|
|
"tyapi-server/internal/application/finance/dto/queries"
|
|
|
|
|
|
"tyapi-server/internal/application/finance/dto/responses"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/config"
|
|
|
|
|
|
finance_entities "tyapi-server/internal/domains/finance/entities"
|
|
|
|
|
|
finance_repositories "tyapi-server/internal/domains/finance/repositories"
|
|
|
|
|
|
finance_services "tyapi-server/internal/domains/finance/services"
|
2025-08-02 02:54:21 +08:00
|
|
|
|
user_repositories "tyapi-server/internal/domains/user/repositories"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/shared/database"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
"tyapi-server/internal/shared/export"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"tyapi-server/internal/shared/interfaces"
|
|
|
|
|
|
"tyapi-server/internal/shared/payment"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
|
|
"github.com/smartwalle/alipay/v3"
|
2025-12-12 15:27:15 +08:00
|
|
|
|
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
2025-07-28 01:46:39 +08:00
|
|
|
|
"go.uber.org/zap"
|
2025-07-13 16:36:20 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// FinanceApplicationServiceImpl 财务应用服务实现
|
|
|
|
|
|
type FinanceApplicationServiceImpl struct {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
aliPayClient *payment.AliPayService
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatPayService *payment.WechatPayService
|
2025-07-28 01:46:39 +08:00
|
|
|
|
walletService finance_services.WalletAggregateService
|
|
|
|
|
|
rechargeRecordService finance_services.RechargeRecordService
|
|
|
|
|
|
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
|
|
|
|
|
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatOrderRepo finance_repositories.WechatOrderRepository
|
|
|
|
|
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository
|
2025-08-02 02:54:21 +08:00
|
|
|
|
userRepo user_repositories.UserRepository
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager *database.TransactionManager
|
2025-09-12 01:15:09 +08:00
|
|
|
|
exportManager *export.ExportManager
|
2025-07-28 01:46:39 +08:00
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
config *config.Config
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewFinanceApplicationService 创建财务应用服务
|
|
|
|
|
|
func NewFinanceApplicationService(
|
2025-07-28 01:46:39 +08:00
|
|
|
|
aliPayClient *payment.AliPayService,
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatPayService *payment.WechatPayService,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
walletService finance_services.WalletAggregateService,
|
|
|
|
|
|
rechargeRecordService finance_services.RechargeRecordService,
|
|
|
|
|
|
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
|
|
|
|
|
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatOrderRepo finance_repositories.WechatOrderRepository,
|
|
|
|
|
|
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
|
2025-08-02 02:54:21 +08:00
|
|
|
|
userRepo user_repositories.UserRepository,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager *database.TransactionManager,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
logger *zap.Logger,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
config *config.Config,
|
2025-09-12 01:15:09 +08:00
|
|
|
|
exportManager *export.ExportManager,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
) FinanceApplicationService {
|
|
|
|
|
|
return &FinanceApplicationServiceImpl{
|
2025-07-28 01:46:39 +08:00
|
|
|
|
aliPayClient: aliPayClient,
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatPayService: wechatPayService,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
walletService: walletService,
|
|
|
|
|
|
rechargeRecordService: rechargeRecordService,
|
|
|
|
|
|
walletTransactionRepository: walletTransactionRepository,
|
|
|
|
|
|
alipayOrderRepo: alipayOrderRepo,
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatOrderRepo: wechatOrderRepo,
|
|
|
|
|
|
rechargeRecordRepo: rechargeRecordRepo,
|
2025-08-02 02:54:21 +08:00
|
|
|
|
userRepo: userRepo,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
txManager: txManager,
|
2025-09-12 01:15:09 +08:00
|
|
|
|
exportManager: exportManager,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
logger: logger,
|
|
|
|
|
|
config: config,
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 调用钱包聚合服务创建钱包
|
|
|
|
|
|
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建钱包失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.WalletResponse{
|
|
|
|
|
|
ID: wallet.ID,
|
|
|
|
|
|
UserID: wallet.UserID,
|
|
|
|
|
|
IsActive: wallet.IsActive,
|
|
|
|
|
|
Balance: wallet.Balance,
|
|
|
|
|
|
BalanceStatus: wallet.GetBalanceStatus(),
|
|
|
|
|
|
IsArrears: wallet.IsArrears(),
|
|
|
|
|
|
IsLowBalance: wallet.IsLowBalance(),
|
|
|
|
|
|
CreatedAt: wallet.CreatedAt,
|
|
|
|
|
|
UpdatedAt: wallet.UpdatedAt,
|
|
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// 调用钱包聚合服务获取钱包信息
|
|
|
|
|
|
wallet, err := s.walletService.LoadWalletByUserId(ctx, query.UserID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("获取钱包信息失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.WalletResponse{
|
|
|
|
|
|
ID: wallet.ID,
|
|
|
|
|
|
UserID: wallet.UserID,
|
|
|
|
|
|
IsActive: wallet.IsActive,
|
|
|
|
|
|
Balance: wallet.Balance,
|
|
|
|
|
|
BalanceStatus: wallet.GetBalanceStatus(),
|
|
|
|
|
|
IsArrears: wallet.IsArrears(),
|
|
|
|
|
|
IsLowBalance: wallet.IsLowBalance(),
|
2025-12-12 15:27:15 +08:00
|
|
|
|
|
|
|
|
|
|
CreatedAt: wallet.CreatedAt,
|
|
|
|
|
|
UpdatedAt: wallet.UpdatedAt,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateAlipayRechargeOrder 创建支付宝充值订单(完整流程编排)
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error) {
|
|
|
|
|
|
cmd.Subject = "天远数据API充值"
|
|
|
|
|
|
// 将字符串金额转换为 decimal.Decimal
|
|
|
|
|
|
amount, err := decimal.NewFromString(cmd.Amount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("金额格式错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证金额是否大于0
|
|
|
|
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额必须大于0")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从配置中获取充值限制
|
2025-07-31 15:41:00 +08:00
|
|
|
|
minAmount, err := decimal.NewFromString(s.config.Wallet.MinAmount)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
2025-07-31 15:41:00 +08:00
|
|
|
|
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Wallet.MinAmount), zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-31 15:41:00 +08:00
|
|
|
|
maxAmount, err := decimal.NewFromString(s.config.Wallet.MaxAmount)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
2025-07-31 15:41:00 +08:00
|
|
|
|
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Wallet.MaxAmount), zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证充值金额范围
|
|
|
|
|
|
if amount.LessThan(minAmount) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额不能少于%s元", minAmount.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if amount.GreaterThan(maxAmount) {
|
|
|
|
|
|
return nil, fmt.Errorf("单次充值金额不能超过%s元", maxAmount.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 生成订单号
|
|
|
|
|
|
outTradeNo := s.aliPayClient.GenerateOutTradeNo()
|
|
|
|
|
|
var payUrl string
|
|
|
|
|
|
// 2. 进入事务,创建充值记录和支付宝订单本地记录
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
var err error
|
|
|
|
|
|
// 创建充值记录
|
|
|
|
|
|
rechargeRecord, err := s.rechargeRecordService.CreateAlipayRecharge(txCtx, cmd.UserID, amount, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建支付宝充值记录失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("创建支付宝充值记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建支付宝订单本地记录
|
|
|
|
|
|
err = s.rechargeRecordService.CreateAlipayOrder(txCtx, rechargeRecord.ID, outTradeNo, cmd.Subject, amount, cmd.Platform)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建支付宝订单记录失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("创建支付宝订单记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3. 创建支付宝订单(调用支付宝API,非事务内)
|
|
|
|
|
|
payUrl, err = s.aliPayClient.CreateAlipayOrder(ctx, cmd.Platform, amount, cmd.Subject, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建支付宝订单失败", zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("创建支付宝订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("支付宝充值订单创建成功",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.String("platform", cmd.Platform),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.AlipayRechargeOrderResponse{
|
|
|
|
|
|
PayURL: payUrl,
|
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
|
Amount: amount,
|
|
|
|
|
|
Platform: cmd.Platform,
|
|
|
|
|
|
Subject: cmd.Subject,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// CreateWechatRechargeOrder 创建微信充值订单(完整流程编排)
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) CreateWechatRechargeOrder(ctx context.Context, cmd *commands.CreateWechatRechargeCommand) (*responses.WechatRechargeOrderResponse, error) {
|
|
|
|
|
|
cmd.Subject = "天远数据API充值"
|
|
|
|
|
|
amount, err := decimal.NewFromString(cmd.Amount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("金额格式错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额必须大于0")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
minAmount, err := decimal.NewFromString(s.config.Wallet.MinAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Wallet.MinAmount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
maxAmount, err := decimal.NewFromString(s.config.Wallet.MaxAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Wallet.MaxAmount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("系统配置错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if amount.LessThan(minAmount) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额不能少于%s元", minAmount.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
if amount.GreaterThan(maxAmount) {
|
|
|
|
|
|
return nil, fmt.Errorf("单次充值金额不能超过%s元", maxAmount.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
platform := normalizeWechatPlatform(cmd.Platform)
|
|
|
|
|
|
if platform != payment.PlatformWxNative && platform != payment.PlatformWxH5 {
|
|
|
|
|
|
return nil, fmt.Errorf("不支持的支付平台: %s", cmd.Platform)
|
|
|
|
|
|
}
|
|
|
|
|
|
if s.wechatPayService == nil {
|
|
|
|
|
|
return nil, fmt.Errorf("微信支付服务未初始化")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
outTradeNo := s.wechatPayService.GenerateOutTradeNo()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("开始创建微信充值订单",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.String("platform", cmd.Platform),
|
|
|
|
|
|
zap.String("subject", cmd.Subject),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var prepayData interface{}
|
|
|
|
|
|
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
// 创建微信充值记录
|
|
|
|
|
|
rechargeRecord := finance_entities.NewWechatRechargeRecord(cmd.UserID, amount, outTradeNo)
|
|
|
|
|
|
createdRecord, createErr := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
|
|
|
|
|
|
if createErr != nil {
|
|
|
|
|
|
s.logger.Error("创建微信充值记录失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.Error(createErr),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("创建微信充值记录失败: %w", createErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("创建微信充值记录成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("recharge_id", createdRecord.ID),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建微信订单本地记录
|
|
|
|
|
|
wechatOrder := finance_entities.NewWechatOrder(createdRecord.ID, outTradeNo, cmd.Subject, amount, platform)
|
|
|
|
|
|
createdOrder, orderErr := s.wechatOrderRepo.Create(txCtx, *wechatOrder)
|
|
|
|
|
|
if orderErr != nil {
|
|
|
|
|
|
s.logger.Error("创建微信订单记录失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("recharge_id", createdRecord.ID),
|
|
|
|
|
|
zap.Error(orderErr),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("创建微信订单记录失败: %w", orderErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("创建微信订单记录成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("order_id", createdOrder.ID),
|
|
|
|
|
|
zap.String("recharge_id", createdRecord.ID),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
payCtx := context.WithValue(ctx, "platform", platform)
|
|
|
|
|
|
payCtx = context.WithValue(payCtx, "user_id", cmd.UserID)
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("调用微信支付接口创建订单",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("platform", platform),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
prepayData, err = s.wechatPayService.CreateWechatOrder(payCtx, amount.InexactFloat64(), cmd.Subject, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("微信下单失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 回写失败状态
|
|
|
|
|
|
_ = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
order, getErr := s.wechatOrderRepo.GetByOutTradeNo(txCtx, outTradeNo)
|
|
|
|
|
|
if getErr == nil && order != nil {
|
|
|
|
|
|
order.MarkFailed("create_failed", err.Error())
|
|
|
|
|
|
updateErr := s.wechatOrderRepo.Update(txCtx, *order)
|
|
|
|
|
|
if updateErr != nil {
|
|
|
|
|
|
s.logger.Error("回写微信订单失败状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(updateErr),
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
s.logger.Info("回写微信订单失败状态成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("创建微信支付订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("微信充值订单创建成功",
|
|
|
|
|
|
zap.String("user_id", cmd.UserID),
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.String("platform", cmd.Platform),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.WechatRechargeOrderResponse{
|
|
|
|
|
|
OutTradeNo: outTradeNo,
|
|
|
|
|
|
Amount: amount,
|
|
|
|
|
|
Platform: platform,
|
|
|
|
|
|
Subject: cmd.Subject,
|
|
|
|
|
|
PrepayData: prepayData,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// normalizeWechatPlatform 将兼容写法(h5/mini)转换为系统内使用的wx_h5/wx_mini
|
|
|
|
|
|
func normalizeWechatPlatform(p string) string {
|
|
|
|
|
|
switch p {
|
|
|
|
|
|
case "h5", payment.PlatformWxH5:
|
|
|
|
|
|
return payment.PlatformWxNative
|
|
|
|
|
|
case "native":
|
|
|
|
|
|
return payment.PlatformWxNative
|
|
|
|
|
|
default:
|
|
|
|
|
|
return p
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// TransferRecharge 对公转账充值
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
|
|
|
|
|
// 将字符串金额转换为 decimal.Decimal
|
|
|
|
|
|
amount, err := decimal.NewFromString(cmd.Amount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("金额格式错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证金额是否大于0
|
|
|
|
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额必须大于0")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 调用充值记录服务进行对公转账充值
|
|
|
|
|
|
rechargeRecord, err := s.rechargeRecordService.TransferRecharge(ctx, cmd.UserID, amount, cmd.TransferOrderID, cmd.Notes)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("对公转账充值失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
transferOrderID := ""
|
|
|
|
|
|
if rechargeRecord.TransferOrderID != nil {
|
|
|
|
|
|
transferOrderID = *rechargeRecord.TransferOrderID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.RechargeRecordResponse{
|
|
|
|
|
|
ID: rechargeRecord.ID,
|
|
|
|
|
|
UserID: rechargeRecord.UserID,
|
|
|
|
|
|
Amount: rechargeRecord.Amount,
|
|
|
|
|
|
RechargeType: string(rechargeRecord.RechargeType),
|
|
|
|
|
|
Status: string(rechargeRecord.Status),
|
|
|
|
|
|
TransferOrderID: transferOrderID,
|
|
|
|
|
|
Notes: rechargeRecord.Notes,
|
|
|
|
|
|
CreatedAt: rechargeRecord.CreatedAt,
|
|
|
|
|
|
UpdatedAt: rechargeRecord.UpdatedAt,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GiftRecharge 赠送充值
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
|
|
|
|
|
// 将字符串金额转换为 decimal.Decimal
|
|
|
|
|
|
amount, err := decimal.NewFromString(cmd.Amount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("金额格式错误: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证金额是否大于0
|
|
|
|
|
|
if amount.LessThanOrEqual(decimal.Zero) {
|
|
|
|
|
|
return nil, fmt.Errorf("充值金额必须大于0")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前操作员ID(这里假设从上下文中获取,实际可能需要从认证中间件获取)
|
|
|
|
|
|
operatorID := "system" // 临时使用,实际应该从认证上下文获取
|
|
|
|
|
|
|
|
|
|
|
|
// 调用充值记录服务进行赠送充值
|
|
|
|
|
|
rechargeRecord, err := s.rechargeRecordService.GiftRecharge(ctx, cmd.UserID, amount, operatorID, cmd.Notes)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("赠送充值失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.RechargeRecordResponse{
|
|
|
|
|
|
ID: rechargeRecord.ID,
|
|
|
|
|
|
UserID: rechargeRecord.UserID,
|
|
|
|
|
|
Amount: rechargeRecord.Amount,
|
|
|
|
|
|
RechargeType: string(rechargeRecord.RechargeType),
|
|
|
|
|
|
Status: string(rechargeRecord.Status),
|
|
|
|
|
|
OperatorID: "system", // 临时使用,实际应该从认证上下文获取
|
|
|
|
|
|
Notes: rechargeRecord.Notes,
|
|
|
|
|
|
CreatedAt: rechargeRecord.CreatedAt,
|
|
|
|
|
|
UpdatedAt: rechargeRecord.UpdatedAt,
|
|
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// GetUserWalletTransactions 获取用户钱包交易记录
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error) {
|
|
|
|
|
|
// 查询钱包交易记录(包含产品名称)
|
|
|
|
|
|
productNameMap, transactions, total, err := s.walletTransactionRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询钱包交易记录失败", zap.Error(err), zap.String("userID", userID))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []responses.WalletTransactionResponse
|
|
|
|
|
|
for _, transaction := range transactions {
|
|
|
|
|
|
item := responses.WalletTransactionResponse{
|
|
|
|
|
|
ID: transaction.ID,
|
|
|
|
|
|
UserID: transaction.UserID,
|
|
|
|
|
|
ApiCallID: transaction.ApiCallID,
|
|
|
|
|
|
TransactionID: transaction.TransactionID,
|
|
|
|
|
|
ProductID: transaction.ProductID,
|
|
|
|
|
|
ProductName: productNameMap[transaction.ProductID], // 从映射中获取产品名称
|
|
|
|
|
|
Amount: transaction.Amount,
|
|
|
|
|
|
CreatedAt: transaction.CreatedAt,
|
|
|
|
|
|
UpdatedAt: transaction.UpdatedAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.WalletTransactionListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// GetAdminWalletTransactions 获取管理端钱包交易记录
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error) {
|
|
|
|
|
|
// 查询钱包交易记录(包含产品名称)
|
|
|
|
|
|
productNameMap, transactions, total, err := s.walletTransactionRepository.ListWithFiltersAndProductName(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询管理端钱包交易记录失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []responses.WalletTransactionResponse
|
|
|
|
|
|
for _, transaction := range transactions {
|
|
|
|
|
|
item := responses.WalletTransactionResponse{
|
|
|
|
|
|
ID: transaction.ID,
|
|
|
|
|
|
UserID: transaction.UserID,
|
|
|
|
|
|
ApiCallID: transaction.ApiCallID,
|
|
|
|
|
|
TransactionID: transaction.TransactionID,
|
|
|
|
|
|
ProductID: transaction.ProductID,
|
|
|
|
|
|
ProductName: productNameMap[transaction.ProductID], // 从映射中获取产品名称
|
|
|
|
|
|
Amount: transaction.Amount,
|
|
|
|
|
|
CreatedAt: transaction.CreatedAt,
|
|
|
|
|
|
UpdatedAt: transaction.UpdatedAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户信息和企业名称
|
|
|
|
|
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, transaction.UserID)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
item.CompanyName = companyName
|
|
|
|
|
|
item.User = &responses.UserSimpleResponse{
|
|
|
|
|
|
ID: user.ID,
|
|
|
|
|
|
CompanyName: companyName,
|
|
|
|
|
|
Phone: user.Phone,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.WalletTransactionListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
2025-09-12 01:15:09 +08:00
|
|
|
|
// ExportAdminWalletTransactions 导出管理端钱包交易记录
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) ExportAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error) {
|
|
|
|
|
|
const batchSize = 1000 // 每批处理1000条记录
|
|
|
|
|
|
var allTransactions []*finance_entities.WalletTransaction
|
|
|
|
|
|
var productNameMap map[string]string
|
|
|
|
|
|
|
|
|
|
|
|
// 分批获取数据
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
for {
|
|
|
|
|
|
// 查询当前批次的数据
|
|
|
|
|
|
batchProductNameMap, transactions, _, err := s.walletTransactionRepository.ListWithFiltersAndProductName(ctx, filters, interfaces.ListOptions{
|
|
|
|
|
|
Page: page,
|
|
|
|
|
|
PageSize: batchSize,
|
|
|
|
|
|
Sort: "created_at",
|
|
|
|
|
|
Order: "desc",
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询导出钱包交易记录失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 合并产品名称映射
|
|
|
|
|
|
if productNameMap == nil {
|
|
|
|
|
|
productNameMap = batchProductNameMap
|
|
|
|
|
|
} else {
|
|
|
|
|
|
for k, v := range batchProductNameMap {
|
|
|
|
|
|
productNameMap[k] = v
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到总数据中
|
|
|
|
|
|
allTransactions = append(allTransactions, transactions...)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前批次数据少于批次大小,说明已经是最后一批
|
|
|
|
|
|
if len(transactions) < batchSize {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
page++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有数据
|
|
|
|
|
|
if len(allTransactions) == 0 {
|
|
|
|
|
|
return nil, fmt.Errorf("没有找到符合条件的数据")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量获取企业名称映射,避免N+1查询问题
|
|
|
|
|
|
companyNameMap, err := s.batchGetCompanyNames(ctx, allTransactions)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
companyNameMap = make(map[string]string)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 准备导出数据
|
|
|
|
|
|
headers := []string{"交易ID", "企业名称", "产品名称", "消费金额", "消费时间"}
|
|
|
|
|
|
columnWidths := []float64{20, 25, 20, 15, 20}
|
|
|
|
|
|
|
|
|
|
|
|
data := make([][]interface{}, len(allTransactions))
|
|
|
|
|
|
for i, transaction := range allTransactions {
|
|
|
|
|
|
companyName := companyNameMap[transaction.UserID]
|
|
|
|
|
|
if companyName == "" {
|
|
|
|
|
|
companyName = "未知企业"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
productName := productNameMap[transaction.ProductID]
|
|
|
|
|
|
if productName == "" {
|
|
|
|
|
|
productName = "未知产品"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
data[i] = []interface{}{
|
|
|
|
|
|
transaction.TransactionID,
|
|
|
|
|
|
companyName,
|
|
|
|
|
|
productName,
|
|
|
|
|
|
transaction.Amount.String(),
|
|
|
|
|
|
transaction.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建导出配置
|
|
|
|
|
|
config := &export.ExportConfig{
|
|
|
|
|
|
SheetName: "消费记录",
|
|
|
|
|
|
Headers: headers,
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
ColumnWidths: columnWidths,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用导出管理器生成文件
|
|
|
|
|
|
return s.exportManager.Export(ctx, config, format)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// batchGetCompanyNames 批量获取企业名称映射
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) batchGetCompanyNames(ctx context.Context, transactions []*finance_entities.WalletTransaction) (map[string]string, error) {
|
|
|
|
|
|
// 收集所有唯一的用户ID
|
|
|
|
|
|
userIDSet := make(map[string]bool)
|
|
|
|
|
|
for _, transaction := range transactions {
|
|
|
|
|
|
userIDSet[transaction.UserID] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为切片
|
|
|
|
|
|
userIDs := make([]string, 0, len(userIDSet))
|
|
|
|
|
|
for userID := range userIDSet {
|
|
|
|
|
|
userIDs = append(userIDs, userID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量查询用户信息
|
|
|
|
|
|
users, err := s.userRepo.BatchGetByIDsWithEnterpriseInfo(ctx, userIDs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建企业名称映射
|
|
|
|
|
|
companyNameMap := make(map[string]string)
|
|
|
|
|
|
for _, user := range users {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
companyNameMap[user.ID] = companyName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return companyNameMap, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ExportAdminRechargeRecords 导出管理端充值记录
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error) {
|
|
|
|
|
|
const batchSize = 1000 // 每批处理1000条记录
|
|
|
|
|
|
var allRecords []finance_entities.RechargeRecord
|
|
|
|
|
|
|
|
|
|
|
|
// 分批获取数据
|
|
|
|
|
|
page := 1
|
|
|
|
|
|
for {
|
|
|
|
|
|
// 查询当前批次的数据
|
|
|
|
|
|
records, err := s.rechargeRecordService.GetAll(ctx, filters, interfaces.ListOptions{
|
|
|
|
|
|
Page: page,
|
|
|
|
|
|
PageSize: batchSize,
|
|
|
|
|
|
Sort: "created_at",
|
|
|
|
|
|
Order: "desc",
|
|
|
|
|
|
})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询导出充值记录失败", zap.Error(err))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加到总数据中
|
|
|
|
|
|
allRecords = append(allRecords, records...)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果当前批次数据少于批次大小,说明已经是最后一批
|
|
|
|
|
|
if len(records) < batchSize {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
page++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量获取企业名称映射,避免N+1查询问题
|
|
|
|
|
|
companyNameMap, err := s.batchGetCompanyNamesForRechargeRecords(ctx, convertToRechargeRecordPointers(allRecords))
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Warn("批量获取企业名称失败,使用默认值", zap.Error(err))
|
|
|
|
|
|
companyNameMap = make(map[string]string)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 准备导出数据
|
2025-12-12 15:27:15 +08:00
|
|
|
|
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "微信订单号", "转账订单号", "备注", "充值时间"}
|
|
|
|
|
|
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20, 20}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
|
|
|
|
|
|
data := make([][]interface{}, len(allRecords))
|
|
|
|
|
|
for i, record := range allRecords {
|
|
|
|
|
|
// 从映射中获取企业名称
|
|
|
|
|
|
companyName := companyNameMap[record.UserID]
|
|
|
|
|
|
if companyName == "" {
|
|
|
|
|
|
companyName = "未知企业"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取订单号
|
|
|
|
|
|
alipayOrderID := ""
|
|
|
|
|
|
if record.AlipayOrderID != nil && *record.AlipayOrderID != "" {
|
|
|
|
|
|
alipayOrderID = *record.AlipayOrderID
|
|
|
|
|
|
}
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatOrderID := ""
|
|
|
|
|
|
if record.WechatOrderID != nil && *record.WechatOrderID != "" {
|
|
|
|
|
|
wechatOrderID = *record.WechatOrderID
|
|
|
|
|
|
}
|
2025-09-12 01:15:09 +08:00
|
|
|
|
transferOrderID := ""
|
|
|
|
|
|
if record.TransferOrderID != nil && *record.TransferOrderID != "" {
|
|
|
|
|
|
transferOrderID = *record.TransferOrderID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取备注
|
|
|
|
|
|
notes := ""
|
|
|
|
|
|
if record.Notes != "" {
|
|
|
|
|
|
notes = record.Notes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
createdAt := record.CreatedAt.Format("2006-01-02 15:04:05")
|
|
|
|
|
|
|
|
|
|
|
|
data[i] = []interface{}{
|
|
|
|
|
|
companyName,
|
|
|
|
|
|
record.Amount.String(),
|
|
|
|
|
|
translateRechargeType(record.RechargeType),
|
|
|
|
|
|
translateRechargeStatus(record.Status),
|
|
|
|
|
|
alipayOrderID,
|
2025-12-12 15:27:15 +08:00
|
|
|
|
wechatOrderID,
|
2025-09-12 01:15:09 +08:00
|
|
|
|
transferOrderID,
|
|
|
|
|
|
notes,
|
|
|
|
|
|
createdAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建导出配置
|
|
|
|
|
|
config := &export.ExportConfig{
|
|
|
|
|
|
SheetName: "充值记录",
|
|
|
|
|
|
Headers: headers,
|
|
|
|
|
|
Data: data,
|
|
|
|
|
|
ColumnWidths: columnWidths,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用导出管理器生成文件
|
|
|
|
|
|
return s.exportManager.Export(ctx, config, format)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// translateRechargeType 翻译充值类型为中文
|
|
|
|
|
|
func translateRechargeType(rechargeType finance_entities.RechargeType) string {
|
|
|
|
|
|
switch rechargeType {
|
|
|
|
|
|
case finance_entities.RechargeTypeAlipay:
|
|
|
|
|
|
return "支付宝充值"
|
2025-12-12 15:27:15 +08:00
|
|
|
|
case finance_entities.RechargeTypeWechat:
|
|
|
|
|
|
return "微信充值"
|
2025-09-12 01:15:09 +08:00
|
|
|
|
case finance_entities.RechargeTypeTransfer:
|
|
|
|
|
|
return "对公转账"
|
|
|
|
|
|
case finance_entities.RechargeTypeGift:
|
|
|
|
|
|
return "赠送"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "未知类型"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// translateRechargeStatus 翻译充值状态为中文
|
|
|
|
|
|
func translateRechargeStatus(status finance_entities.RechargeStatus) string {
|
|
|
|
|
|
switch status {
|
|
|
|
|
|
case finance_entities.RechargeStatusPending:
|
|
|
|
|
|
return "待处理"
|
|
|
|
|
|
case finance_entities.RechargeStatusSuccess:
|
|
|
|
|
|
return "成功"
|
|
|
|
|
|
case finance_entities.RechargeStatusFailed:
|
|
|
|
|
|
return "失败"
|
|
|
|
|
|
case finance_entities.RechargeStatusCancelled:
|
|
|
|
|
|
return "已取消"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "未知状态"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// convertToRechargeRecordPointers 将RechargeRecord切片转换为指针切片
|
|
|
|
|
|
func convertToRechargeRecordPointers(records []finance_entities.RechargeRecord) []*finance_entities.RechargeRecord {
|
|
|
|
|
|
pointers := make([]*finance_entities.RechargeRecord, len(records))
|
|
|
|
|
|
for i := range records {
|
|
|
|
|
|
pointers[i] = &records[i]
|
|
|
|
|
|
}
|
|
|
|
|
|
return pointers
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// batchGetCompanyNamesForRechargeRecords 批量获取企业名称映射(用于充值记录)
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) batchGetCompanyNamesForRechargeRecords(ctx context.Context, records []*finance_entities.RechargeRecord) (map[string]string, error) {
|
|
|
|
|
|
// 收集所有唯一的用户ID
|
|
|
|
|
|
userIDSet := make(map[string]bool)
|
|
|
|
|
|
for _, record := range records {
|
|
|
|
|
|
userIDSet[record.UserID] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为切片
|
|
|
|
|
|
userIDs := make([]string, 0, len(userIDSet))
|
|
|
|
|
|
for userID := range userIDSet {
|
|
|
|
|
|
userIDs = append(userIDs, userID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量查询用户信息
|
|
|
|
|
|
users, err := s.userRepo.BatchGetByIDsWithEnterpriseInfo(ctx, userIDs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建企业名称映射
|
|
|
|
|
|
companyNameMap := make(map[string]string)
|
|
|
|
|
|
for _, user := range users {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
companyNameMap[user.ID] = companyName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return companyNameMap, nil
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// HandleAlipayCallback 处理支付宝回调
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context, r *http.Request) error {
|
|
|
|
|
|
// 解析并验证支付宝回调通知
|
|
|
|
|
|
notification, err := s.aliPayClient.HandleAliPaymentNotification(r)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("支付宝回调验证失败", zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录回调数据
|
|
|
|
|
|
s.logger.Info("支付宝回调数据",
|
|
|
|
|
|
zap.String("out_trade_no", notification.OutTradeNo),
|
|
|
|
|
|
zap.String("trade_no", notification.TradeNo),
|
|
|
|
|
|
zap.String("trade_status", string(notification.TradeStatus)),
|
|
|
|
|
|
zap.String("total_amount", notification.TotalAmount),
|
|
|
|
|
|
zap.String("buyer_id", notification.BuyerId),
|
|
|
|
|
|
zap.String("seller_id", notification.SellerId),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查交易状态
|
|
|
|
|
|
if !s.aliPayClient.IsAlipayPaymentSuccess(notification) {
|
|
|
|
|
|
s.logger.Warn("支付宝交易未成功",
|
|
|
|
|
|
zap.String("out_trade_no", notification.OutTradeNo),
|
|
|
|
|
|
zap.String("trade_status", string(notification.TradeStatus)),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil // 不返回错误,因为这是正常的业务状态
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 使用公共方法处理支付成功逻辑
|
|
|
|
|
|
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("处理支付宝支付成功失败",
|
|
|
|
|
|
zap.String("out_trade_no", notification.OutTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// processAlipayPaymentSuccess 处理支付宝支付成功的公共逻辑
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.Context, outTradeNo, tradeNo, totalAmount, buyerID, sellerID string) error {
|
|
|
|
|
|
// 解析金额
|
|
|
|
|
|
amount, err := decimal.NewFromString(totalAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("解析支付宝金额失败",
|
|
|
|
|
|
zap.String("total_amount", totalAmount),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 直接调用充值记录服务处理支付成功逻辑
|
|
|
|
|
|
// 该服务内部会处理所有必要的检查、事务和更新操作
|
|
|
|
|
|
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
|
|
|
|
|
if err != nil {
|
2025-09-12 01:15:09 +08:00
|
|
|
|
s.logger.Error("处理支付宝支付成功失败",
|
2025-07-28 01:46:39 +08:00
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("支付宝支付成功处理完成",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("trade_no", tradeNo),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// updateAlipayOrderStatus 根据支付宝状态更新本地订单状态
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) updateAlipayOrderStatus(ctx context.Context, outTradeNo string, alipayStatus alipay.TradeStatus, tradeNo, totalAmount string) error {
|
|
|
|
|
|
// 查找支付宝订单
|
|
|
|
|
|
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("查找支付宝订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if alipayOrder == nil {
|
|
|
|
|
|
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
return fmt.Errorf("支付宝订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch alipayStatus {
|
|
|
|
|
|
case alipay.TradeStatusSuccess:
|
|
|
|
|
|
// 支付成功,调用公共处理逻辑
|
|
|
|
|
|
return s.processAlipayPaymentSuccess(ctx, outTradeNo, tradeNo, totalAmount, "", "")
|
|
|
|
|
|
case alipay.TradeStatusClosed:
|
|
|
|
|
|
// 交易关闭
|
|
|
|
|
|
s.logger.Info("支付宝订单已关闭", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
alipayOrder.MarkClosed()
|
|
|
|
|
|
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
case alipay.TradeStatusWaitBuyerPay:
|
|
|
|
|
|
// 等待买家付款,保持pending状态
|
|
|
|
|
|
s.logger.Info("支付宝订单等待买家付款", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 其他状态,记录日志
|
|
|
|
|
|
s.logger.Info("支付宝订单其他状态", zap.String("out_trade_no", outTradeNo), zap.String("status", string(alipayStatus)))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// HandleAlipayReturn 处理支付宝同步回调
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error) {
|
|
|
|
|
|
if outTradeNo == "" {
|
|
|
|
|
|
return "", fmt.Errorf("缺少商户订单号")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找支付宝订单
|
|
|
|
|
|
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return "", fmt.Errorf("查找支付宝订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if alipayOrder == nil {
|
|
|
|
|
|
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
return "", fmt.Errorf("支付宝订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录同步回调查询
|
|
|
|
|
|
s.logger.Info("支付宝同步回调查询订单状态",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("order_status", string(alipayOrder.Status)),
|
|
|
|
|
|
zap.String("trade_no", func() string {
|
|
|
|
|
|
if alipayOrder.TradeNo != nil {
|
|
|
|
|
|
return *alipayOrder.TradeNo
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 返回订单状态
|
|
|
|
|
|
switch alipayOrder.Status {
|
|
|
|
|
|
case finance_entities.AlipayOrderStatusSuccess:
|
|
|
|
|
|
return "TRADE_SUCCESS", nil
|
|
|
|
|
|
case finance_entities.AlipayOrderStatusPending:
|
|
|
|
|
|
// 对于pending状态,需要特殊处理
|
|
|
|
|
|
// 可能是用户支付了但支付宝异步回调还没到,或者用户还没支付
|
|
|
|
|
|
// 这里可以尝试主动查询支付宝订单状态,但为了简化处理,先返回WAIT_BUYER_PAY
|
|
|
|
|
|
// 让前端显示"支付处理中"的状态,用户可以通过刷新页面或等待异步回调来更新状态
|
|
|
|
|
|
s.logger.Info("支付宝订单状态为pending,建议用户等待异步回调或刷新页面",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
return "WAIT_BUYER_PAY", nil
|
|
|
|
|
|
case finance_entities.AlipayOrderStatusFailed:
|
|
|
|
|
|
return "TRADE_FAILED", nil
|
|
|
|
|
|
case finance_entities.AlipayOrderStatusClosed:
|
|
|
|
|
|
return "TRADE_CLOSED", nil
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "UNKNOWN", nil
|
|
|
|
|
|
}
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// GetAlipayOrderStatus 获取支付宝订单状态
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error) {
|
|
|
|
|
|
if outTradeNo == "" {
|
|
|
|
|
|
return nil, fmt.Errorf("缺少商户订单号")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找支付宝订单
|
|
|
|
|
|
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("查找支付宝订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if alipayOrder == nil {
|
|
|
|
|
|
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
return nil, fmt.Errorf("支付宝订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果订单状态为pending,主动查询支付宝订单状态
|
|
|
|
|
|
if alipayOrder.Status == finance_entities.AlipayOrderStatusPending {
|
|
|
|
|
|
s.logger.Info("订单状态为pending,主动查询支付宝订单状态", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
|
|
|
|
|
|
// 调用支付宝查询接口
|
|
|
|
|
|
alipayResp, err := s.aliPayClient.QueryOrderStatus(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
// 查询失败不影响返回,继续使用数据库中的状态
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 解析支付宝返回的状态
|
|
|
|
|
|
alipayStatus := alipayResp.TradeStatus
|
|
|
|
|
|
s.logger.Info("支付宝返回订单状态",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("alipay_status", string(alipayStatus)),
|
|
|
|
|
|
zap.String("trade_no", alipayResp.TradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 使用公共方法更新订单状态
|
|
|
|
|
|
err = s.updateAlipayOrderStatus(ctx, outTradeNo, alipayStatus, alipayResp.TradeNo, alipayResp.TotalAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新获取更新后的订单信息
|
|
|
|
|
|
updatedOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err == nil && updatedOrder != nil {
|
|
|
|
|
|
alipayOrder = updatedOrder
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否处理中
|
|
|
|
|
|
isProcessing := alipayOrder.Status == finance_entities.AlipayOrderStatusPending
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否可以重试(失败状态可以重试)
|
|
|
|
|
|
canRetry := alipayOrder.Status == finance_entities.AlipayOrderStatusFailed
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
response := &responses.AlipayOrderStatusResponse{
|
|
|
|
|
|
OutTradeNo: alipayOrder.OutTradeNo,
|
|
|
|
|
|
TradeNo: alipayOrder.TradeNo,
|
|
|
|
|
|
Status: string(alipayOrder.Status),
|
|
|
|
|
|
Amount: alipayOrder.Amount,
|
|
|
|
|
|
Subject: alipayOrder.Subject,
|
|
|
|
|
|
Platform: alipayOrder.Platform,
|
|
|
|
|
|
CreatedAt: alipayOrder.CreatedAt,
|
|
|
|
|
|
UpdatedAt: alipayOrder.UpdatedAt,
|
|
|
|
|
|
NotifyTime: alipayOrder.NotifyTime,
|
|
|
|
|
|
ReturnTime: alipayOrder.ReturnTime,
|
|
|
|
|
|
ErrorCode: &alipayOrder.ErrorCode,
|
|
|
|
|
|
ErrorMessage: &alipayOrder.ErrorMessage,
|
|
|
|
|
|
IsProcessing: isProcessing,
|
|
|
|
|
|
CanRetry: canRetry,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果错误码为空,设置为nil
|
|
|
|
|
|
if alipayOrder.ErrorCode == "" {
|
|
|
|
|
|
response.ErrorCode = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if alipayOrder.ErrorMessage == "" {
|
|
|
|
|
|
response.ErrorMessage = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("查询支付宝订单状态完成",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("status", string(alipayOrder.Status)),
|
|
|
|
|
|
zap.Bool("is_processing", isProcessing),
|
|
|
|
|
|
zap.Bool("can_retry", canRetry),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// GetUserRechargeRecords 获取用户充值记录
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 确保 filters 不为 nil
|
|
|
|
|
|
if filters == nil {
|
|
|
|
|
|
filters = make(map[string]interface{})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
|
|
|
|
|
|
filters["user_id"] = userID
|
|
|
|
|
|
|
|
|
|
|
|
// 查询用户充值记录(使用筛选和分页功能)
|
|
|
|
|
|
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 获取总数(使用筛选条件)
|
|
|
|
|
|
total, err := s.rechargeRecordService.Count(ctx, filters)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("统计用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []responses.RechargeRecordResponse
|
|
|
|
|
|
for _, record := range records {
|
|
|
|
|
|
item := responses.RechargeRecordResponse{
|
|
|
|
|
|
ID: record.ID,
|
|
|
|
|
|
UserID: record.UserID,
|
|
|
|
|
|
Amount: record.Amount,
|
|
|
|
|
|
RechargeType: string(record.RechargeType),
|
|
|
|
|
|
Status: string(record.Status),
|
|
|
|
|
|
Notes: record.Notes,
|
|
|
|
|
|
CreatedAt: record.CreatedAt,
|
|
|
|
|
|
UpdatedAt: record.UpdatedAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 根据充值类型设置相应的订单号和平台信息
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if record.AlipayOrderID != nil {
|
|
|
|
|
|
item.AlipayOrderID = *record.AlipayOrderID
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 通过订单号获取平台信息
|
|
|
|
|
|
if alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, *record.AlipayOrderID); err == nil && alipayOrder != nil {
|
|
|
|
|
|
item.Platform = alipayOrder.Platform
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if record.WechatOrderID != nil {
|
|
|
|
|
|
item.WechatOrderID = *record.WechatOrderID
|
|
|
|
|
|
// 通过订单号获取平台信息
|
|
|
|
|
|
if wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, *record.WechatOrderID); err == nil && wechatOrder != nil {
|
|
|
|
|
|
item.Platform = wechatOrder.Platform
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
if record.TransferOrderID != nil {
|
|
|
|
|
|
item.TransferOrderID = *record.TransferOrderID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.RechargeRecordListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// GetAdminRechargeRecords 获取管理端充值记录
|
2025-07-28 01:46:39 +08:00
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// 查询充值记录
|
2025-07-28 01:46:39 +08:00
|
|
|
|
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
|
|
|
|
|
|
if err != nil {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
s.logger.Error("查询管理端充值记录失败", zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取总数
|
|
|
|
|
|
total, err := s.rechargeRecordService.Count(ctx, filters)
|
|
|
|
|
|
if err != nil {
|
2025-08-02 02:54:21 +08:00
|
|
|
|
s.logger.Error("统计管理端充值记录失败", zap.Error(err))
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
var items []responses.RechargeRecordResponse
|
|
|
|
|
|
for _, record := range records {
|
|
|
|
|
|
item := responses.RechargeRecordResponse{
|
2025-09-12 01:15:09 +08:00
|
|
|
|
ID: record.ID,
|
|
|
|
|
|
UserID: record.UserID,
|
|
|
|
|
|
Amount: record.Amount,
|
|
|
|
|
|
RechargeType: string(record.RechargeType),
|
|
|
|
|
|
Status: string(record.Status),
|
|
|
|
|
|
Notes: record.Notes,
|
|
|
|
|
|
CreatedAt: record.CreatedAt,
|
|
|
|
|
|
UpdatedAt: record.UpdatedAt,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 根据充值类型设置相应的订单号和平台信息
|
2025-07-28 01:46:39 +08:00
|
|
|
|
if record.AlipayOrderID != nil {
|
|
|
|
|
|
item.AlipayOrderID = *record.AlipayOrderID
|
2025-12-12 15:27:15 +08:00
|
|
|
|
// 通过订单号获取平台信息
|
|
|
|
|
|
if alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, *record.AlipayOrderID); err == nil && alipayOrder != nil {
|
|
|
|
|
|
item.Platform = alipayOrder.Platform
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if record.WechatOrderID != nil {
|
|
|
|
|
|
item.WechatOrderID = *record.WechatOrderID
|
|
|
|
|
|
// 通过订单号获取平台信息
|
|
|
|
|
|
if wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, *record.WechatOrderID); err == nil && wechatOrder != nil {
|
|
|
|
|
|
item.Platform = wechatOrder.Platform
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
if record.TransferOrderID != nil {
|
|
|
|
|
|
item.TransferOrderID = *record.TransferOrderID
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-02 02:54:21 +08:00
|
|
|
|
// 获取用户信息和企业名称
|
|
|
|
|
|
user, err := s.userRepo.GetByIDWithEnterpriseInfo(ctx, record.UserID)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
companyName := "未知企业"
|
|
|
|
|
|
if user.EnterpriseInfo != nil {
|
|
|
|
|
|
companyName = user.EnterpriseInfo.CompanyName
|
|
|
|
|
|
}
|
|
|
|
|
|
item.CompanyName = companyName
|
|
|
|
|
|
item.User = &responses.UserSimpleResponse{
|
|
|
|
|
|
ID: user.ID,
|
|
|
|
|
|
CompanyName: companyName,
|
|
|
|
|
|
Phone: user.Phone,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
items = append(items, item)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return &responses.RechargeRecordListResponse{
|
|
|
|
|
|
Items: items,
|
|
|
|
|
|
Total: total,
|
|
|
|
|
|
Page: options.Page,
|
|
|
|
|
|
Size: options.PageSize,
|
|
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-07-28 01:46:39 +08:00
|
|
|
|
// GetRechargeConfig 获取充值配置
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
2025-07-31 15:41:00 +08:00
|
|
|
|
bonus := make([]responses.AlipayRechargeBonusRuleResponse, 0, len(s.config.Wallet.AliPayRechargeBonus))
|
|
|
|
|
|
for _, rule := range s.config.Wallet.AliPayRechargeBonus {
|
|
|
|
|
|
bonus = append(bonus, responses.AlipayRechargeBonusRuleResponse{
|
|
|
|
|
|
RechargeAmount: rule.RechargeAmount,
|
|
|
|
|
|
BonusAmount: rule.BonusAmount,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-07-28 01:46:39 +08:00
|
|
|
|
return &responses.RechargeConfigResponse{
|
2025-09-12 01:15:09 +08:00
|
|
|
|
MinAmount: s.config.Wallet.MinAmount,
|
|
|
|
|
|
MaxAmount: s.config.Wallet.MaxAmount,
|
2025-07-31 15:41:00 +08:00
|
|
|
|
AlipayRechargeBonus: bonus,
|
2025-07-28 01:46:39 +08:00
|
|
|
|
}, nil
|
2025-07-13 16:36:20 +08:00
|
|
|
|
}
|
2025-12-12 15:27:15 +08:00
|
|
|
|
|
|
|
|
|
|
// GetWechatOrderStatus 获取微信订单状态
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) GetWechatOrderStatus(ctx context.Context, outTradeNo string) (*responses.WechatOrderStatusResponse, error) {
|
|
|
|
|
|
if outTradeNo == "" {
|
|
|
|
|
|
return nil, fmt.Errorf("缺少商户订单号")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找微信订单
|
|
|
|
|
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找微信订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return nil, fmt.Errorf("查找微信订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if wechatOrder == nil {
|
|
|
|
|
|
s.logger.Error("微信订单不存在", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
return nil, fmt.Errorf("微信订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果订单状态为pending,主动查询微信订单状态
|
|
|
|
|
|
if wechatOrder.Status == finance_entities.WechatOrderStatusPending {
|
|
|
|
|
|
s.logger.Info("订单状态为pending,主动查询微信订单状态",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 调用微信查询接口
|
|
|
|
|
|
transaction, err := s.wechatPayService.QueryOrderStatus(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查询微信订单状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
// 查询失败不影响返回,继续使用数据库中的状态
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 解析微信返回的状态
|
|
|
|
|
|
tradeState := ""
|
|
|
|
|
|
transactionID := ""
|
|
|
|
|
|
if transaction.TradeState != nil {
|
|
|
|
|
|
tradeState = *transaction.TradeState
|
|
|
|
|
|
}
|
|
|
|
|
|
if transaction.TransactionId != nil {
|
|
|
|
|
|
transactionID = *transaction.TransactionId
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("微信查询订单状态返回",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("trade_state", tradeState),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 使用公共方法更新订单状态
|
|
|
|
|
|
err = s.updateWechatOrderStatus(ctx, outTradeNo, tradeState, transaction)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新微信订单状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("trade_state", tradeState),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重新获取更新后的订单信息
|
|
|
|
|
|
updatedOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err == nil && updatedOrder != nil {
|
|
|
|
|
|
wechatOrder = updatedOrder
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否处理中
|
|
|
|
|
|
isProcessing := wechatOrder.Status == finance_entities.WechatOrderStatusPending
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否可以重试(失败状态可以重试)
|
|
|
|
|
|
canRetry := wechatOrder.Status == finance_entities.WechatOrderStatusFailed
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为响应DTO
|
|
|
|
|
|
response := &responses.WechatOrderStatusResponse{
|
|
|
|
|
|
OutTradeNo: wechatOrder.OutTradeNo,
|
|
|
|
|
|
TransactionID: wechatOrder.TradeNo,
|
|
|
|
|
|
Status: string(wechatOrder.Status),
|
|
|
|
|
|
Amount: wechatOrder.Amount,
|
|
|
|
|
|
Subject: wechatOrder.Subject,
|
|
|
|
|
|
Platform: wechatOrder.Platform,
|
|
|
|
|
|
CreatedAt: wechatOrder.CreatedAt,
|
|
|
|
|
|
UpdatedAt: wechatOrder.UpdatedAt,
|
|
|
|
|
|
NotifyTime: wechatOrder.NotifyTime,
|
|
|
|
|
|
ReturnTime: wechatOrder.ReturnTime,
|
|
|
|
|
|
ErrorCode: &wechatOrder.ErrorCode,
|
|
|
|
|
|
ErrorMessage: &wechatOrder.ErrorMessage,
|
|
|
|
|
|
IsProcessing: isProcessing,
|
|
|
|
|
|
CanRetry: canRetry,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果错误码为空,设置为nil
|
|
|
|
|
|
if wechatOrder.ErrorCode == "" {
|
|
|
|
|
|
response.ErrorCode = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if wechatOrder.ErrorMessage == "" {
|
|
|
|
|
|
response.ErrorMessage = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("查询微信订单状态完成",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("status", string(wechatOrder.Status)),
|
|
|
|
|
|
zap.Bool("is_processing", isProcessing),
|
|
|
|
|
|
zap.Bool("can_retry", canRetry),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return response, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// updateWechatOrderStatus 根据微信状态更新本地订单状态
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) updateWechatOrderStatus(ctx context.Context, outTradeNo string, tradeState string, transaction *payments.Transaction) error {
|
|
|
|
|
|
// 查找微信订单
|
|
|
|
|
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找微信订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
|
|
|
|
|
return fmt.Errorf("查找微信订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if wechatOrder == nil {
|
|
|
|
|
|
s.logger.Error("微信订单不存在", zap.String("out_trade_no", outTradeNo))
|
|
|
|
|
|
return fmt.Errorf("微信订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch tradeState {
|
|
|
|
|
|
case payment.TradeStateSuccess:
|
|
|
|
|
|
// 支付成功,调用公共处理逻辑
|
|
|
|
|
|
transactionID := ""
|
|
|
|
|
|
if transaction.TransactionId != nil {
|
|
|
|
|
|
transactionID = *transaction.TransactionId
|
|
|
|
|
|
}
|
|
|
|
|
|
payAmount := decimal.Zero
|
|
|
|
|
|
if transaction.Amount != nil && transaction.Amount.Total != nil {
|
|
|
|
|
|
// 将分转换为元
|
|
|
|
|
|
payAmount = decimal.NewFromInt(*transaction.Amount.Total).Div(decimal.NewFromInt(100))
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, payAmount)
|
|
|
|
|
|
case payment.TradeStateClosed:
|
|
|
|
|
|
// 交易关闭
|
|
|
|
|
|
s.logger.Info("微信订单交易关闭",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
wechatOrder.MarkClosed()
|
|
|
|
|
|
err = s.wechatOrderRepo.Update(ctx, *wechatOrder)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新微信订单关闭状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Info("微信订单关闭状态更新成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
case payment.TradeStateNotPay:
|
|
|
|
|
|
// 未支付,保持pending状态
|
|
|
|
|
|
s.logger.Info("微信订单未支付",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
default:
|
|
|
|
|
|
// 其他状态,记录日志
|
|
|
|
|
|
s.logger.Info("微信订单其他状态",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("trade_state", tradeState),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HandleWechatPayCallback 处理微信支付回调
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) HandleWechatPayCallback(ctx context.Context, r *http.Request) error {
|
|
|
|
|
|
if s.wechatPayService == nil {
|
|
|
|
|
|
s.logger.Error("微信支付服务未初始化")
|
|
|
|
|
|
return fmt.Errorf("微信支付服务未初始化")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析并验证微信支付回调通知
|
|
|
|
|
|
transaction, err := s.wechatPayService.HandleWechatPayNotification(ctx, r)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("微信支付回调验证失败", zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 提取回调数据
|
|
|
|
|
|
outTradeNo := ""
|
|
|
|
|
|
if transaction.OutTradeNo != nil {
|
|
|
|
|
|
outTradeNo = *transaction.OutTradeNo
|
|
|
|
|
|
}
|
|
|
|
|
|
transactionID := ""
|
|
|
|
|
|
if transaction.TransactionId != nil {
|
|
|
|
|
|
transactionID = *transaction.TransactionId
|
|
|
|
|
|
}
|
|
|
|
|
|
tradeState := ""
|
|
|
|
|
|
if transaction.TradeState != nil {
|
|
|
|
|
|
tradeState = *transaction.TradeState
|
|
|
|
|
|
}
|
|
|
|
|
|
totalAmount := decimal.Zero
|
|
|
|
|
|
if transaction.Amount != nil && transaction.Amount.Total != nil {
|
|
|
|
|
|
// 将分转换为元
|
|
|
|
|
|
totalAmount = decimal.NewFromInt(*transaction.Amount.Total).Div(decimal.NewFromInt(100))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录回调数据
|
|
|
|
|
|
s.logger.Info("微信支付回调数据",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("trade_state", tradeState),
|
|
|
|
|
|
zap.String("total_amount", totalAmount.String()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 检查交易状态
|
|
|
|
|
|
if tradeState != payment.TradeStateSuccess {
|
|
|
|
|
|
s.logger.Warn("微信支付交易未成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("trade_state", tradeState),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil // 不返回错误,因为这是正常的业务状态
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理支付成功逻辑
|
|
|
|
|
|
err = s.processWechatPaymentSuccess(ctx, outTradeNo, transactionID, totalAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("处理微信支付成功失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("amount", totalAmount.String()),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// processWechatPaymentSuccess 处理微信支付成功的公共逻辑
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) processWechatPaymentSuccess(ctx context.Context, outTradeNo, transactionID string, amount decimal.Decimal) error {
|
|
|
|
|
|
// 查找微信订单
|
|
|
|
|
|
wechatOrder, err := s.wechatOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找微信订单失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("查找微信订单失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if wechatOrder == nil {
|
|
|
|
|
|
s.logger.Error("微信订单不存在",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("微信订单不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找对应的充值记录
|
|
|
|
|
|
rechargeRecord, err := s.rechargeRecordService.GetByID(ctx, wechatOrder.RechargeID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("查找充值记录失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("recharge_id", wechatOrder.RechargeID),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return fmt.Errorf("查找充值记录失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查订单和充值记录状态,如果都已成功则跳过(只记录一次日志)
|
|
|
|
|
|
if wechatOrder.Status == finance_entities.WechatOrderStatusSuccess && rechargeRecord.Status == finance_entities.RechargeStatusSuccess {
|
|
|
|
|
|
s.logger.Info("微信支付订单已处理成功,跳过重复处理",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("order_id", wechatOrder.ID),
|
|
|
|
|
|
zap.String("recharge_id", rechargeRecord.ID),
|
|
|
|
|
|
)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算充值赠送金额(复用支付宝的赠送逻辑)
|
|
|
|
|
|
bonusAmount := decimal.Zero
|
|
|
|
|
|
if len(s.config.Wallet.AliPayRechargeBonus) > 0 {
|
|
|
|
|
|
for i := len(s.config.Wallet.AliPayRechargeBonus) - 1; i >= 0; i-- {
|
|
|
|
|
|
rule := s.config.Wallet.AliPayRechargeBonus[i]
|
|
|
|
|
|
if amount.GreaterThanOrEqual(decimal.NewFromFloat(rule.RechargeAmount)) {
|
|
|
|
|
|
bonusAmount = decimal.NewFromFloat(rule.BonusAmount)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录开始处理支付成功
|
|
|
|
|
|
s.logger.Info("开始处理微信支付成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.String("user_id", rechargeRecord.UserID),
|
|
|
|
|
|
zap.String("bonus_amount", bonusAmount.String()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 在事务中处理支付成功逻辑
|
|
|
|
|
|
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
|
|
|
|
|
// 更新微信订单状态
|
|
|
|
|
|
wechatOrder.MarkSuccess(transactionID, "", "", amount, amount)
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
wechatOrder.NotifyTime = &now
|
|
|
|
|
|
err := s.wechatOrderRepo.Update(txCtx, *wechatOrder)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新微信订单状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新充值记录状态为成功
|
|
|
|
|
|
rechargeRecord.MarkSuccess()
|
|
|
|
|
|
err = s.rechargeRecordRepo.Update(txCtx, *rechargeRecord)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("更新充值记录状态失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("recharge_id", rechargeRecord.ID),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有赠送金额,创建赠送充值记录
|
|
|
|
|
|
if bonusAmount.GreaterThan(decimal.Zero) {
|
|
|
|
|
|
giftRechargeRecord := finance_entities.NewGiftRechargeRecord(rechargeRecord.UserID, bonusAmount, "充值活动赠送")
|
|
|
|
|
|
createdGift, err := s.rechargeRecordRepo.Create(txCtx, *giftRechargeRecord)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("创建赠送充值记录失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("user_id", rechargeRecord.UserID),
|
|
|
|
|
|
zap.String("bonus_amount", bonusAmount.String()),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Info("创建赠送充值记录成功",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("gift_recharge_id", createdGift.ID),
|
|
|
|
|
|
zap.String("bonus_amount", bonusAmount.String()),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 充值到钱包(包含赠送金额)
|
|
|
|
|
|
totalRechargeAmount := amount.Add(bonusAmount)
|
|
|
|
|
|
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, totalRechargeAmount)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("充值到钱包失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("user_id", rechargeRecord.UserID),
|
|
|
|
|
|
zap.String("total_amount", totalRechargeAmount.String()),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("处理微信支付成功失败",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.Error(err),
|
|
|
|
|
|
)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("微信支付成功处理完成",
|
|
|
|
|
|
zap.String("out_trade_no", outTradeNo),
|
|
|
|
|
|
zap.String("transaction_id", transactionID),
|
|
|
|
|
|
zap.String("amount", amount.String()),
|
|
|
|
|
|
zap.String("bonus_amount", bonusAmount.String()),
|
|
|
|
|
|
zap.String("user_id", rechargeRecord.UserID),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HandleWechatRefundCallback 处理微信退款回调
|
|
|
|
|
|
func (s *FinanceApplicationServiceImpl) HandleWechatRefundCallback(ctx context.Context, r *http.Request) error {
|
|
|
|
|
|
if s.wechatPayService == nil {
|
|
|
|
|
|
s.logger.Error("微信支付服务未初始化")
|
|
|
|
|
|
return fmt.Errorf("微信支付服务未初始化")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析并验证微信退款回调通知
|
|
|
|
|
|
refund, err := s.wechatPayService.HandleRefundNotification(ctx, r)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("微信退款回调验证失败", zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录回调数据
|
|
|
|
|
|
s.logger.Info("微信退款回调数据",
|
|
|
|
|
|
zap.String("out_trade_no", func() string {
|
|
|
|
|
|
if refund.OutTradeNo != nil {
|
|
|
|
|
|
return *refund.OutTradeNo
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
zap.String("out_refund_no", func() string {
|
|
|
|
|
|
if refund.OutRefundNo != nil {
|
|
|
|
|
|
return *refund.OutRefundNo
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
zap.String("refund_id", func() string {
|
|
|
|
|
|
if refund.RefundId != nil {
|
|
|
|
|
|
return *refund.RefundId
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
zap.Any("status", func() interface{} {
|
|
|
|
|
|
if refund.Status != nil {
|
|
|
|
|
|
return *refund.Status
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 处理退款逻辑
|
|
|
|
|
|
// 这里可以根据实际业务需求实现退款处理逻辑
|
|
|
|
|
|
s.logger.Info("微信退款回调处理完成",
|
|
|
|
|
|
zap.String("out_trade_no", func() string {
|
|
|
|
|
|
if refund.OutTradeNo != nil {
|
|
|
|
|
|
return *refund.OutTradeNo
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
zap.String("refund_id", func() string {
|
|
|
|
|
|
if refund.RefundId != nil {
|
|
|
|
|
|
return *refund.RefundId
|
|
|
|
|
|
}
|
|
|
|
|
|
return ""
|
|
|
|
|
|
}()),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|