package pay import ( "context" "database/sql" "encoding/json" "fmt" "os" "strconv" "strings" "time" "ycc-server/app/main/api/internal/svc" "ycc-server/app/main/api/internal/types" "ycc-server/app/main/model" "ycc-server/common/ctxdata" "ycc-server/common/xerr" "ycc-server/pkg/lzkit/lzUtils" "github.com/google/uuid" "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 string // 订单ID,用于开发环境测试支付模式 } 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) { var paymentTypeResp *PaymentTypeResp var prepayData interface{} var orderID string // 检查是否为开发环境的测试支付模式 env := os.Getenv("ENV") isDevTestPayment := env == "development" && (req.PayMethod == "test" || req.PayMethod == "test_empty") isEmptyReportMode := env == "development" && req.PayMethod == "test_empty" 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 } case "agent_upgrade": paymentTypeResp, err = l.AgentUpgradeOrderPayment(req, session) if err != nil { return err } } // 开发环境测试支付模式:跳过实际支付流程 // 注意:订单状态更新在事务外进行,避免在事务中查询不到订单的问题 if isDevTestPayment { // 获取订单ID(从 QueryOrderPayment 返回的 orderID) if paymentTypeResp.orderID == "" { return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "开发测试模式,订单ID无效") } orderID = paymentTypeResp.orderID // 在事务中只记录订单ID,不更新订单状态 // 订单状态的更新和后续流程在事务提交后处理 logx.Infof("开发环境测试支付模式:订单 %s (ID: %s) 将在事务提交后更新状态", paymentTypeResp.outTradeNo, orderID) // 返回测试支付标识 prepayData = "test_payment_success" 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 } // 开发环境测试支付模式:事务提交后处理订单状态更新和后续流程 if isDevTestPayment && paymentTypeResp != nil && paymentTypeResp.orderID != "" { // 使用 goroutine 异步处理,确保事务已完全提交 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: %s, 错误: %v", finalOrderID, findOrderErr) return } // 更新订单状态为已支付 order.Status = "paid" now := time.Now() order.PayTime = sql.NullTime{Time: now, Valid: true} // 空报告模式:在 PaymentPlatform 字段中标记,用于后续生成空报告 if isEmptyReportMode { order.PaymentPlatform = "test_empty" logx.Infof("开发环境空报告模式:订单 %s (ID: %s) 已标记为空报告模式", paymentTypeResp.outTradeNo, finalOrderID) } // 更新订单状态(在事务外执行) updateErr := l.svcCtx.OrderModel.UpdateWithVersion(context.Background(), nil, order) if updateErr != nil { logx.Errorf("开发测试模式,更新订单状态失败,订单ID: %s, 错误: %+v", finalOrderID, updateErr) return } logx.Infof("开发环境测试支付模式:订单 %s (ID: %s) 已自动标记为已支付", paymentTypeResp.outTradeNo, finalOrderID) // 再次短暂延迟,确保订单状态更新已提交 time.Sleep(100 * time.Millisecond) // 根据订单类型处理后续流程 if strings.HasPrefix(paymentTypeResp.outTradeNo, "U_") { // 升级订单:直接执行升级操作 upgradeRecords, findUpgradeErr := l.svcCtx.AgentUpgradeModel.FindAll(context.Background(), l.svcCtx.AgentUpgradeModel.SelectBuilder(). Where("order_no = ?", paymentTypeResp.outTradeNo). Limit(1), "") if findUpgradeErr != nil || len(upgradeRecords) == 0 { logx.Errorf("开发测试模式,查找升级记录失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, findUpgradeErr) return } upgradeRecord := upgradeRecords[0] // 执行升级操作 err := l.svcCtx.AgentWalletModel.Trans(context.Background(), func(transCtx context.Context, session sqlx.Session) error { if err := l.svcCtx.AgentService.ProcessUpgrade(transCtx, upgradeRecord.AgentId, upgradeRecord.ToLevel, upgradeRecord.UpgradeType, upgradeRecord.UpgradeFee, upgradeRecord.RebateAmount, paymentTypeResp.outTradeNo, ""); err != nil { return errors.Wrapf(err, "执行升级操作失败") } // 更新升级记录状态为已完成 upgradeRecord.Status = 2 // 已完成(status: 1=待处理,2=已完成,3=已失败) upgradeRecord.Remark = lzUtils.StringToNullString("测试支付成功,升级完成") if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(transCtx, session, upgradeRecord); updateErr != nil { return errors.Wrapf(updateErr, "更新升级记录状态失败") } return nil }) if err != nil { logx.Errorf("开发测试模式,处理升级订单失败,订单号: %s, 错误: %+v", paymentTypeResp.outTradeNo, err) } else { logx.Infof("开发测试模式,代理升级成功,订单号: %s, 代理ID: %s", paymentTypeResp.outTradeNo, upgradeRecord.AgentId) } } else { // 查询订单:发送支付成功通知任务,触发后续流程(生成报告和代理处理) if sendErr := l.svcCtx.AsynqService.SendQueryTask(finalOrderID); sendErr != nil { logx.Errorf("开发测试模式,发送支付成功通知任务失败,订单ID: %s, 错误: %+v", finalOrderID, sendErr) } else { logx.Infof("开发测试模式,已发送支付成功通知任务,订单ID: %s", finalOrderID) } } }() } 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) } var agentLinkModel *model.AgentLink if data.AgentIdentifier != "" { var findAgentLinkErr error 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.SetPrice } else { amount = product.SellPrice } if user.Inside == 1 { amount = 0.01 } order := model.Order{ Id: uuid.NewString(), OrderNo: outTradeNo, UserId: userID, ProductId: product.Id, PaymentPlatform: req.PayMethod, PaymentScene: "app", Amount: amount, Status: "pending", } _, insertOrderErr := l.svcCtx.OrderModel.Insert(l.ctx, session, &order) if insertOrderErr != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 保存订单失败: %+v", insertOrderErr) } orderID := order.Id // 如果是代理推广订单,创建完整的代理订单记录 if data.AgentIdentifier != "" && agentLinkModel != nil { // 获取代理信息 agent, err := l.svcCtx.AgentModel.FindOne(l.ctx, agentLinkModel.AgentId) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询代理信息失败: %+v", err) } // 获取产品配置(必须存在) productConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, product.Id) if err != nil { if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单失败,产品配置不存在, productId: %s,请先在后台配置产品价格参数", product.Id) } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 查询产品配置失败: %+v", err) } // 获取等级加成(需要从系统配置读取) levelBonus, err := l.getLevelBonus(agent.Level) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成订单, 获取等级加成配置失败: %+v", err) } // 使用产品配置的底价计算实际底价 basePrice := productConfig.BasePrice actualBasePrice := basePrice + float64(levelBonus) // 计算提价成本(使用产品配置) priceThreshold := 0.0 priceFeeRate := 0.0 if productConfig.PriceThreshold.Valid { priceThreshold = productConfig.PriceThreshold.Float64 } if productConfig.PriceFeeRate.Valid { priceFeeRate = productConfig.PriceFeeRate.Float64 } priceCost := 0.0 if agentLinkModel.SetPrice > priceThreshold { priceCost = (agentLinkModel.SetPrice - priceThreshold) * priceFeeRate } // 计算代理收益 agentProfit := agentLinkModel.SetPrice - actualBasePrice - priceCost // 创建代理订单记录 agentOrder := model.AgentOrder{ Id: uuid.NewString(), AgentId: agentLinkModel.AgentId, OrderId: orderID, ProductId: product.Id, OrderAmount: amount, SetPrice: agentLinkModel.SetPrice, ActualBasePrice: actualBasePrice, PriceCost: priceCost, AgentProfit: agentProfit, ProcessStatus: 0, // 待处理 } _, 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 } // AgentVipOrderPayment 代理会员充值订单(已废弃,新系统使用升级功能替代) func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { // 新代理系统已废弃会员充值功能,请使用升级功能 return nil, errors.Wrapf(xerr.NewErrMsg("该功能已废弃,请使用代理升级功能"), "") } // AgentUpgradeOrderPayment 代理升级订单支付 func (l *PaymentLogic) AgentUpgradeOrderPayment(req *types.PaymentReq, session sqlx.Session) (resp *PaymentTypeResp, err error) { userID, err := ctxdata.GetUidFromCtx(l.ctx) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err) } // 1. 解析升级记录ID upgradeId := req.Id // 2. 查找升级记录 upgradeRecord, err := l.svcCtx.AgentUpgradeModel.FindOne(l.ctx, upgradeId) if err != nil { if errors.Is(err, model.ErrNotFound) { return nil, errors.Wrapf(xerr.NewErrMsg("升级记录不存在"), "") } return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询升级记录失败, %v", err) } // 3. 验证升级记录状态(必须是待支付状态) if upgradeRecord.Status != 1 { return nil, errors.Wrapf(xerr.NewErrMsg("升级记录状态不正确,无法支付"), "") } // 4. 验证代理ID是否匹配 agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err) } if agent.Id != upgradeRecord.AgentId { return nil, errors.Wrapf(xerr.NewErrMsg("无权支付此升级订单"), "") } // 5. 生成订单号(升级订单前缀 U_,限制长度不超过32) base := l.svcCtx.AlipayService.GenerateOutTradeNo() outTradeNo := "U_" + base if len(outTradeNo) > 32 { outTradeNo = outTradeNo[:32] } // 6. 获取用户信息(用于内部用户判断) user, err := l.svcCtx.UserModel.FindOne(l.ctx, userID) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户信息失败: %v", err) } // 7. 计算支付金额 amount := upgradeRecord.UpgradeFee if user.Inside == 1 { amount = 0.01 // 内部用户测试金额 } // 8. 创建订单记录 order := model.Order{ Id: uuid.NewString(), OrderNo: outTradeNo, UserId: userID, ProductId: "", // 升级订单没有产品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) } _ = orderInsertResult orderID := order.Id // 9. 更新升级记录的订单号 upgradeRecord.OrderNo = lzUtils.StringToNullString(outTradeNo) if updateErr := l.svcCtx.AgentUpgradeModel.UpdateWithVersion(l.ctx, session, upgradeRecord); updateErr != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新升级记录订单号失败: %+v", updateErr) } // 10. 生成描述信息 levelNames := map[int64]string{ 1: "普通代理", 2: "黄金代理", 3: "钻石代理", } fromLevelName := levelNames[upgradeRecord.FromLevel] toLevelName := levelNames[upgradeRecord.ToLevel] description := fmt.Sprintf("代理升级:%s → %s", fromLevelName, toLevelName) return &PaymentTypeResp{ amount: amount, outTradeNo: outTradeNo, description: description, orderID: orderID, }, nil } // getLevelBonus 获取等级加成(从配置表读取) func (l *PaymentLogic) getLevelBonus(level int64) (int64, error) { var configKey string switch level { case 1: // 普通 configKey = "level_1_bonus" case 2: // 黄金 configKey = "level_2_bonus" case 3: // 钻石 configKey = "level_3_bonus" default: return 0, nil } bonus, err := l.getConfigFloat(configKey) if err != nil { // 配置不存在时返回默认值 l.Errorf("获取等级加成配置失败, level: %d, key: %s, err: %v,使用默认值", level, configKey, err) switch level { case 1: return 6, nil case 2: return 3, nil case 3: return 0, nil } return 0, nil } return int64(bonus), nil } // getConfigFloat 获取配置值(浮点数) func (l *PaymentLogic) getConfigFloat(configKey string) (float64, error) { config, err := l.svcCtx.AgentConfigModel.FindOneByConfigKey(l.ctx, configKey) if err != nil { return 0, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取配置失败, key: %s, %v", configKey, err) } value, err := strconv.ParseFloat(config.ConfigValue, 64) if err != nil { return 0, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析配置值失败, key: %s, value: %s, %v", configKey, config.ConfigValue, err) } return value, nil }