Files
tyc-server-v2/app/main/api/internal/logic/pay/paymentlogic.go
2026-01-30 11:39:28 +08:00

310 lines
12 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 pay
import (
"context"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"time"
"tyc-server/app/main/api/internal/svc"
"tyc-server/app/main/api/internal/types"
"tyc-server/app/main/model"
"tyc-server/common/ctxdata"
"tyc-server/common/xerr"
"tyc-server/pkg/lzkit/crypto"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type PaymentLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
type PaymentTypeResp struct {
amount float64
outTradeNo string
description string
orderID int64
}
func NewPaymentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PaymentLogic {
return &PaymentLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *PaymentLogic) Payment(req *types.PaymentReq) (resp *types.PaymentResp, err error) {
// 暂时关闭微信支付
if req.PayMethod == "wechat" {
return nil, errors.Wrapf(xerr.NewErrMsg("微信支付暂时关闭"), "微信支付暂时关闭")
}
var paymentTypeResp *PaymentTypeResp
var prepayData interface{}
l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
switch req.PayType {
case "agent_vip":
paymentTypeResp, err = l.AgentVipOrderPayment(req, session)
if err != nil {
return err
}
case "query":
paymentTypeResp, err = l.QueryOrderPayment(req, session)
if err != nil {
return err
}
}
// 开发环境测试支付模式:仅当 pay_method=test 时跳过实际支付,直接返回 test_payment_success
// 支付宝/微信在开发环境下仍走真实支付流程(跳转沙箱),支付成功后由回调更新订单
// 注意:订单状态更新在事务外进行,避免在事务中查询不到订单的问题
isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test"
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 {
prepayData = "test_payment_success"
logx.Infof("开发环境测试支付模式:订单 %s (ID: %d) 将在事务提交后更新状态", paymentTypeResp.outTradeNo, paymentTypeResp.orderID)
return nil
}
var createOrderErr error
if req.PayMethod == "wechat" {
prepayData, createOrderErr = l.svcCtx.WechatPayService.CreateWechatOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "alipay" {
prepayData, createOrderErr = l.svcCtx.AlipayService.CreateAlipayOrder(l.ctx, paymentTypeResp.amount, paymentTypeResp.description, paymentTypeResp.outTradeNo)
} else if req.PayMethod == "appleiap" {
prepayData = l.svcCtx.ApplePayService.GetIappayAppID(paymentTypeResp.outTradeNo)
}
if createOrderErr != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 创建支付订单失败: %+v", createOrderErr)
}
return nil
})
if err != nil {
return nil, err
}
// 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程(仅 pay_method=test 且 query 类型 orderID>0
isDevTestPayment := os.Getenv("ENV") == "development" && req.PayMethod == "test"
if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != 0 {
go func() {
time.Sleep(200 * time.Millisecond)
finalOrderID := paymentTypeResp.orderID
order, findOrderErr := l.svcCtx.OrderModel.FindOne(context.Background(), finalOrderID)
if findOrderErr != nil {
logx.Errorf("开发测试模式查找订单失败订单ID: %d, 错误: %v", finalOrderID, findOrderErr)
return
}
order.Status = "paid"
now := time.Now()
order.PayTime = sql.NullTime{Time: now, Valid: true}
// 空报告模式:在 PaymentPlatform 标记为 "test",在 paySuccessNotify.ProcessTask 中通过
// order.PaymentPlatform == "test" 识别isEmptyReportMode并生成空报告、跳过 API 调用
isEmptyReportMode := req.PayMethod == "test"
if isEmptyReportMode {
order.PaymentPlatform = "test"
logx.Infof("开发环境空报告模式:订单 %s (ID: %d) 已标记为空报告模式", paymentTypeResp.outTradeNo, finalOrderID)
}
updateErr := l.svcCtx.OrderModel.UpdateWithVersion(context.Background(), nil, order)
if updateErr != nil {
logx.Errorf("开发测试模式更新订单状态失败订单ID: %d, 错误: %v", finalOrderID, updateErr)
return
}
if enqErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); enqErr != nil {
logx.Errorf("开发测试模式入队生成报告失败订单ID: %d, 错误: %v", finalOrderID, enqErr)
}
logx.Infof("开发测试模式订单状态已更新为已支付并已入队生成报告订单ID: %d", finalOrderID)
// 再次短暂延迟,确保订单状态更新已提交
time.Sleep(100 * time.Millisecond)
}()
}
switch v := prepayData.(type) {
case string:
// 如果 prepayData 是字符串类型,直接返回
return &types.PaymentResp{PrepayId: v, OrderNo: paymentTypeResp.outTradeNo}, nil
default:
return &types.PaymentResp{PrepayData: prepayData, OrderNo: paymentTypeResp.outTradeNo}, nil
}
}
func (l *PaymentLogic) QueryOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
}
outTradeNo := req.Id
redisKey := fmt.Sprintf(types.QueryCacheKey, userID, outTradeNo)
cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey)
if cacheErr != nil {
if cacheErr == redis.Nil {
return nil, errors.Wrapf(xerr.NewErrMsg("订单已过期"), "生成订单, 缓存不存在, %+v", cacheErr)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取缓存失败, %+v", cacheErr)
}
var data types.QueryCacheLoad
err = json.Unmarshal([]byte(cache), &data)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %v", err)
}
product, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, data.Product)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 查找产品错误: %v", err)
}
var amount float64
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %v", err)
}
if data.AgentIdentifier != "" {
agentLinkModel, findAgentLinkErr := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, data.AgentIdentifier)
if findAgentLinkErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取代理订单失败: %+v", findAgentLinkErr)
}
amount = agentLinkModel.Price
} else {
amount = product.SellPrice
}
if user.Inside == 1 {
amount = 0.01
}
var orderID int64
order := model.Order{
OrderNo: outTradeNo,
UserId: userID,
ProductId: product.Id,
PaymentPlatform: req.PayMethod,
PaymentScene: "app",
Amount: amount,
Status: "pending",
}
orderInsertResult, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order)
if insertOrderErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr)
}
insertedOrderID, lastInsertIdErr := orderInsertResult.LastInsertId()
if lastInsertIdErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取保存订单ID失败: %+v", lastInsertIdErr)
}
orderID = insertedOrderID
// 更新 query_user_record 的 order_id用于关联订单见 query_user_record.sql 说明 2
qb := l.svcCtx.QueryUserRecordModel.SelectBuilder().Where(squirrel.Eq{"query_no": outTradeNo})
records, findRecErr := l.svcCtx.QueryUserRecordModel.FindAll(l.ctx, qb, "")
if findRecErr == nil && len(records) > 0 {
for _, rec := range records {
rec.OrderId = orderID
_, _ = l.svcCtx.QueryUserRecordModel.Update(l.ctx, session, rec)
}
}
if data.AgentIdentifier != "" {
agent, parsingErr := l.agentParsing(data.AgentIdentifier)
if parsingErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 解析代理标识符失败: %+v", parsingErr)
}
var agentOrder model.AgentOrder
agentOrder.OrderId = orderID
agentOrder.AgentId = agent.AgentID
_, agentOrderInsert := l.svcCtx.AgentOrderModel.Insert(l.ctx, session, &agentOrder)
if agentOrderInsert != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理订单失败: %+v", agentOrderInsert)
}
}
return &PaymentTypeResp{amount: amount, outTradeNo: outTradeNo, description: product.ProductName, orderID: orderID}, nil
}
func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) {
userID, getUidErr := ctxdata.GetUidFromCtx(l.ctx)
if getUidErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取用户信息失败, %+v", getUidErr)
}
user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取用户信息失败: %v", err)
}
// 查询用户代理信息
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
}
redisKey := fmt.Sprintf(types.AgentVipCacheKey, userID, req.Id)
cache, cacheErr := l.svcCtx.Redis.GetCtx(l.ctx, redisKey)
if cacheErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取缓存失败, %+v", cacheErr)
}
var agentVipCache types.AgentVipCache
err = json.Unmarshal([]byte(cache), &agentVipCache)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 解析缓存内容失败, %+v", err)
}
agentMembershipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, agentVipCache.Type)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取代理会员配置失败, %+v", err)
}
// 验证会员配置价格是否有效
if !agentMembershipConfig.Price.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效", agentVipCache.Type)
}
amount := agentMembershipConfig.Price.Float64
// 验证价格是否合理
if amount <= 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效: %f", agentVipCache.Type, amount)
}
// 内部用户测试金额
if user.Inside == 1 {
amount = 0.01
}
agentMembershipRechargeOrder := model.AgentMembershipRechargeOrder{
OrderNo: req.Id,
UserId: userID,
AgentId: agentModel.Id,
Amount: amount,
PaymentMethod: req.PayMethod,
LevelName: agentVipCache.Type,
Status: "pending",
}
_, err = l.svcCtx.AgentMembershipRechargeOrderModel.Insert(l.ctx, session, &agentMembershipRechargeOrder)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存代理会员充值订单失败: %+v", err)
}
return &PaymentTypeResp{amount: amount, outTradeNo: req.Id, description: fmt.Sprintf("%s代理会员充值", agentMembershipConfig.LevelName)}, nil
}
func (l *PaymentLogic) agentParsing(agentIdentifier string) (*types.AgentIdentifier, error) {
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")
if decodeErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, 获取AES密钥失败: %+v", decodeErr)
}
// Encrypt the params
encrypted, err := crypto.AesDecryptURL(agentIdentifier, key)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务, %v", err)
}
var agentIdentifierStruct types.AgentIdentifier
err = json.Unmarshal(encrypted, &agentIdentifierStruct)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询服务,反序列化失败 %v", err)
}
return &agentIdentifierStruct, nil
}