Files
tyapi-server/internal/infrastructure/http/handlers/finance_handler.go
2025-07-28 01:46:39 +08:00

557 lines
17 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 handlers
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/application/finance"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/shared/interfaces"
)
// FinanceHandler 财务HTTP处理器
type FinanceHandler struct {
appService finance.FinanceApplicationService
responseBuilder interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
}
// NewFinanceHandler 创建财务HTTP处理器
func NewFinanceHandler(
appService finance.FinanceApplicationService,
responseBuilder interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
) *FinanceHandler {
return &FinanceHandler{
appService: appService,
responseBuilder: responseBuilder,
validator: validator,
logger: logger,
}
}
// GetWallet 获取钱包信息
// @Summary 获取钱包信息
// @Description 获取当前用户的钱包详细信息
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} responses.WalletResponse "获取钱包信息成功"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "钱包不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet [get]
func (h *FinanceHandler) GetWallet(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
query := &queries.GetWalletInfoQuery{UserID: userID}
result, err := h.appService.GetWallet(c.Request.Context(), query)
if err != nil {
h.logger.Error("获取钱包信息失败",
zap.String("user_id", userID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "获取钱包信息成功")
}
// GetUserWalletTransactions 获取用户钱包交易记录
// @Summary 获取用户钱包交易记录
// @Description 获取当前用户的钱包交易记录列表,支持分页和筛选
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param start_time query string false "开始时间 (格式: 2006-01-02 15:04:05)"
// @Param end_time query string false "结束时间 (格式: 2006-01-02 15:04:05)"
// @Param transaction_id query string false "交易ID"
// @Param product_name query string false "产品名称"
// @Param min_amount query string false "最小金额"
// @Param max_amount query string false "最大金额"
// @Success 200 {object} responses.WalletTransactionListResponse "获取成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/transactions [get]
func (h *FinanceHandler) GetUserWalletTransactions(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 交易ID筛选
if transactionId := c.Query("transaction_id"); transactionId != "" {
filters["transaction_id"] = transactionId
}
// 产品名称筛选
if productName := c.Query("product_name"); productName != "" {
filters["product_name"] = productName
}
// 金额范围筛选
if minAmount := c.Query("min_amount"); minAmount != "" {
filters["min_amount"] = minAmount
}
if maxAmount := c.Query("max_amount"); maxAmount != "" {
filters["max_amount"] = maxAmount
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetUserWalletTransactions(c.Request.Context(), userID, filters, options)
if err != nil {
h.logger.Error("获取用户钱包交易记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取钱包交易记录失败")
return
}
h.responseBuilder.Success(c, result, "获取钱包交易记录成功")
}
// getIntQuery 获取整数查询参数
func (h *FinanceHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
if value := c.Query(key); value != "" {
if intValue, err := strconv.Atoi(value); err == nil && intValue > 0 {
return intValue
}
}
return defaultValue
}
// HandleAlipayCallback 处理支付宝支付回调
// @Summary 支付宝支付回调
// @Description 处理支付宝异步支付通知
// @Tags 支付管理
// @Accept application/x-www-form-urlencoded
// @Produce text/plain
// @Success 200 {string} string "success"
// @Failure 400 {string} string "fail"
// @Router /api/v1/finance/alipay/callback [post]
func (h *FinanceHandler) HandleAlipayCallback(c *gin.Context) {
// 记录回调请求信息
h.logger.Info("收到支付宝回调请求",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("remote_addr", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
)
// 通过应用服务处理支付宝回调
err := h.appService.HandleAlipayCallback(c.Request.Context(), c.Request)
if err != nil {
h.logger.Error("支付宝回调处理失败", zap.Error(err))
c.String(400, "fail")
return
}
// 返回成功响应支付宝要求返回success
c.String(200, "success")
}
// HandleAlipayReturn 处理支付宝同步回调
// @Summary 支付宝同步回调
// @Description 处理支付宝同步支付通知,跳转到前端成功页面
// @Tags 支付管理
// @Accept application/x-www-form-urlencoded
// @Produce text/html
// @Success 200 {string} string "支付成功页面"
// @Failure 400 {string} string "支付失败页面"
// @Router /api/v1/finance/alipay/return [get]
func (h *FinanceHandler) HandleAlipayReturn(c *gin.Context) {
// 记录同步回调请求信息
h.logger.Info("收到支付宝同步回调请求",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("remote_addr", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
)
// 获取查询参数
outTradeNo := c.Query("out_trade_no")
tradeNo := c.Query("trade_no")
totalAmount := c.Query("total_amount")
h.logger.Info("支付宝同步回调参数",
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", tradeNo),
zap.String("total_amount", totalAmount),
)
// 验证必要参数
if outTradeNo == "" {
h.logger.Error("支付宝同步回调缺少商户订单号")
h.redirectToFailPage(c, "", "缺少商户订单号")
return
}
// 通过应用服务处理同步回调,查询订单状态
orderStatus, err := h.appService.HandleAlipayReturn(c.Request.Context(), outTradeNo)
if err != nil {
h.logger.Error("支付宝同步回调处理失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err))
h.redirectToFailPage(c, outTradeNo, "订单处理失败")
return
}
// 根据环境确定前端域名
frontendDomain := "https://www.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
// 根据订单状态跳转到相应页面
switch orderStatus {
case "TRADE_SUCCESS":
// 支付成功,跳转到前端成功页面
successURL := fmt.Sprintf("%s/finance/wallet/success?out_trade_no=%s&trade_no=%s&amount=%s",
frontendDomain, outTradeNo, tradeNo, totalAmount)
c.Redirect(http.StatusFound, successURL)
case "WAIT_BUYER_PAY":
// 支付处理中,跳转到处理中页面
h.redirectToProcessingPage(c, outTradeNo, totalAmount)
default:
// 支付失败或取消,跳转到前端失败页面
h.redirectToFailPage(c, outTradeNo, orderStatus)
}
}
// redirectToFailPage 跳转到失败页面
func (h *FinanceHandler) redirectToFailPage(c *gin.Context, outTradeNo, reason string) {
frontendDomain := "https://www.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
failURL := fmt.Sprintf("%s/finance/wallet/fail?out_trade_no=%s&reason=%s",
frontendDomain, outTradeNo, reason)
c.Redirect(http.StatusFound, failURL)
}
// redirectToProcessingPage 跳转到处理中页面
func (h *FinanceHandler) redirectToProcessingPage(c *gin.Context, outTradeNo, amount string) {
frontendDomain := "https://www.tianyuanapi.com"
if gin.Mode() == gin.DebugMode {
frontendDomain = "http://localhost:5173"
}
processingURL := fmt.Sprintf("%s/finance/wallet/processing?out_trade_no=%s&amount=%s",
frontendDomain, outTradeNo, amount)
c.Redirect(http.StatusFound, processingURL)
}
// CreateAlipayRecharge 创建支付宝充值订单
// @Summary 创建支付宝充值订单
// @Description 创建支付宝充值订单并返回支付链接
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body commands.CreateAlipayRechargeCommand true "充值请求"
// @Success 200 {object} responses.AlipayRechargeOrderResponse "创建充值订单成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/alipay-recharge [post]
func (h *FinanceHandler) CreateAlipayRecharge(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
var cmd commands.CreateAlipayRechargeCommand
cmd.UserID = userID
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
// 调用应用服务进行完整的业务流程编排
result, err := h.appService.CreateAlipayRechargeOrder(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("创建支付宝充值订单失败",
zap.String("user_id", userID),
zap.String("amount", cmd.Amount),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, "创建支付宝充值订单失败: "+err.Error())
return
}
h.logger.Info("支付宝充值订单创建成功",
zap.String("user_id", userID),
zap.String("out_trade_no", result.OutTradeNo),
zap.String("amount", cmd.Amount),
zap.String("platform", cmd.Platform),
)
// 返回支付链接和订单信息
h.responseBuilder.Success(c, result, "支付宝充值订单创建成功")
}
// TransferRecharge 管理员对公转账充值
func (h *FinanceHandler) TransferRecharge(c *gin.Context) {
var cmd commands.TransferRechargeCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
if cmd.UserID == "" {
h.responseBuilder.BadRequest(c, "缺少用户ID")
return
}
result, err := h.appService.TransferRecharge(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("对公转账充值失败",
zap.String("user_id", cmd.UserID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "对公转账充值成功")
}
// GiftRecharge 管理员赠送充值
func (h *FinanceHandler) GiftRecharge(c *gin.Context) {
var cmd commands.GiftRechargeCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
if cmd.UserID == "" {
h.responseBuilder.BadRequest(c, "缺少用户ID")
return
}
result, err := h.appService.GiftRecharge(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("赠送充值失败",
zap.String("user_id", cmd.UserID),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, err.Error())
return
}
h.responseBuilder.Success(c, result, "赠送充值成功")
}
// GetUserRechargeRecords 用户获取自己充值记录分页
func (h *FinanceHandler) GetUserRechargeRecords(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 充值类型筛选
if rechargeType := c.Query("recharge_type"); rechargeType != "" {
filters["recharge_type"] = rechargeType
}
// 状态筛选
if status := c.Query("status"); status != "" {
filters["status"] = status
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetUserRechargeRecords(c.Request.Context(), userID, filters, options)
if err != nil {
h.logger.Error("获取用户充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值记录失败")
return
}
h.responseBuilder.Success(c, result, "获取充值记录成功")
}
// GetAdminRechargeRecords 管理员获取充值记录分页
func (h *FinanceHandler) GetAdminRechargeRecords(c *gin.Context) {
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 用户ID筛选
if userID := c.Query("user_id"); userID != "" {
filters["user_id"] = userID
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 充值类型筛选
if rechargeType := c.Query("recharge_type"); rechargeType != "" {
filters["recharge_type"] = rechargeType
}
// 状态筛选
if status := c.Query("status"); status != "" {
filters["status"] = status
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.appService.GetAdminRechargeRecords(c.Request.Context(), filters, options)
if err != nil {
h.logger.Error("获取充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值记录失败")
return
}
h.responseBuilder.Success(c, result, "获取充值记录成功")
}
// GetRechargeConfig 获取充值配置
// @Summary 获取充值配置
// @Description 获取当前环境的充值配置信息(最低充值金额、最高充值金额等)
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Success 200 {object} responses.RechargeConfigResponse "获取充值配置成功"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/recharge-config [get]
func (h *FinanceHandler) GetRechargeConfig(c *gin.Context) {
result, err := h.appService.GetRechargeConfig(c.Request.Context())
if err != nil {
h.logger.Error("获取充值配置失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取充值配置失败")
return
}
h.responseBuilder.Success(c, result, "获取充值配置成功")
}
// GetAlipayOrderStatus 获取支付宝订单状态
// @Summary 获取支付宝订单状态
// @Description 获取支付宝订单的当前状态,用于轮询查询
// @Tags 钱包管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param out_trade_no query string true "商户订单号"
// @Success 200 {object} responses.AlipayOrderStatusResponse "获取订单状态成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "订单不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/finance/wallet/alipay-order-status [get]
func (h *FinanceHandler) GetAlipayOrderStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
outTradeNo := c.Query("out_trade_no")
if outTradeNo == "" {
h.responseBuilder.BadRequest(c, "缺少商户订单号")
return
}
result, err := h.appService.GetAlipayOrderStatus(c.Request.Context(), outTradeNo)
if err != nil {
h.logger.Error("获取支付宝订单状态失败",
zap.String("user_id", userID),
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
h.responseBuilder.BadRequest(c, "获取订单状态失败: "+err.Error())
return
}
h.responseBuilder.Success(c, result, "获取订单状态成功")
}