Files
kami_backend/internal/logic/jd_cookie/order_utils.go
danial f358aa0745 feat(jd-cookie): 引入用户订单号支持并重构订单创建逻辑
- 新增用户订单号字段以区分内部订单号
- 修改订单表结构添加 user_order_id 字段及索引
- 更新 CreateOrder 接口支持用户订单号参数-重构 CreateOrder 和 GetPaymentUrl 方法返回统一结果对象
- 新增模型定义用于封装订单创建与支付结果
- 调整相关逻辑方法签名与调用方式适配新结构- 优化订单创建流程增加内部订单号生成逻辑
- 完善订单查询逻辑确保正确关联用户订单号- 更新控制器层对接新版服务接口- 升级 Cookie 状态及订单状态管理枚举类型使用
2025-10-13 15:00:11 +08:00

373 lines
13 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 jd_cookie
import (
"context"
"kami/internal/consts"
"kami/internal/dao"
"kami/internal/model/do"
"kami/internal/model/entity"
"kami/utility/config"
"kami/utility/integration/originalJd"
"kami/utility/utils"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
// ====================================================================================
// 数据库查询辅助方法
// ====================================================================================
// getOrderByOrderId 根据订单号查询订单
func (s *sJdCookie) getOrderByOrderId(ctx context.Context, orderId string) (order *entity.V1JdCookieOrder, err error) {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Scan(&order)
return
}
// getOrderByUserOrderId 根据用户订单号查询订单
func (s *sJdCookie) getOrderByUserOrderId(ctx context.Context, userOrderId string) (order *entity.V1JdCookieOrder, err error) {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieOrder.Columns().UserOrderId, userOrderId).Scan(&order)
err = utils.HandleNoRowsError(err)
return
}
// getJdOrderByJdOrderId 根据京东订单号查询京东订单
func (s *sJdCookie) getJdOrderByJdOrderId(ctx context.Context, jdOrderId string) (jdOrder *entity.V1JdCookieJdOrder, err error) {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&jdOrder)
return
}
// getCookieById 根据Cookie ID获取Cookie信息
func (s *sJdCookie) getCookieById(ctx context.Context, cookieId string) (cookie *entity.V1JdCookieAccount, err error) {
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
err = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Scan(&cookie)
return
}
// ====================================================================================
// 数据库更新辅助方法
// ====================================================================================
// createJdOrderRecord 创建京东订单记录
func (s *sJdCookie) createJdOrderRecord(ctx context.Context, jdOrderId, payId, cookieId string, category consts.RedeemOrderCardCategory, amount float64, wxPayUrl string) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Insert(&do.V1JdCookieJdOrder{
JdOrderId: jdOrderId,
PayId: payId,
Amount: amount,
Category: category,
CookieId: cookieId,
Status: int(consts.JdOrderStatusPending),
WxPayUrl: wxPayUrl,
WxPayExpireAt: gtime.Now().Add(time.Minute * consts.WxPayUrlExpireDuration),
OrderExpireAt: gtime.Now().Add(time.Hour * consts.JdOrderExpireDuration),
})
return err
}
// createOrderRecord 创建订单记录
func (s *sJdCookie) createOrderRecord(ctx context.Context, internalOrderId, userOrderId string, amount float64, category consts.RedeemOrderCardCategory, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Insert(&do.V1JdCookieOrder{
OrderId: internalOrderId,
UserOrderId: userOrderId,
Amount: amount,
Category: category,
JdOrderId: jdOrderId,
Status: int(consts.OrderStatusPending),
WxPayUrl: wxPayUrl,
})
return err
}
// updateOrderLastRequest 更新订单最后请求时间
func (s *sJdCookie) updateOrderLastRequest(ctx context.Context, orderId string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Update(&do.V1JdCookieOrder{
LastRequestAt: gtime.Now(),
})
return err
}
// updateOrderJdOrderId 更新订单关联的京东订单ID和支付链接
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1())
_, err := m.Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Update(&do.V1JdCookieOrder{
JdOrderId: jdOrderId,
WxPayUrl: wxPayUrl,
})
return err
}
// updateJdOrderPaymentUrl 更新京东订单的支付链接
func (s *sJdCookie) updateJdOrderPaymentUrl(ctx context.Context, jdOrderId, wxPayUrl string) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 获取更新前的订单信息
var oldOrder *entity.V1JdCookieJdOrder
err := m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldOrder)
if err != nil || oldOrder == nil {
glog.Warning(ctx, "查询京东订单失败,无法记录历史", err)
// 即使查询失败也继续更新
}
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
WxPayUrl: wxPayUrl,
WxPayExpireAt: gtime.Now().Add(time.Minute * consts.WxPayUrlExpireDuration),
})
if err != nil {
return err
}
// 记录支付链接更新历史
if oldOrder != nil && oldOrder.WxPayUrl != wxPayUrl {
orderId := ""
if oldOrder.CurrentOrderId > 0 {
var order *entity.V1JdCookieOrder
_ = dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1JdCookieOrder.Columns().Id, oldOrder.CurrentOrderId).
Scan(&order)
if order != nil {
orderId = order.OrderId
}
}
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeReplace, orderId, wxPayUrl)
}
return err
}
// updateJdOrderCurrentOrderId 更新京东订单的当前关联订单ID
func (s *sJdCookie) updateJdOrderCurrentOrderId(ctx context.Context, jdOrderId, orderId string) error {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 如果orderId为空表示解绑
if orderId == "" {
// 获取更新前的京东订单信息
var oldJdOrder *entity.V1JdCookieJdOrder
err := m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldJdOrder)
if err != nil || oldJdOrder == nil {
glog.Warning(ctx, "查询京东订单失败,无法记录历史", err)
// 即使查询失败也继续更新
}
// 解绑设置为null
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
CurrentOrderId: nil,
})
if err != nil {
return err
}
// 记录解绑历史
if oldJdOrder != nil && oldJdOrder.CurrentOrderId > 0 {
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeUnbind, "", oldJdOrder.WxPayUrl)
}
return nil
}
// 查找订单ID对应的内部ID
var order *entity.V1JdCookieOrder
err := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).
Scan(&order)
if err != nil || order == nil {
return gerror.New("订单不存在")
}
// 获取更新前的京东订单信息
var oldJdOrder *entity.V1JdCookieJdOrder
err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Scan(&oldJdOrder)
if err != nil || oldJdOrder == nil {
glog.Warning(ctx, "查询京东订单失败,无法记录历史", err)
// 即使查询失败也继续更新
}
_, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
CurrentOrderId: order.Id,
})
if err != nil {
return err
}
// 记录订单绑定历史
var changeType consts.JdOrderChangeType
if oldJdOrder != nil {
if oldJdOrder.CurrentOrderId == 0 {
changeType = consts.JdOrderChangeTypeBind // 首次绑定
} else if oldJdOrder.CurrentOrderId != order.Id {
changeType = consts.JdOrderChangeTypeReplace // 更换绑定
}
} else {
changeType = consts.JdOrderChangeTypeBind // 首次绑定
}
// 获取京东订单的支付链接
wxPayUrl := ""
if oldJdOrder != nil {
wxPayUrl = oldJdOrder.WxPayUrl
}
_ = s.RecordJdOrderHistory(ctx, jdOrderId, changeType, orderId, wxPayUrl)
return err
}
// ====================================================================================
// 订单复用相关方法
// ====================================================================================
// findReusableJdOrder 查找可复用的京东订单
func (s *sJdCookie) findReusableJdOrder(ctx context.Context, amount float64, category consts.RedeemOrderCardCategory) (jdOrder *entity.V1JdCookieJdOrder, err error) {
m := dao.V1JdCookieJdOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 查找符合条件的京东订单:
// 1. 金额和品类相同
// 2. 状态为待支付
// 3. 没有当前关联订单或者关联订单已过期
// 4. 订单未过期
err = m.Where(dao.V1JdCookieJdOrder.Columns().Amount, amount).
Where(dao.V1JdCookieJdOrder.Columns().Category, category).
Where(dao.V1JdCookieJdOrder.Columns().Status, int(consts.JdOrderStatusPending)).
WhereNull(dao.V1JdCookieJdOrder.Columns().CurrentOrderId).
WhereGT(dao.V1JdCookieJdOrder.Columns().OrderExpireAt, gtime.Now()).
OrderAsc(dao.V1JdCookieJdOrder.Columns().CreatedAt).
Limit(1).
Scan(&jdOrder)
return
}
// ====================================================================================
// 京东接口调用方法
// ====================================================================================
// callJdCreateOrder 调用京东下单接口
func (s *sJdCookie) callJdCreateOrder(ctx context.Context, cookieId string, amount float64, category consts.RedeemOrderCardCategory) (jdOrderId, payId, wxPayUrl string, err error) {
cookie, err := s.getCookieById(ctx, cookieId)
if err != nil {
return
}
appResp, err := originalJd.NewClient().AppleRecharge(ctx, &originalJd.AppleRechargeReq{
FacePrice: amount,
Category: category,
OrderNum: utils.GenerateRandomUUID(),
Cookies: cookie.CookieValue,
})
glog.Info(ctx, "下单接口返回", appResp)
if err != nil {
return
}
if appResp.Code != consts.CardCookieJDStatusSuccess || appResp.Deeplink == "" {
return "", "", "", gerror.New("下单失败," + appResp.Msg)
}
jdOrderId = appResp.OrderId
payId = appResp.PayId
wxPayUrl = appResp.Deeplink
return
}
// refreshPaymentUrl 刷新支付链接
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId, orderId string) (wxPayUrl string, err error) {
cookie, err := s.getCookieById(ctx, cookieId)
if err != nil {
return
}
jdOrderIdInt := gconv.Int64(jdOrderId)
appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
Cookies: cookie.CookieValue,
OrderId: jdOrderIdInt,
PayId: payId,
UserOrderId: orderId,
})
glog.Info(ctx, "刷新支付链接", g.Map{
"appResp": appResp,
"cookie": cookie,
"jdOrderId": jdOrderId,
})
if err != nil {
return
}
if appResp.Code != consts.CardCookieJDStatusSuccess || appResp.Deeplink == "" {
return "", gerror.New("刷新失败," + appResp.Msg)
}
wxPayUrl = appResp.Deeplink
return
}
// callJdCheckPayment 调用京东接口检查支付状态
func (s *sJdCookie) callJdCheckPayment(ctx context.Context, jdOrderId, cookieValue string, category consts.RedeemOrderCardCategory) (*originalJd.AppleRechargeCardInfoResp, error) {
// 创建京东客户端
client := originalJd.NewClient()
// 调用检查支付状态接口
req := &originalJd.AppleRechargeCardInfoReq{
OrderId: utils.GenerateRandomUUID(),
JdOrderId: jdOrderId,
Cookies: cookieValue,
Category: category,
}
resp, err := client.GetCardInfo(ctx, req)
if err != nil {
glog.Error(ctx, "调用京东检查支付状态接口失败", g.Map{
"jdOrderId": jdOrderId,
"error": err,
})
return nil, err
}
glog.Info(ctx, "京东检查支付状态成功", resp)
return resp, nil
}
// ====================================================================================
// Cookie处理方法
// ====================================================================================
// handleCookieFailure 处理Cookie失败
func (s *sJdCookie) handleCookieFailure(ctx context.Context, cookieId, orderId string) {
// 获取当前Cookie信息
m := dao.V1JdCookieAccount.Ctx(ctx).DB(config.GetDatabaseV1())
var account *entity.V1JdCookieAccount
err := m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Scan(&account)
if err != nil || account == nil {
return
}
newFailureCount := account.FailureCount + 1
statusBefore := account.Status
var statusAfter int
if newFailureCount >= consts.JdCookieMaxFailureCount {
// 失败次数过多,标记为失效
statusAfter = int(consts.JdCookieStatusExpired)
_, _ = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Update(&do.V1JdCookieAccount{
Status: statusAfter,
FailureCount: newFailureCount,
})
} else {
// 暂停Cookie
statusAfter = int(consts.JdCookieStatusSuspend)
suspendUntil := gtime.Now().Add(time.Minute * consts.JdCookieSuspendDuration)
_, _ = m.Where(dao.V1JdCookieAccount.Columns().CookieId, cookieId).Update(&do.V1JdCookieAccount{
Status: statusAfter,
FailureCount: newFailureCount,
SuspendUntil: suspendUntil,
})
}
// 记录失败历史
_ = s.RecordCookieHistory(ctx, cookieId, consts.CookieChangeTypeFail, statusBefore, statusAfter, orderId, newFailureCount)
}