Files
tyapi-server/internal/application/finance/finance_application_service_impl.go
2025-12-12 15:47:20 +08:00

1671 lines
55 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 finance
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"github.com/smartwalle/alipay/v3"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"go.uber.org/zap"
"net/http"
"time"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"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"
user_repositories "tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/export"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/payment"
)
// FinanceApplicationServiceImpl 财务应用服务实现
type FinanceApplicationServiceImpl struct {
aliPayClient *payment.AliPayService
wechatPayService *payment.WechatPayService
walletService finance_services.WalletAggregateService
rechargeRecordService finance_services.RechargeRecordService
walletTransactionRepository finance_repositories.WalletTransactionRepository
alipayOrderRepo finance_repositories.AlipayOrderRepository
wechatOrderRepo finance_repositories.WechatOrderRepository
rechargeRecordRepo finance_repositories.RechargeRecordRepository
userRepo user_repositories.UserRepository
txManager *database.TransactionManager
exportManager *export.ExportManager
logger *zap.Logger
config *config.Config
}
// NewFinanceApplicationService 创建财务应用服务
func NewFinanceApplicationService(
aliPayClient *payment.AliPayService,
wechatPayService *payment.WechatPayService,
walletService finance_services.WalletAggregateService,
rechargeRecordService finance_services.RechargeRecordService,
walletTransactionRepository finance_repositories.WalletTransactionRepository,
alipayOrderRepo finance_repositories.AlipayOrderRepository,
wechatOrderRepo finance_repositories.WechatOrderRepository,
rechargeRecordRepo finance_repositories.RechargeRecordRepository,
userRepo user_repositories.UserRepository,
txManager *database.TransactionManager,
logger *zap.Logger,
config *config.Config,
exportManager *export.ExportManager,
) FinanceApplicationService {
return &FinanceApplicationServiceImpl{
aliPayClient: aliPayClient,
wechatPayService: wechatPayService,
walletService: walletService,
rechargeRecordService: rechargeRecordService,
walletTransactionRepository: walletTransactionRepository,
alipayOrderRepo: alipayOrderRepo,
wechatOrderRepo: wechatOrderRepo,
rechargeRecordRepo: rechargeRecordRepo,
userRepo: userRepo,
txManager: txManager,
exportManager: exportManager,
logger: logger,
config: config,
}
}
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
// 调用钱包聚合服务创建钱包
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
}
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
// 调用钱包聚合服务获取钱包信息
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(),
CreatedAt: wallet.CreatedAt,
UpdatedAt: wallet.UpdatedAt,
}, 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")
}
// 从配置中获取充值限制
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())
}
// 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
}
// 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
}
}
// 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
}
// 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
}
// 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
}
// 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)
}
// 准备导出数据
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "微信订单号", "转账订单号", "备注", "充值时间"}
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20, 20}
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
}
wechatOrderID := ""
if record.WechatOrderID != nil && *record.WechatOrderID != "" {
wechatOrderID = *record.WechatOrderID
}
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,
wechatOrderID,
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 "支付宝充值"
case finance_entities.RechargeTypeWechat:
return "微信充值"
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
}
// 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
}
// 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 {
s.logger.Error("处理支付宝支付成功失败",
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
}
// 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
}
// 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
}
}
// 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
}
// GetUserRechargeRecords 获取用户充值记录
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
// 确保 filters 不为 nil
if filters == nil {
filters = make(map[string]interface{})
}
// 添加 user_id 筛选条件,确保只能查询当前用户的记录
filters["user_id"] = userID
// 查询用户充值记录(使用筛选和分页功能)
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
if err != nil {
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 获取总数(使用筛选条件)
total, err := s.rechargeRecordService.Count(ctx, filters)
if err != nil {
s.logger.Error("统计用户充值记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 转换为响应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,
}
// 根据充值类型设置相应的订单号和平台信息
if record.AlipayOrderID != nil {
item.AlipayOrderID = *record.AlipayOrderID
// 通过订单号获取平台信息
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
}
}
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
}
// GetAdminRechargeRecords 获取管理端充值记录
func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
// 查询充值记录
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
if err != nil {
s.logger.Error("查询管理端充值记录失败", zap.Error(err))
return nil, err
}
// 获取总数
total, err := s.rechargeRecordService.Count(ctx, filters)
if err != nil {
s.logger.Error("统计管理端充值记录失败", zap.Error(err))
return nil, err
}
// 转换为响应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,
}
// 根据充值类型设置相应的订单号和平台信息
if record.AlipayOrderID != nil {
item.AlipayOrderID = *record.AlipayOrderID
// 通过订单号获取平台信息
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
}
}
if record.TransferOrderID != nil {
item.TransferOrderID = *record.TransferOrderID
}
// 获取用户信息和企业名称
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,
}
}
items = append(items, item)
}
return &responses.RechargeRecordListResponse{
Items: items,
Total: total,
Page: options.Page,
Size: options.PageSize,
}, nil
}
// GetRechargeConfig 获取充值配置
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
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,
})
}
return &responses.RechargeConfigResponse{
MinAmount: s.config.Wallet.MinAmount,
MaxAmount: s.config.Wallet.MaxAmount,
AlipayRechargeBonus: bonus,
}, nil
}
// 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
}