# 订单创建逻辑优化更新说明

## 📋 更新概述

根据需求，对京东Cookie订单创建逻辑进行了进一步优化，主要包括以下三个方面的改进：

1. **刷新支付链接传递订单ID**：替换随机UUID，使用实际订单ID
2. **GetPaymentUrl支付链接刷新失败降级**：刷新失败时自动创建新京东订单
3. **移除重试次数限制**：无限重试直到没有可用Cookie为止

## 🔧 详细变更

### 1. 刷新支付链接传递订单ID

**文件**：`internal/logic/jd_cookie/order_utils.go`

**变更前**：

```go
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId string) (wxPayUrl string, err error) {
    // ...
    appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
        Cookies:     cookie.CookieValue,
        OrderId:     jdOrderIdInt,
        PayId:       payId,
        UserOrderId: utils.GenerateRandomUUID(), // 使用随机UUID
    })
    // ...
}
```

**变更后**：

```go
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId, orderId string) (wxPayUrl string, err error) {
    // ...
    appResp, err := originalJd.NewClient().RefreshPayment(ctx, &originalJd.RefreshPaymentReq{
        Cookies:     cookie.CookieValue,
        OrderId:     jdOrderIdInt,
        PayId:       payId,
        UserOrderId: orderId, // 使用实际订单ID
    })
    // ...
}
```

**改进点**：

- ✅ 传递真实订单ID，便于追踪
- ✅ 移除对 `utils.GenerateRandomUUID()` 的依赖
- ✅ 所有调用该方法的地方都需要传递orderId参数

**影响范围**：

- `order_create.go` - 订单复用时刷新支付链接
- `order_query.go` - 获取支付链接时刷新

---

### 2. GetPaymentUrl 刷新失败自动降级

**文件**：`internal/logic/jd_cookie/order_query.go`

**变更前**：

```go
func (s *sJdCookie) GetPaymentUrl(ctx context.Context, orderId string) (...) {
    // ...
    if jdOrder.WxPayExpireAt != nil && gtime.Now().After(jdOrder.WxPayExpireAt) {
        // 支付链接已过期，尝试刷新
        newWxPayUrl, err2 := s.refreshPaymentUrl(ctx, jdOrder.JdOrderId, jdOrder.PayId, jdOrder.CookieId)
        if err2 != nil {
            return "", "", "", gerror.Wrap(err, "刷新支付链接失败") // 直接返回错误
        }
        wxPayUrl = newWxPayUrl
    }
    // ...
}
```

**变更后**：

```go
func (s *sJdCookie) GetPaymentUrl(ctx context.Context, orderId string) (...) {
    // ...
    if jdOrder.WxPayExpireAt != nil && gtime.Now().After(jdOrder.WxPayExpireAt) {
        // 支付链接已过期，尝试刷新
        newWxPayUrl, refreshErr := s.refreshPaymentUrl(ctx, jdOrder.JdOrderId, jdOrder.PayId, jdOrder.CookieId, orderId)
        if refreshErr != nil {
            // 刷新失败，标记旧订单为失效
            _ = s.UpdateJdOrderStatus(ctx, jdOrder.JdOrderId, int(consts.JdOrderStatusExpired), "刷新支付链接失败")
            _ = s.RecordJdOrderHistory(ctx, jdOrder.JdOrderId, string(consts.JdOrderChangeTypeInvalid), orderId, jdOrder.WxPayUrl)
            _ = s.RecordCookieHistory(ctx, jdOrder.CookieId, string(consts.CookieChangeTypeRefreshFail), 0, 0, orderId, 0)

            // 解绑旧京东订单
            _ = s.updateJdOrderCurrentOrderId(ctx, jdOrder.JdOrderId, "")

            // 创建新的京东订单（带重试机制）
            newJdOrderId, newCookieId, newWxPayUrl, createErr := s.createNewJdOrderWithRetry(ctx, orderId, gconv.Float64(order.Amount), consts.RedeemOrderCardCategory(order.Category))
            if createErr != nil {
                return "", "", "", gerror.Wrap(createErr, "刷新失败且创建新订单失败")
            }

            // 更新订单关联的京东订单ID和支付链接
            _ = s.updateOrderJdOrderId(ctx, orderId, newJdOrderId, newWxPayUrl)

            // 更新京东订单的当前关联订单ID
            _ = s.updateJdOrderCurrentOrderId(ctx, newJdOrderId, orderId)

            // 记录Cookie使用历史
            _ = s.RecordCookieHistory(ctx, newCookieId, string(consts.CookieChangeTypeUse), 0, 0, orderId, 0)

            // 记录订单重新绑定历史
            _ = s.RecordOrderHistory(ctx, orderId, string(consts.OrderChangeTypeRebind), newJdOrderId)

            // 返回新的支付信息
            jdOrderId = newJdOrderId
            wxPayUrl = newWxPayUrl
        } else {
            // 刷新成功，更新支付链接
            wxPayUrl = newWxPayUrl
            _ = s.updateJdOrderPaymentUrl(ctx, jdOrderId, wxPayUrl)
        }
    }
    // ...
}
```

