# md **Repository Path**: xhmax1018/md ## Basic Information - **Project Name**: md - **Description**: ddddddd - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-16 - **Last Updated**: 2025-11-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 订单服务端点说明文档 本文档详细说明了 SigBlock 订单服务系统的所有 API 端点,包括课程、指标、套餐、折扣、升级、续费和退款等相关接口。 ## 目录 - [1. 课程相关](#1-课程相关) - [2. 课程订单](#2-课程订单) - [3. 课程升级订单](#3-课程升级订单) - [4. 折扣相关](#4-折扣相关) - [5. 折扣订单](#5-折扣订单) - [6. 指标相关](#6-指标相关) - [7. 指标订单](#7-指标订单) - [8. 指标续费订单](#8-指标续费订单) - [9. 指标升级订单](#9-指标升级订单) - [10. 套餐相关](#10-套餐相关) - [11. 套餐订单](#11-套餐订单) - [12. 退款相关](#12-退款相关) --- ## 1. 课程相关 ### 1.1 获取所有启用的课程 **端点:** `GET /api/course/list` **描述:** 获取所有启用的课程列表(公开接口,无需认证) **认证:** 无需认证 **限流:** 30 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "课程名称", "price": 99.00, "isActive": true, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z" } ] ``` **业务说明:** - 只返回 `isActive: true` 的课程 - 按创建时间升序排列 --- ## 2. 课程订单 ### 2.1 创建课程订单 **端点:** `POST /api/course-order/create` **描述:** 创建单个课程的购买订单 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "courseId": 1 } ``` **响应示例:** ```json { "id": 1, "courseId": 1, "price": 99.00, "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 验证课程存在且启用 - 检查用户是否已拥有该课程(防止重复购买) - 课程订单不支持退款,创建后即有效 - 自动更新用户首次购买标记 - 发送订单确认邮件 **错误情况:** - 课程不存在或未启用 - 用户已拥有该课程 - 幂等性键冲突 --- ### 2.2 获取当前用户的所有课程订单 **端点:** `GET /api/course-order/list` **描述:** 获取当前用户的所有课程订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "courseId": 1, "price": 99.00, "fromPackages": null, "fromDiscount": null, "fromBundle": null, "createdAt": "2024-01-01T00:00:00.000Z", "course": { "id": 1, "name": "课程名称" } } ] ``` **业务说明:** - 返回用户的所有课程订单(包括通过套餐、折扣等方式获得的) - 按创建时间降序排列 - 包含课程关联信息 --- ### 2.3 获取用户可用的课程列表 **端点:** `GET /api/course-order/available` **描述:** 返回用户可用的课程清单(过滤已拥有的课程) **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **响应示例:** ```json [ { "id": 2, "name": "进阶课程", "price": 199.00, "isActive": true, "createdAt": "2024-01-01T00:00:00.000Z" } ] ``` **判定规则:** - 仅返回 `isActive: true` 的课程 - 过滤掉用户已拥有的课程(包括通过套餐/折扣/升级创建的课程订单;课程订单不支持退款,存在即视为拥有) - 默认按创建时间或需要的排序返回 注:若不使用该端点,前端也可通过 `GET /api/course-order/list` 在本地构建 `ownedCourseIds`,再对课程目录做差集得到可购清单。 --- --- ## 3. 课程升级订单 ### 3.1 创建课程升级订单 **端点:** `POST /api/course-upgrade-order/create` **描述:** 从单个课程升级到包含该课程的组合课程 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "orderId": 1, "bundleId": 2 } ``` **响应示例:** ```json { "id": 1, "originalOrderId": 1, "bundleId": 2, "usedDays": 5, "priceDifference": 50.00, "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 只允许从独立购买的单个课程订单升级(禁止从套餐、折扣或升级订单再次升级) - 升级时间窗口:14 天内 - 检查是否已升级过该组合包(基于邮箱和用户ID,防止重复升级) - 计算价格差:组合包价格 - 已购买课程总价 - 展开组合包:为用户还没有的课程创建新订单 - 记录用户对课程组合的升级记录(永久保存) - 发送升级确认邮件 **错误情况:** - 原始订单不存在或不属于当前用户 - 升级时间窗口已过期(超过14天) - 目标组合包不存在或未启用 - 已升级过该组合包 - 组合包中所有课程都已拥有 - 价格计算异常(价差为负或为零) --- ### 3.2 获取当前用户的所有课程升级订单 **端点:** `GET /api/course-upgrade-order/list` **描述:** 获取当前用户的所有课程升级订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "originalOrderId": 1, "bundleId": 2, "usedDays": 5, "priceDifference": 50.00, "createdAt": "2024-01-01T00:00:00.000Z", "originalOrder": { ... }, "bundle": { ... } } ] ``` **业务说明:** - 返回用户的所有课程升级订单 - 按创建时间降序排列 - 包含原始订单和组合包关联信息 --- ### 3.3 获取用户可升级的课程组合列表 **端点:** `GET /api/course-upgrade-order/available` **描述:** 返回当前用户可升级的课程组合列表 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "bundleId": 2, "canUpgrade": true, "baseOrder": { ... }, "targetBundle": { ... }, "usedDays": 5, "priceDifference": 50.00, "upgradeDeadline": "2024-01-15T00:00:00.000Z", "ownedCourses": [1], "newCourses": [2, 3] } ] ``` **业务说明:** - 服务端遍历所有启用中的课程组合,依据现有逻辑筛选可升级项 - 返回可升级的组合列表与相关信息(基础订单、目标组合、已用天数、价差、升级截止时间等) --- ## 4. 折扣相关 ### 4.1 获取所有启用的折扣 **端点:** `GET /api/discount/list` **描述:** 获取所有启用的折扣列表(公开接口,无需认证) **认证:** 无需认证 **限流:** 30 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "折扣名称", "discountType": "COURSE", "discountPrice": 49.00, "isActive": true, "startAt": "2024-01-01T00:00:00.000Z", "endAt": "2024-12-31T23:59:59.000Z", "sortOrder": 1, "course": { ... }, "indicator": { ... } } ] ``` **业务说明:** - 只返回 `isActive: true` 且在有效期内的折扣 - 按排序顺序和创建时间升序排列 - 包含关联的课程或指标信息 --- ## 5. 折扣订单 ### 5.1 创建折扣订单 **端点:** `POST /api/discount-order/create` **描述:** 创建折扣订单(支持课程折扣和指标折扣) **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "discountId": 1, "tradingViewId": "optional" // 仅指标折扣需要 } ``` **响应示例:** ```json { "id": 1, "discountId": 1, "discountType": "INDICATOR", "price": 49.00, "status": "ACTIVE", "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** **课程折扣:** - 检查用户是否已拥有该课程 - 创建课程订单(标记来自折扣) - 记录折扣购买记录(基于邮箱和用户ID) **指标折扣:** - 如果用户已有永久指标(非源代码),且折扣不是源代码指标,不允许购买 - 延续过期时间:从现有订单的过期时间延续,或从当前时间开始 - 创建指标订单(标记来自折扣) - 如果用户已有有效的指标订单(非永久),将其标记为过期 - 记录折扣购买记录(基于邮箱和用户ID) **通用:** - 检查用户是否已购买过该折扣(防止重复购买) - 自动更新用户首次购买标记 - 发送订单确认邮件 **错误情况:** - 折扣不存在、未启用或不在有效期内 - 用户已购买过该折扣 - 课程折扣:用户已拥有该课程 - 指标折扣:用户已有永久指标(非源代码)且折扣不是源代码指标 --- ### 5.2 获取当前用户的所有折扣订单 **端点:** `GET /api/discount-order/list` **描述:** 获取当前用户的所有折扣订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "discountId": 1, "discountType": "INDICATOR", "price": 49.00, "status": "ACTIVE", "createdAt": "2024-01-01T00:00:00.000Z", "discount": { ... } } ] ``` **业务说明:** - 返回用户的所有折扣订单 - 按创建时间降序排列 - 包含折扣关联信息 --- ### 5.3 获取用户可用的折扣列表 **端点:** `GET /api/discount-order/available` **描述:** 获取用户可用的折扣列表(过滤已购买和已拥有的) **认证:** 需要 JWT 认证 **限流:** 20 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "折扣名称", "discountType": "COURSE", "discountPrice": 49.00, "isActive": true, "startAt": "2024-01-01T00:00:00.000Z", "endAt": "2024-12-31T23:59:59.000Z" } ] ``` **业务说明:** - 过滤规则: 1. 折扣必须启用且在有效期内 2. 用户已购买过的折扣不显示(基于邮箱和用户ID) 3. 课程折扣:如果用户已拥有该课程,不显示 4. 指标折扣:如果用户已有永久指标,不显示任何指标折扣 --- ## 6. 指标相关 ### 6.1 获取所有启用的指标 **端点:** `GET /api/indicator/list` **描述:** 获取所有启用的指标列表(公开接口,无需认证) **认证:** 无需认证 **限流:** 30 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "指标名称", "price": 99.00, "durationDays": 30, "isPermanent": false, "isSourceCode": false, "isActive": true, "createdAt": "2024-01-01T00:00:00.000Z" } ] ``` **业务说明:** - 只返回 `isActive: true` 的指标 - 按创建时间升序排列 --- ## 7. 指标订单 ### 7.1 创建指标订单 **端点:** `POST /api/indicator-order/create` **描述:** 创建单个指标的购买订单 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "indicatorId": 1, "tradingViewId": "optional" } ``` **响应示例:** ```json { "id": 1, "indicatorId": 1, "price": 99.00, "status": "ACTIVE", "expiresAt": "2024-02-01T00:00:00.000Z", "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 验证指标存在且启用 - 检查用户是否已有活跃的指标订单(非源码指标限制): - 如果用户有生效的非永久指标订单(非源码)→ 不能新购非源码指标(只能续费),但可以新购源码指标 - 如果用户有生效的永久指标订单(非源码)→ 只能新购源码指标,不能新购非源码指标 - 计算过期时间(永久指标或源码指标没有过期时间) - 自动更新用户首次购买标记 - 发送订单确认邮件 **错误情况:** - 指标不存在或未启用 - 用户已有活跃的指标订单(非源码指标限制) - 并发问题(使用悲观锁防止) --- ### 7.2 获取当前用户的所有指标订单 **端点:** `GET /api/indicator-order/list` **描述:** 获取当前用户的所有指标订单(包括已过期的) **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "indicatorId": 1, "price": 99.00, "status": "ACTIVE", "expiresAt": "2024-02-01T00:00:00.000Z", "tradingViewId": "xxx", "createdAt": "2024-01-01T00:00:00.000Z", "indicator": { ... } } ] ``` **业务说明:** - 返回用户的所有指标订单(包括已过期的) - 实时检查并更新已过期但状态仍为 ACTIVE 的订单 - 按创建时间降序排列 - 包含指标关联信息 --- --- ## 8. 指标续费订单 ### 8.1 创建指标续费订单 **端点:** `POST /api/indicator-renewal-order/create` **描述:** 为指标订单续费,延续过期时间 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "orderId": 1, "targetIndicatorId": 2 // 可选,指定续费挡位 } ``` **响应示例:** ```json { "id": 1, "originalOrderId": 1, "indicatorId": 1, "price": 99.00, "durationDays": 30, "previousExpiry": "2024-02-01T00:00:00.000Z", "newExpiry": "2024-03-03T00:00:00.000Z", "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 验证原始订单存在且属于当前用户 - 验证订单状态为 ACTIVE 且未过期 - 验证指标是否支持续费(永久指标、源码指标或没有期限的指标不支持续费) - 计算新的过期时间:从原始订单的过期时间延续 - 将原始订单状态改为过期,并记录过期原因 - 创建新的指标订单(从原始订单的过期时间延续) - 发送续费确认邮件 **错误情况:** - 原始订单不存在或不属于当前用户 - 订单状态不是 ACTIVE - 订单已过期 - 指标不支持续费(永久指标、源码指标等) --- ### 8.2 获取当前用户的所有指标续费订单 **端点:** `GET /api/indicator-renewal-order/list` **描述:** 获取当前用户的所有指标续费订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "originalOrderId": 1, "indicatorId": 1, "price": 99.00, "durationDays": 30, "previousExpiry": "2024-02-01T00:00:00.000Z", "newExpiry": "2024-03-03T00:00:00.000Z", "createdAt": "2024-01-01T00:00:00.000Z", "originalOrder": { ... }, "indicator": { ... } } ] ``` **业务说明:** - 返回用户的所有指标续费订单 - 按创建时间降序排列 - 包含原始订单和指标关联信息 --- ## 9. 指标升级订单 ### 9.1 创建指标升级订单 **端点:** `POST /api/indicator-upgrade-order/create` **描述:** 从当前指标升级到更高档位的指标 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "orderId": 1, "targetIndicatorId": 2 } ``` **响应示例:** ```json { "id": 1, "originalOrderId": 1, "previousIndicatorId": 1, "targetIndicatorId": 2, "previousPrice": 99.00, "targetPrice": 199.00, "priceDifference": 100.00, "usedDays": 5, "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 只允许从独立购买的指标订单升级(禁止从套餐、折扣或升级订单升级) - 升级时间窗口:14 天内 - 检查用户是否已经升级过(终身只能选择一次升级方案,基于邮箱和用户ID) - 验证升级规则: - 永久指标不能升级到有时长的指标 - 永久指标不能升级到永久指标 - 如果目标档位有时长,检查已使用天数是否超过目标时长 - 计算价格差:目标价格 - 当前价格(必须大于0) - 计算新的过期时间: - 如果目标档位是永久指标,新订单没有过期时间 - 如果目标档位有时长,从当前时间开始计算,减去已使用的天数后,加上目标档位的完整时长 - 将原始订单状态改为过期,并记录过期原因 - 创建新的指标订单(使用目标指标的信息) - 记录用户对目标档位的升级记录(永久保存) - 发送升级确认邮件 **错误情况:** - 原始订单不存在或不属于当前用户 - 升级时间窗口已过期(超过14天) - 用户已经升级过(终身只能升级一次) - 原始订单的指标是源码指标 - 目标指标是源码指标 - 升级规则不满足(永久指标限制、时长限制等) - 价格差无效(目标价格不高于当前价格) --- ### 9.2 获取当前用户的所有指标升级订单 **端点:** `GET /api/indicator-upgrade-order/list` **描述:** 获取当前用户的所有指标升级订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "originalOrderId": 1, "previousIndicatorId": 1, "targetIndicatorId": 2, "previousPrice": 99.00, "targetPrice": 199.00, "priceDifference": 100.00, "usedDays": 5, "createdAt": "2024-01-01T00:00:00.000Z", "originalOrder": { ... }, "previousIndicator": { ... }, "targetIndicator": { ... } } ] ``` **业务说明:** - 返回用户的所有指标升级订单 - 按创建时间降序排列 - 包含原始订单、原始指标和目标指标关联信息 --- ### 9.3 获取用户可升级的指标列表 **端点:** `GET /api/indicator-upgrade-order/available` **描述:** 返回当前用户可升级的指标列表(以对象映射形式返回) **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json { "2": { "canUpgrade": true, "currentOrder": { ... }, "targetIndicator": { ... }, "usedDays": 5, "priceDifference": 100.00, "upgradeDeadline": "2024-01-15T00:00:00.000Z" } } ``` **业务说明:** - 服务端遍历所有启用指标,依据现有逻辑筛选可升级项 - 返回可升级指标的映射,包含升级能力、原因代码、价格差、升级截止时间等信息 --- ## 10. 套餐相关 ### 10.1 获取所有启用的新人套餐 **端点:** `GET /api/package/list` **描述:** 获取所有启用的新人套餐列表(公开接口,无需认证) **认证:** 无需认证 **限流:** 20 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "新人套餐", "price": 199.00, "discountPrice": 99.00, "isActive": true, "includeCourses": true, "includeIndicators": true, "sortOrder": 1, "courses": [ ... ], "indicators": [ ... ] } ] ``` **业务说明:** - 只返回 `isActive: true` 的套餐 - 按排序顺序和创建时间升序排列 - 包含关联的课程和指标信息 - 前端可通过 `package.includeIndicators` 判断是否需要 TradingView ID --- ### 10.2 获取用户可见的套餐 **端点:** `GET /api/package/available` **描述:** 获取用户可见的套餐(需要认证,根据用户状态判断) **认证:** 需要 JWT 认证 **限流:** 20 次/分钟 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "name": "新人套餐", "price": 199.00, "discountPrice": 99.00, "isActive": true, "includeCourses": true, "includeIndicators": true, "courses": [ ... ], "indicators": [ ... ] } ] ``` **业务说明:** - 判断用户是否为新用户(注册72小时内) - 判断用户是否已完成首购 - 只有新用户且未完成首购的用户才能看到新人套餐 - 前端可通过 `package.includeIndicators` 判断是否需要 TradingView ID --- ## 11. 套餐订单 ### 11.1 创建套餐订单 **端点:** `POST /api/package-order/create` **描述:** 创建新人套餐订单 **认证:** 需要 JWT 认证 **限流:** 5 次/分钟(中等限流) **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "packageId": 1, "tradingViewId": "optional" } ``` **响应示例:** ```json { "id": 1, "packageId": 1, "price": 99.00, "status": "ACTIVE", "createdAt": "2024-01-01T00:00:00.000Z" } ``` **业务说明:** - 验证用户是否符合新人套餐条件(注册72小时内且未完成首购) - 验证套餐存在且启用 - 检查用户是否已购买过该套餐(基于邮箱和用户ID) - 检查用户是否已有永久指标(非源代码),如果套餐包含非源代码指标,不允许购买 - 展开套餐: - 为所有课程创建订单(不检查是否已拥有,使用唯一约束错误处理防止重复) - 为所有指标创建订单 - 如果用户已有活跃的指标订单(非源代码),且套餐包含非源代码指标,将其标记为过期 - 记录套餐购买记录(基于邮箱) - 更新用户首次购买标记 - 发送订单确认邮件 **错误情况:** - 用户不符合新人套餐条件(不是新用户或已完成首购) - 套餐不存在或未启用 - 用户已购买过该套餐 - 用户已有永久指标(非源代码)且套餐包含非源代码指标 --- ### 11.2 获取当前用户的所有套餐订单 **端点:** `GET /api/package-order/list` **描述:** 获取当前用户的所有套餐订单 **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求参数:** 无 **响应示例:** ```json [ { "id": 1, "userId": 1, "packageId": 1, "price": 99.00, "status": "ACTIVE", "tradingViewId": "xxx", "createdAt": "2024-01-01T00:00:00.000Z", "package": { ... } } ] ``` **业务说明:** - 返回用户的所有套餐订单 - 按创建时间降序排列 - 包含套餐关联信息 --- ## 12. 退款相关 ### 12.1 申请退款 **端点:** `POST /api/refund` **描述:** 申请退款(支持指标订单、套餐订单、折扣订单) **认证:** 需要 JWT 认证 **幂等性:** 需要 `Idempotency-Key` 请求头(UUID v4) **请求头:** ``` Authorization: Bearer Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 ``` **请求体:** ```json { "orderType": "indicator_order", "orderId": 1, "refundAmount": 99.00, "refundReason": "不需要了" } ``` **响应示例:** ```json { "message": "退款申请已提交" } ``` **业务说明:** **指标订单退款:** - 检查订单是否有来源(优惠方案)- 如果有,不允许单独退款 - 检查退款有效期(7天内,包含第7天) - 检查指标是否为源代码类型(源代码指标不支持退款) - 检查订单是否允许退款(是否有后续操作:续费、升级、折扣) - 验证退款金额(必须等于订单价格) - 将当前订单状态改为 CANCELLED - 查找并恢复历史订单(如果存在且未自然过期): - 通过续费订单创建:恢复原始订单 - 通过升级订单创建:恢复原始订单 - 通过折扣订单创建:如果有 originalOrderId,恢复原始订单 - 创建退款记录 **套餐订单退款:** - 检查退款有效期(7天内,包含第7天) - 检查套餐展开的指标订单关联的指标是否为源代码类型 - 检查套餐订单是否允许退款(是否有后续操作) - 计算退款金额:套餐价格 - 课程原价总和 - 验证退款金额 - 将套餐订单状态改为 CANCELLED - 取消套餐展开的指标订单 - 查找并恢复历史订单(如果存在且未自然过期) - 创建退款记录 **折扣订单退款:** - 检查退款有效期(7天内,包含第7天) - 检查折扣类型(课程折扣不支持退款) - 如果是指标折扣,检查指标是否为源代码类型 - 检查订单是否允许退款(是否有后续操作) - 验证退款金额(必须等于折扣价格) - 将折扣订单状态改为 CANCELLED - 如果是指标折扣,取消对应的指标订单 - 查找并恢复历史订单(如果存在且未自然过期) - 创建退款记录 **错误情况:** - 订单不存在或不属于当前用户 - 退款有效期已过期(超过7天) - 源代码指标不支持退款 - 订单有后续操作(续费、升级、折扣) - 退款金额不正确 - 指标订单有来源(优惠方案),需要退款对应的优惠方案订单 --- ### 12.2 批量检查订单是否可以退款 **端点:** `POST /api/refund/check-refundability` **描述:** 批量检查订单是否可以退款(支持单个或多个订单) **认证:** 需要 JWT 认证 **限流:** 无特殊限流 **请求体:** ```json { "indicatorOrderIds": [1, 2, 3], "packageOrderIds": [4, 5], "discountOrderIds": [6] } ``` **响应示例:** ```json { "indicator_order_1": { "orderType": "indicator_order", "orderId": 1, "price": 99.00, "createdAt": "2024-01-01T00:00:00.000Z", "canRefund": true, "refundAmount": 99.00 }, "indicator_order_2": { "orderType": "indicator_order", "orderId": 2, "price": 99.00, "createdAt": "2024-01-01T00:00:00.000Z", "canRefund": false, "reasonCode": "REFUND_EXPIRED" }, "package_order_4": { "orderType": "package_order", "orderId": 4, "price": 199.00, "createdAt": "2024-01-01T00:00:00.000Z", "canRefund": true, "refundAmount": 100.00 } } ``` **业务说明:** - 支持批量检查多个订单(指标订单、套餐订单、折扣订单) - 返回每个订单的退款能力、原因代码、退款金额等信息 - 检查退款有效期、源代码限制、后续操作等条件 - 套餐订单的退款金额 = 套餐价格 - 课程原价总和 --- ## 通用说明 ### 认证要求 - 大部分订单相关接口需要 JWT 认证 - 公开接口(如获取课程列表、指标列表等)无需认证 - 认证方式:在请求头中携带 `Authorization: Bearer ` ### 幂等性保护 - 所有创建订单的接口都支持幂等性保护 - 客户端必须在请求头中携带 `Idempotency-Key`(UUID v4 格式) - 相同的 `Idempotency-Key` 在24小时内只会处理一次 ### 限流策略 - **默认限流:** 30 次/分钟(公开接口) - **中等限流:** 5 次/分钟(订单创建接口) - **查询限流:** 20 次/分钟(部分查询接口) ### 错误处理 - 所有错误都遵循统一的错误响应格式 - 错误消息支持多语言(通过 `Accept-Language` 请求头) - 常见错误码: - `ORDER_NOT_FOUND`: 订单不存在 - `REFUND_EXPIRED`: 退款有效期已过期 - `SOURCE_CODE_NOT_SUPPORTED`: 源代码指标不支持退款 - `HAS_SUBSEQUENT_ORDER`: 订单有后续操作,不允许退款 - `UPGRADE_WINDOW_EXPIRED`: 升级时间窗口已过期 - `INDICATOR_UPGRADE_ALREADY_USED`: 用户已经升级过(终身只能升级一次) ### 业务规则 1. **课程订单:** 不支持退款,订单存在即表示用户拥有该课程 2. **指标订单:** 支持退款(7天内),但源代码指标不支持退款 3. **套餐订单:** 支持退款(7天内),退款金额 = 套餐价格 - 课程原价总和 4. **折扣订单:** 指标折扣支持退款(7天内),课程折扣不支持退款 5. **升级限制:** 用户终身只能选择一次升级方案(基于邮箱和用户ID) 6. **升级时间窗口:** 14 天内可以升级 7. **退款时间窗口:** 7 天内可以退款(包含第7天) ### 邮件通知 - 所有订单创建、升级、续费操作都会发送确认邮件 - 邮件发送失败不会影响订单创建,但会记录错误日志 ### 缓存策略 - 用户升级记录:24 小时缓存 - 用户已购买折扣列表:5 分钟缓存 - 用户已拥有课程列表:5 分钟缓存 - 指标信息:1 小时缓存 --- ## 更新日志 - **2025-11-16**: 初始版本,包含所有订单服务端点说明