This commit is contained in:
2025-07-28 01:46:39 +08:00
parent b03129667a
commit 357639462a
219 changed files with 21634 additions and 8138 deletions

View File

@@ -1,69 +1,31 @@
package commands
import (
"time"
"github.com/shopspring/decimal"
)
// CreateWalletCommand 创建钱包命令
type CreateWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
}
// UpdateWalletCommand 更新钱包命令
type UpdateWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Balance decimal.Decimal `json:"balance" binding:"omitempty"`
IsActive *bool `json:"is_active"`
// TransferRechargeCommand 对公转账充值命令
type TransferRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
TransferOrderID string `json:"transfer_order_id" binding:"required" comment:"转账订单号"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// RechargeWalletCommand 充值钱包命令
type RechargeWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
// GiftRechargeCommand 赠送充值命令
type GiftRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// RechargeCommand 充值命令
type RechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// WithdrawWalletCommand 提现钱包命令
type WithdrawWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// WithdrawCommand 提现命令
type WithdrawCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
}
// CreateUserSecretsCommand 创建用户密钥命令
type CreateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
}
// RegenerateAccessKeyCommand 重新生成访问密钥命令
type RegenerateAccessKeyCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
}
// DeactivateUserSecretsCommand 停用用户密钥命令
type DeactivateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
}
// WalletTransactionCommand 钱包交易命令
type WalletTransactionCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
FromUserID string `json:"from_user_id" binding:"required,uuid"`
ToUserID string `json:"to_user_id" binding:"required,uuid"`
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
Notes string `json:"notes" binding:"omitempty,max=200"`
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
type CreateAlipayRechargeCommand struct {
UserID string `json:"-"` // 用户ID从token获取
Amount string `json:"amount" binding:"required"` // 充值金额
Subject string `json:"-"` // 订单标题
Platform string `json:"platform" binding:"required,oneof=app h5 pc"` // 支付平台app/h5/pc
}

View File

@@ -0,0 +1,25 @@
package responses
import (
"time"
"github.com/shopspring/decimal"
)
// AlipayOrderStatusResponse 支付宝订单状态响应
type AlipayOrderStatusResponse struct {
OutTradeNo string `json:"out_trade_no"` // 商户订单号
TradeNo *string `json:"trade_no"` // 支付宝交易号
Status string `json:"status"` // 订单状态
Amount decimal.Decimal `json:"amount"` // 订单金额
Subject string `json:"subject"` // 订单标题
Platform string `json:"platform"` // 支付平台
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
NotifyTime *time.Time `json:"notify_time"` // 异步通知时间
ReturnTime *time.Time `json:"return_time"` // 同步返回时间
ErrorCode *string `json:"error_code"` // 错误码
ErrorMessage *string `json:"error_message"` // 错误信息
IsProcessing bool `json:"is_processing"` // 是否处理中
CanRetry bool `json:"can_retry"` // 是否可以重试
}

View File

@@ -8,24 +8,21 @@ import (
// WalletResponse 钱包响应
type WalletResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
IsActive bool `json:"is_active"`
Balance decimal.Decimal `json:"balance"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
UserID string `json:"user_id"`
IsActive bool `json:"is_active"`
Balance decimal.Decimal `json:"balance"`
BalanceStatus string `json:"balance_status"` // normal, low, arrears
IsArrears bool `json:"is_arrears"` // 是否欠费
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TransactionResponse 交易响应
type TransactionResponse struct {
TransactionID string `json:"transaction_id"`
FromUserID string `json:"from_user_id"`
ToUserID string `json:"to_user_id"`
Amount decimal.Decimal `json:"amount"`
FromBalance decimal.Decimal `json:"from_balance"`
ToBalance decimal.Decimal `json:"to_balance"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
}
// UserSecretsResponse 用户密钥响应
@@ -49,3 +46,62 @@ type WalletStatsResponse struct {
TodayTransactions int64 `json:"today_transactions"`
TodayVolume decimal.Decimal `json:"today_volume"`
}
// RechargeRecordResponse 充值记录响应
type RechargeRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Amount decimal.Decimal `json:"amount"`
RechargeType string `json:"recharge_type"`
Status string `json:"status"`
AlipayOrderID string `json:"alipay_order_id,omitempty"`
TransferOrderID string `json:"transfer_order_id,omitempty"`
Notes string `json:"notes,omitempty"`
OperatorID string `json:"operator_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionResponse 钱包交易记录响应
type WalletTransactionResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ApiCallID string `json:"api_call_id"`
TransactionID string `json:"transaction_id"`
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
Amount decimal.Decimal `json:"amount"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionListResponse 钱包交易记录列表响应
type WalletTransactionListResponse struct {
Items []WalletTransactionResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// RechargeRecordListResponse 充值记录列表响应
type RechargeRecordListResponse struct {
Items []RechargeRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// AlipayRechargeOrderResponse 支付宝充值订单响应
type AlipayRechargeOrderResponse struct {
PayURL string `json:"pay_url"` // 支付链接
OutTradeNo string `json:"out_trade_no"` // 商户订单号
Amount decimal.Decimal `json:"amount"` // 充值金额
Platform string `json:"platform"` // 支付平台
Subject string `json:"subject"` // 订单标题
}
// RechargeConfigResponse 充值配置响应
type RechargeConfigResponse struct {
MinAmount string `json:"min_amount"` // 最低充值金额
MaxAmount string `json:"max_amount"` // 最高充值金额
}

View File

@@ -2,23 +2,36 @@ package finance
import (
"context"
"net/http"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/shared/interfaces"
)
// FinanceApplicationService 财务应用服务接口
type FinanceApplicationService interface {
CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error)
GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error)
UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error
Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error)
Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error)
CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error)
GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error)
RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error)
DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error
WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error)
GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error)
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error)
HandleAlipayCallback(ctx context.Context, r *http.Request) error
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error)
GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error)
TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error)
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
// 获取用户钱包交易记录
GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
// 获取用户充值记录
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 管理员获取充值记录
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 获取充值配置
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
}

View File

@@ -2,88 +2,649 @@ package finance
import (
"context"
"fmt"
"go.uber.org/zap"
"net/http"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/domains/finance/repositories"
"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"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/payment"
"github.com/shopspring/decimal"
"github.com/smartwalle/alipay/v3"
"go.uber.org/zap"
)
// FinanceApplicationServiceImpl 财务应用服务实现
type FinanceApplicationServiceImpl struct {
walletRepo repositories.WalletRepository
userSecretsRepo repositories.UserSecretsRepository
logger *zap.Logger
aliPayClient *payment.AliPayService
walletService finance_services.WalletAggregateService
rechargeRecordService finance_services.RechargeRecordService
walletTransactionRepository finance_repositories.WalletTransactionRepository
alipayOrderRepo finance_repositories.AlipayOrderRepository
txManager *database.TransactionManager
logger *zap.Logger
config *config.Config
}
// NewFinanceApplicationService 创建财务应用服务
func NewFinanceApplicationService(
walletRepo repositories.WalletRepository,
userSecretsRepo repositories.UserSecretsRepository,
aliPayClient *payment.AliPayService,
walletService finance_services.WalletAggregateService,
rechargeRecordService finance_services.RechargeRecordService,
walletTransactionRepository finance_repositories.WalletTransactionRepository,
alipayOrderRepo finance_repositories.AlipayOrderRepository,
txManager *database.TransactionManager,
logger *zap.Logger,
config *config.Config,
) FinanceApplicationService {
return &FinanceApplicationServiceImpl{
walletRepo: walletRepo,
userSecretsRepo: userSecretsRepo,
logger: logger,
aliPayClient: aliPayClient,
walletService: walletService,
rechargeRecordService: rechargeRecordService,
walletTransactionRepository: walletTransactionRepository,
alipayOrderRepo: alipayOrderRepo,
txManager: txManager,
logger: logger,
config: config,
}
}
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 调用钱包聚合服务创建钱包
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) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 调用钱包聚合服务获取钱包信息
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
}
func (s *FinanceApplicationServiceImpl) UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
// 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.Recharge.MinAmount)
if err != nil {
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Recharge.MinAmount), zap.Error(err))
return nil, fmt.Errorf("系统配置错误: %w", err)
}
maxAmount, err := decimal.NewFromString(s.config.Recharge.MaxAmount)
if err != nil {
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Recharge.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
}
func (s *FinanceApplicationServiceImpl) Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
// 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
}
func (s *FinanceApplicationServiceImpl) WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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
}
}
func (s *FinanceApplicationServiceImpl) GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
// 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) {
// 查询用户充值记录
records, err := s.rechargeRecordService.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
return nil, err
}
// 计算总数
total := int64(len(records))
// 转换为响应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 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 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
}
// GetRechargeConfig 获取充值配置
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
return &responses.RechargeConfigResponse{
MinAmount: s.config.Recharge.MinAmount,
MaxAmount: s.config.Recharge.MaxAmount,
}, nil
}