**改进点**：

- ✅ 刷新失败时不直接返回错误
- ✅ 自动标记旧订单为失效
- ✅ 自动创建新的京东订单（带重试机制）
- ✅ 更新订单关联关系
- ✅ 记录完整的操作历史（符合变更记录规范）
- ✅ 提升用户体验，保证支付链接可用

**新增辅助方法**：

```go
// updateOrderJdOrderId 更新订单关联的京东订单ID和支付链接
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error
```

**历史记录**：

- Cookie刷新失败历史
- 京东订单失效历史
- 京东订单解绑历史
- 新Cookie使用历史
- 订单重新绑定历史

---

### 3. 移除重试次数限制

**文件**：`internal/logic/jd_cookie/order_create.go`

**变更前**：

```go
func (s *sJdCookie) createNewJdOrderWithRetry(...) (jdOrderId, cookieId, wxPayUrl string, err error) {
    const maxRetryCount = 10 // 最大重试次数
    var lastErr error
    var triedCookies []string

    for retryCount := range maxRetryCount {
        // 获取可用的Cookie
        availableCookieId, cookieErr := s.GetAvailableCookie(ctx)
        if cookieErr != nil {
            glog.Warning(ctx, "获取可用Cookie失败", g.Map{
                "orderId":    orderId,
                "retryCount": retryCount,
                "error":      cookieErr,
            })
            lastErr = cookieErr
            break
        }
        
        // ... 其他逻辑
        
        glog.Info(ctx, "创建京东订单成功", g.Map{
            "orderId":    orderId,
            "jdOrderId":  jdOrderId,
            "cookieId":   availableCookieId,
            "retryCount": retryCount,
        })
        
        return jdOrderId, availableCookieId, wxPayUrl, nil
    }
    
    // 所有重试都失败了
    return "", "", "", gerror.Wrapf(lastErr, "创建京东订单失败，已尝试%d个Cookie", len(triedCookies))
}
```

**变更后**：

```go
func (s *sJdCookie) createNewJdOrderWithRetry(...) (jdOrderId, cookieId, wxPayUrl string, err error) {
    var lastErr error
    var triedCookies []string // 记录已尝试的Cookie

    // 不断尝试，直到没有Cookie为止
    for {
        // 获取可用的Cookie
        availableCookieId, cookieErr := s.GetAvailableCookie(ctx)
        if cookieErr != nil {
            glog.Warning(ctx, "获取可用Cookie失败", g.Map{
                "orderId":      orderId,
                "triedCookies": len(triedCookies),
                "error":        cookieErr,
            })
            lastErr = cookieErr
            break // 没有可用Cookie，停止重试
        }
        
        // 检查是否已经尝试过这个Cookie
        if s.hasCookieBeenTried(triedCookies, availableCookieId) {
            continue
        }
        
        // 记录已尝试的Cookie
        triedCookies = append(triedCookies, availableCookieId)
        
        // ... 其他逻辑
        
        glog.Info(ctx, "创建京东订单成功", g.Map{
            "orderId":      orderId,
            "jdOrderId":    jdOrderId,
            "cookieId":     availableCookieId,
            "triedCookies": len(triedCookies),
        })
        
        return jdOrderId, availableCookieId, wxPayUrl, nil
    }
    
    // 所有Cookie都失败了
    if lastErr == nil {
        lastErr = gerror.New(consts.ErrCodeCookieNotAvailable)
    }

    glog.Error(ctx, "创建京东订单失败，所有Cookie均不可用", g.Map{
        "orderId":      orderId,
        "triedCookies": triedCookies,
        "error":        lastErr,
    })

    return "", "", "", gerror.Wrapf(lastErr, "创建京东订单失败，已尝试%d个Cookie", len(triedCookies))
}
```

**改进点**：

- ✅ 移除 `maxRetryCount = 10` 的限制（符合重试机制设计规范）
- ✅ 使用 `for` 无限循环，直到没有可用Cookie为止
- ✅ 日志中改用 `triedCookies` 数量代替 `retryCount`
- ✅ 最大化利用所有可用Cookie
- ✅ 提升订单创建成功率

---

### 4. 解绑功能增强

**文件**：`internal/logic/jd_cookie/order_utils.go`

**变更前**：

```go
func (s *sJdCookie) updateJdOrderCurrentOrderId(ctx context.Context, jdOrderId, orderId string) error {
    // 查找订单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("订单不存在")
    }
    
    // 更新京东订单的当前关联订单ID
    _, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
        CurrentOrderId: order.Id,
    })
    // ...
}
```

**变更后**：

```go
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)
        // ...

        // 解绑：设置为null
        _, err = m.Where(dao.V1JdCookieJdOrder.Columns().JdOrderId, jdOrderId).Update(&do.V1JdCookieJdOrder{
            CurrentOrderId: nil,
        })
        
        // 记录解绑历史
        if oldJdOrder != nil && oldJdOrder.CurrentOrderId > 0 {
            _ = s.RecordJdOrderHistory(ctx, jdOrderId, string(consts.JdOrderChangeTypeUnbind), "", oldJdOrder.WxPayUrl)
        }

        return nil
    }

    // 查找订单ID对应的内部ID（绑定操作）
    // ...
}
```

