fix(claim): 统一所有买家领取链接为 H5 订单号格式

This commit is contained in:
yml2213
2026-11-02 22:11:22 +08:00
parent 3989b0a649
commit 4dc2f5ca90
38 changed files with 298 additions and 362 deletions
+1 -1
View File
@@ -97,7 +97,7 @@ your_secret_keybuyNum=1&callbackUrl=https://cb.example.com/notify/91/order&maxAm
4. 若 `productNo` 已映射到 affiliate-dash 履约商品,则创建履约任务;
5. 若 `productNo` 尚未配置,则订单进入后台“待补全”队列,由运营补齐履约配置后重试生成任务;
6. 若传入 `maxAmount`,则校验成本是否超限;
7. 履约任务生成后,生成当前项目领取链接,例如:`https://你的域名/#/claim/{token}`;
7. 履约任务生成后,生成当前项目领取链接,例如:`https://你的域名/h5/{订单号}`;
8. 首次响应返回处理中状态,由 `91卡券` 后续调用查询订单接口获取最终卡密内容。
## 10. 响应参数
+6 -6
View File
@@ -96,7 +96,7 @@ your_secret_keyorderNo=P91KS202605040001&timestamp=1777867260&version=1.0your_se
| `failReason` | string | 失败时建议返回 | 失败原因。 |
| `orderCost` | decimal(14,4) | 成功时建议返回 | 订单总成本,单位:元。 |
| `cards` | string | `orderStatus = 20` 时必须 | 卡密数据加密串。当前项目通过该字段返回领取链接。 |
| `cards[0].cardNo` | string | 成功时必须 | 最终交付的领取链接,格式为 `https://你的域名/#/claim/{token}`。 |
| `cards[0].cardNo` | string | 成功时必须 | 最终交付的领取链接,格式为 `https://你的域名/h5/{订单号}`。 |
| `cards[0].cardPwd` | string | 可选 | 当前场景固定为空字符串。 |
| `cards[0].expireTime` | string | 可选 | 领取链接过期时间,支持 `yyyy-MM-dd HH:mm:ss` 或 10 位秒级 Unix 时间戳。 |
| `cards[0].jumpLink` | string | 可选 | 与 `cardNo` 保持一致,用于兼容链接展示场景。 |
@@ -115,15 +115,15 @@ your_secret_keyorderNo=P91KS202605040001&timestamp=1777867260&version=1.0your_se
```json
[
{
"cardNo": "https://你的域名/#/claim/abc123xyz",
"cardNo": "https://你的域名/h5/6917533224570258991",
"cardPwd": "",
"expireTime": "2026-05-05 12:00:00",
"jumpLink": "https://你的域名/#/claim/abc123xyz"
"jumpLink": "https://你的域名/h5/6917533224570258991"
}
]
```
若 `buyNum > 1`,则 `cards` 原始结构中返回多个卡项,每个卡项对应一个独立领取链接。
若 `buyNum > 1`,则 `cards` 原始结构中返回多个卡项;由于对外链接按订单号生成,每个卡项会返回同一个 `/h5/{订单号}` 链接,领取页按任务顺序逐个推进。
## 12. 响应示例
@@ -168,10 +168,10 @@ your_secret_keyorderNo=P91KS202605040001&timestamp=1777867260&version=1.0your_se
```json
[
{
"cardNo": "https://你的域名/#/claim/abc123xyz",
"cardNo": "https://你的域名/h5/6917533224570258991",
"cardPwd": "",
"expireTime": "2026-05-05 12:00:00",
"jumpLink": "https://你的域名/#/claim/abc123xyz"
"jumpLink": "https://你的域名/h5/6917533224570258991"
}
]
```
+2 -2
View File
@@ -237,9 +237,9 @@ Content-Type: application/json;charset=utf-8
如果 `buyNum > 1`:
- 每个数量生成一个独立领取链接;
- 每个数量生成一个独立履约任务;
- `cards` 中放多个卡项;
- 每个 `cardNo` 对应一个独立链接。
- 每个 `cardNo` 统一返回同一个 `/h5/<订单号>` 链接,领取页按任务顺序推进。
## 9. 查询成功响应示例
+2 -2
View File
@@ -15,7 +15,7 @@
2. 我方将订单作为独立接入平台订单写入当前项目;
3. 已配置商品自动走现有 affiliate-dash 履约链路;
4. 未配置商品进入后台待补全队列,可手动补齐履约信息后重试;
5. 我方生成领取链接 `https://你的域名/#/claim/{token}`;
5. 我方生成领取链接 `https://你的域名/h5/{订单号}`;
6. `91卡券` 调用查询订单接口;
7. 我方在 `cards` 中返回领取链接,由 `91卡券` 自动发货给买家。
@@ -246,7 +246,7 @@ Node 侧实现等价为:
- 内部创建 `N` 个 task;
- 查询成功时返回 `N` 个 card;
- 每个 card 对应一个独立领取链接。
- 每个 card 对应一个独立履约任务;对外链接统一为同一个 `/h5/<订单号>`,领取页按任务顺序推进。
## 10. 商品匹配设计
@@ -74,7 +74,7 @@ kuaishou-91 进单
这里的卡密内容继续保持为我方统一领取链接:
```text
claimUrl = https://你的域名/#/claim/{token}
claimUrl = https://你的域名/h5/{订单号}
```
不要直接把 affiliate-dash 的绑定链接返回给 91。原因是领取页需要统一处理:
@@ -301,7 +301,7 @@ function RedeemApplication() {
**⚠️ `cert_link` 必须做成配置项**(`app_config` 里的 `certLink`),不要硬编码:链接一旦通过 `syncV2` 发给平台就**收不回来**(老券身上永远挂着老链接)。以后换域名时,**老域名必须继续可跳**(或用 301 兜住),否则老券全部点不开。
- 备注:现有领取链接仍是 hash 式 `https://域名/#/claim`(`CLAIM_BASE_URL` 配置)—— 两套并存,本次**不动老链路**。
- 备注:该方案落笔时领取链接仍是 hash 式 `https://域名/#/claim`;现已统一出站为 `https://域名/h5/<平台订单号>`,hash token 路由仅保留历史入站兼容。
---
+2 -2
View File
@@ -163,7 +163,7 @@ npm run douyin-mashida:preflight -- --public-url https://xxxx.ngrok-free.app --s
3. 填真实凭据 面板:uid = 抖店商户号,secret = 真实用户秘钥
(baseUrl 保持 https://dy-openplatform-api.wanmahui.com)
4. 皮肤领取页两项配置 面板:
claimLinkBase = https://<域名>/claim 皮肤订单领取页基址
claimLinkBase = https://<域名>/h5 皮肤订单领取页基址
messageTemplate = 您的订单已发货,请点击领取:{link}
⚠️ claimLinkBase 必须是**路径式**(不要带 #):
#/claim 是打手领取页,配错会把买家带到打手流程
@@ -175,7 +175,7 @@ npm run douyin-mashida:preflight -- --public-url https://xxxx.ngrok-free.app --s
POST /api/v1/open/douyin-mashida/message 收到推送
[douyin-mashida/certificate] 券码已同步 syncV2 成功
fulfillment_tasks / claim_tokens 已建任务与领取 token
8. 手机打开买家领取链接 https://<域名>/claim/<token>(首次要过 ngrok 警示页)
8. 手机打开买家领取链接 https://<域名>/h5/<平台订单号>(首次要过 ngrok 警示页)
→ 看到物品图片 → 填游戏账号 → 领取 → 本地看到 verifyV3
9. 对账 orders / order_items / redeem_certificates / fulfillment_tasks 四张表
```
@@ -38,13 +38,11 @@
## 3. 链接必须是**路径式**,且只有一个
- 领取页走**独立 BrowserRouter 分支**的 `/claim/<token>`(照 `/redeem` 的做法,见 `apps/frontend/src/App.tsx`),
**不带 `#`**;主站里 hash 形式的 `#/claim/:token` 仍保留(pathname 为 `/`,两套互不影响)。
- 链接由抖音配置的 `claimLinkBase` 拼:`claimLinkBase`(去尾斜杠)+ `/` + token。
**绝不回退** `buildClaimUrl` / `orders.claimBaseUrl`(那是 hash 的打手领取页)。
- 同一订单项若有多个履约单元(多个 token),固定取 task id 最小的那个并打 warn,
保证「同一张券每次都解析出同一条链接」。
- 老订单的券里已经写死了 `/redeem` 链接(发出去收不回来),旧页面**保留可用**。
- 对外领取链接统一走**独立 BrowserRouter 分支**的 `/h5/<平台订单号>`(见 `apps/frontend/src/App.tsx`),**不带 `#`**。
- 抖音配置了 `claimLinkBase` 时沿用其站点,未配置时使用全局 `orders.claimBaseUrl`;历史 `/#/claim`、`/claim` 基址会自动归一化到 `/h5`。
- claim token 仍按履约单元生成,但只用于判断“该订单项是否已有自动发货单元”,不再拼进对外链接。
- 同一订单有多个履约单元时共用一个订单号链接,领取页会按任务顺序推进。
- 老订单的券里已经写死的 `/redeem` 或 token 链接(发出去收不回来),旧页面**保留兼容**。
## 4. 商品图怎么来
@@ -14,7 +14,7 @@
2. 订单来源只有三个:`kuaishou-91`(91 回调 + 快手 send-code 两条通道,**同一个来源**)、`pdd`、`douyin-mashida`。代码层的来源键定义在 `apps/backend/src/services/product-match/types.ts`。
3. **核心不变式:一个订单 = 骨架(`orders` + `order_items`)+ 买家凭证(快手电子凭证 / 抖音券)+ 一个执行载体**。载体只有三种:`affiliate_dash` 自动发货、`work_orders` 工单、`manual` 人工(当前无显式表,靠「无任务且无工单」判定)。
4. 履约任务按「订单项 × 数量」拆单元:`fulfillment_tasks` 的 `UNIQUE(order_item_id, unit_index)` 是整套领取链接体系的基座。
5. 领取链接 = `claimLinkBase + '/' + claim_tokens.token`,一任务一 token(`claim_tokens.task_id` 唯一);`CLAIM_TOKEN_TTL_HOURS=0` 表示永久有效。
5. 对外领取链接统一为 `<领取站>/h5/<平台订单号>`;`claim_tokens` 仍按任务生成(`task_id` 唯一),但 token 只用于内部领取单元判定,不再拼进对外链接。
6. 打手侧是一条**独立业务线**:工单状态机 `pending_material → unassigned → open → in_progress → pending_acceptance → accepted`,另有 `problem`、`cancelled`;资金走「钱包 + 流水」模型,不是会计总账。
7. 寄卖是第三个业务面:提报 → 审核入库(`INV…`)→ 规格匹配(系统/区服/系列/钻级/璀璨)→ 履约(内部现货 or 指派打手)→ 结算与撤销押金策略。
8. 备用金台账(迁移 100)已落地:`fund_accounts`(主管池 / 客服账户)+ `fund_entries`(只追加流水,余额只能由流水变动)+ `manual_expenses` + `fund_reconciliations`。
@@ -240,8 +240,8 @@ flowchart TD
- `manual_dispatch` → `profile_key='manual_review'`,初始状态 `manual_review`;
- 未支付 → `pending_payment`。
3. 已支付订单立即 `preparePaidFulfillmentTask`(执行器注册表:`affiliate_dash` / `manual` / `legacy-retired` 只读桩)。
4. 领取 token:`createTaskClaimToken` 写 `claim_tokens`(`task_id` 唯一、`token` 唯一、`max_use_count=1`),`claimUrl = claimBaseUrl + '/' + token`。
5. 抖音走独立基址 `claimLinkBase`(后台配置),**刻意不回退** `orders.claimBaseUrl`。
4. 领取 token:`createTaskClaimToken` 写 `claim_tokens`(`task_id` 唯一、`token` 唯一、`max_use_count=1`);token 只用于内部领取单元判定,不再出现在对外链接中。
5. 所有来源对外统一生成 `claimUrl = <领取站>/h5/<平台订单号>`;历史 `/#/claim/:token`、`/claim/:token` 仅保留入站兼容。
6. `orders.claim_status`(迁移 099)= `active / paused / closed`,用于暂停或关闭整单兑换,是后台处置链接失效问题的新开关。
### 3.5 affiliate-dash 自动履约
@@ -262,8 +262,8 @@ flowchart TD
| 入口 | 路由 | 用途 |
| --- | --- | --- |
| 主站领取页 | `/#/claim/:token` | 快手渠道主路径,按 `executorKey` 渲染不同步骤 |
| H5 领取页 | `/h5/:orderNo`(BrowserRouter 分支) | 抖音等按订单号承接,`/api/v1/claim/h5/...` |
| 主站领取页(历史兼容) | `/#/claim/:token`、`/claim/:token` | 仅接收历史 token 链接,按 `executorKey` 渲染不同步骤 |
| H5 领取页(统一出站) | `/h5/:orderNo`(BrowserRouter 分支) | 所有来源按订单号承接,`/api/v1/claim/h5/...` |
| 兑换页 | `/redeem` | 抖音 DYC 券兑换/核销 |
| 补资料页 | `/collect` | 待补全订单资料收集(**仅凭订单号,无第二因子**) |