**改进点**：

- ✅ 支持传入空字符串进行解绑操作
- ✅ 解绑时设置 `CurrentOrderId` 为 `nil`
- ✅ 记录解绑历史（符合变更记录规范）
- ✅ 用于 GetPaymentUrl 刷新失败时解绑旧订单

---

## 📊 影响范围总结

### 修改的文件

| 文件 | 变更内容 | 行数变化 |
|-----|---------|---------|
| `order_create.go` | 1. 调用refreshPaymentUrl时传递orderId<br>2. 移除重试次数限制 | +14/-14 |
| `order_query.go` | 刷新失败时自动创建新订单 | +38/-7 |
| `order_utils.go` | 1. refreshPaymentUrl添加orderId参数<br>2. 新增updateOrderJdOrderId方法<br>3. updateJdOrderCurrentOrderId支持解绑 | +36/-1 |

### 新增方法

```go
// updateOrderJdOrderId 更新订单关联的京东订单ID和支付链接
func (s *sJdCookie) updateOrderJdOrderId(ctx context.Context, orderId, jdOrderId, wxPayUrl string) error
```

### 方法签名变更

```go
// 变更前
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId string) (wxPayUrl string, err error)

// 变更后
func (s *sJdCookie) refreshPaymentUrl(ctx context.Context, jdOrderId, payId, cookieId, orderId string) (wxPayUrl string, err error)
```

---

## 🧪 测试结果

```bash
=== RUN   TestShouldExtractCard
--- PASS: TestShouldExtractCard (0.00s)
=== RUN   TestBatchCheckPaymentStatusConfig
--- PASS: TestBatchCheckPaymentStatusConfig (0.00s)
=== RUN   TestCacheKeyGeneration
--- PASS: TestCacheKeyGeneration (0.00s)
PASS
ok      kami/internal/logic/jd_cookie     0.875s
```

✅ **所有测试通过！**

```bash
go build -o /dev/null .
```

✅ **编译成功！**

---

## 🎯 优势分析

### 1. 提升成功率

- 无限重试直到所有Cookie用完，最大化订单创建成功率
- GetPaymentUrl刷新失败自动降级，保证用户始终能获得有效支付链接

### 2. 完善追踪

- 刷新支付链接使用真实订单ID，便于日志追踪和问题定位
- 记录完整的操作历史（刷新失败、解绑、重新绑定等）
- 符合**异常处理与记录规范**和**变更记录规范**

### 3. 用户体验

- 刷新失败时用户无感知切换到新订单
- 减少订单创建失败的概率

### 4. 系统健壮性

- 自动处理异常情况，减少人工干预
- 完整的状态流转和历史记录

---

## 📝 使用场景

### 场景1：获取支付链接时刷新失败

**流程**：

```
1. 用户请求支付链接
2. 检测到支付链接已过期
3. 尝试刷新支付链接
4. 刷新失败
   ↓
5. 标记旧京东订单为失效
6. 解绑旧京东订单
7. 创建新京东订单（无限重试）
8. 更新订单关联
9. 返回新的支付链接
```

**历史记录**：

- 旧Cookie刷新失败
- 旧京东订单失效
- 旧京东订单解绑
- 新Cookie使用
- 订单重新绑定

### 场景2：创建订单时Cookie不断失败

**流程**：

```
1. 尝试Cookie 1 → 失败 → 暂停Cookie 1
2. 尝试Cookie 2 → 失败 → 暂停Cookie 2
3. 尝试Cookie 3 → 失败 → 暂停Cookie 3
   ...
N. 尝试Cookie N → 成功 ✓
```

**日志示例**：

```
[WARN] 京东下单失败，尝试切换Cookie重试 {"orderId":"ORDER123","cookieId":"COOKIE1","triedCookies":1}
[WARN] 京东下单失败，尝试切换Cookie重试 {"orderId":"ORDER123","cookieId":"COOKIE2","triedCookies":2}
[INFO] 创建京东订单成功 {"orderId":"ORDER123","jdOrderId":"JD789","cookieId":"COOKIE3","triedCookies":3}
```

---

## ⚠️ 注意事项

1. **性能影响**：无限重试可能导致响应时间较长，建议配合超时控制使用
2. **并发控制**：多个请求同时创建订单时，注意Cookie的并发分配
3. **监控告警**：建议添加监控，当尝试Cookie数量超过阈值时告警

---

## 📚 相关文档

- [订单创建重构报告](JD_ORDER_CREATE_REFACTOR_REPORT.md)
- [订单管理README](../internal/logic/jd_cookie/README.md)
- [重构总结](ORDER_REFACTOR_SUMMARY.md)

---

**更新时间**：2025-10-11  
**更新内容**：订单创建逻辑优化  
**测试状态**：✅ 通过
