docs(douyin-mashida): 09 重写为「一条链接闭环 + 复用 affiliate_dash」
按纠偏后的实现改写:目标流程(一条链接 → 输 UID → 二维码 → 扫码绑定 → 比对 UID → 完成)、 为什么复用 affiliate_dash 而不另建执行器、为什么未命中必须显式回落人工(漏单风险)、 为什么链接必须是路径式且只有一个(老订单的 /redeem 链接保留可用)、商品图来源与降级、 配置项表(含「保存接口必须带全量配置」的注意点)、以及下一步(真正的发货与冲正)。
This commit is contained in:
+53
-66
@@ -1,61 +1,54 @@
|
||||
# 09 - 皮肤自动发货:买家领取流程
|
||||
# 09 - 皮肤自动发货:买家领取流程(一条链接闭环)
|
||||
|
||||
> 本文档描述抖音码速达**皮肤类商品**的交付改造:从「固定兑换链接 + 用户输订单号」
|
||||
> 改为「每单一条领取链接 + 买家填游戏账号领取」。
|
||||
> 代码位置:`apps/backend/src/services/{fulfillment,claim}/`、`apps/backend/src/services/platforms/douyin-mashida/`、
|
||||
> `apps/frontend/src/pages/claim-skin/`。
|
||||
> 本文档描述抖音码速达**皮肤类商品**的交付改造。
|
||||
> 代码位置:`apps/backend/src/services/fulfillment/`、`apps/backend/src/services/claim/`、
|
||||
> `apps/backend/src/services/platforms/douyin-mashida/`、`apps/frontend/src/pages/claim/`。
|
||||
|
||||
## 1. 为什么要改
|
||||
## 1. 目标流程(与旧快手皮肤自动发货完全一致)
|
||||
|
||||
旧流程(`https://<域名>/redeem`,见文档 07)有三个毛病:
|
||||
```text
|
||||
买家付款 → 平台推送 → 建单 + 发券(cert_link = 该订单的领取链接)
|
||||
→ 按模板把【这一条链接】发给买家(order.message)
|
||||
↓
|
||||
买家打开 https://<域名>/claim/<token>
|
||||
├─ 输 UID(游戏账号 / 角色ID)
|
||||
├─ 拿到二维码 → 扫码绑定
|
||||
└─ 系统比对「绑定 UID == 填的 UID」→ 相同即发货完成,并核销平台券
|
||||
```
|
||||
|
||||
| 问题 | 说明 |
|
||||
**关键点:自始至终只有一条链接**。不再有"核销成功后再发一条去使用链接",
|
||||
买家不需要输订单号,也不需要第二个页面。
|
||||
|
||||
## 2. 交付通道:复用现成的 `affiliate_dash`
|
||||
|
||||
皮肤单**不另建执行器**,直接走仓库里已有的 `affiliate_dash` 通道(`services/fulfillment/executors/affiliate-dash-executor.ts`
|
||||
+ `services/claim/kuaishou-cloud-claim-service.ts`):它本来就实现了
|
||||
「输 UID → 建 affiliate-dash 单 → 出二维码 → 扫码绑定 → 比对 UID → 完成」。
|
||||
|
||||
抖音侧只做三件事:
|
||||
|
||||
| 事项 | 做法 |
|
||||
| --- | --- |
|
||||
| 不认人 | 链接是**同一个**,谁拿到都能打开;靠买家手输订单号来"认领" |
|
||||
| 多一步 | 买家要先去抖店订单里复制 19 位订单号,再回到页面粘贴 |
|
||||
| 不合规风险 | 任何人输入别人的订单号都能看到脱敏摘要(虽无手机号,但没必要暴露) |
|
||||
| 商品映射 | 内部 SKU(`sku_order_list[].code`,如「幸运币2个」)经权威表 `AFFILIATE_DASH_SKU_MAPPING` 转成英文 sku,产出 `affiliateDashMatch`,规划器/执行器原样复用 |
|
||||
| 分流 | 命中映射表 → `affiliate_dash`;未命中 → **显式**回落 `manual_dispatch`(接单平台/人工) |
|
||||
| 链接与通知 | `cert_link` 用领取链接;付款后按模板发一条消息 |
|
||||
|
||||
新流程:**一个订单一条链接**,链接自带 token,打开就知道是谁的单,直接领。
|
||||
> ⚠️ 未命中必须**显式**回落人工:线上/联调环境的 `unmatchedExecutorKey` 可能是空串
|
||||
> (dev 库实测就是空串),只靠全局兜底会让未命中的订单既不建任务也不进人工 = 静默漏单。
|
||||
|
||||
## 2. 新流程
|
||||
## 3. 链接必须是**路径式**,且只有一个
|
||||
|
||||
```text
|
||||
买家付款
|
||||
└─ 平台推送 trade_TradePaid → 建单 + order_items
|
||||
└─ 按【内部 SKU】分流
|
||||
├─ 命中 AFFILIATE_DASH_SKU_MAPPING(皮肤/礼包类)→ 自动发货
|
||||
│ ├─ 建履约任务(皮肤执行器,requires_claim)
|
||||
│ ├─ 生成 claim token → 领取链接 = claimLinkBase + '/' + token
|
||||
│ ├─ coupons.syncV2:cert_link = 该订单的领取链接(买家在抖店卡券里点开直达)
|
||||
│ └─ order.message:按 messageTemplate 把链接推给买家
|
||||
└─ 未命中 → manual_dispatch(接单平台/人工)
|
||||
```
|
||||
|
||||
买家侧:
|
||||
|
||||
```text
|
||||
打开 https://<域名>/claim/<token> ← 路径式,没有 #(与打手领取页 #/claim 分开)
|
||||
├─ 展示:物品图片 + 内部 SKU + 数量 + 订单号 + 商品标题
|
||||
├─ token 失效 → 友好提示,不展示表单
|
||||
├─ 待领取 → 填「游戏账号 / 角色ID」→ 二次确认 → 领取
|
||||
└─ 领取成功 → 保存账号到任务 + 调 coupons.verifyV3 核销平台券 → 成功态
|
||||
```
|
||||
|
||||
## 3. 为什么链接必须是**路径式**
|
||||
|
||||
仓库里已经有一个 `#/claim/{token}` 页面,那是**打手**用的(要走游戏 UID 绑定、领取身份校验那一套)。
|
||||
皮肤买家链接如果用 hash 形式,买家会被带进打手流程。
|
||||
|
||||
因此:
|
||||
|
||||
- 买家页走**独立 BrowserRouter 分支**(照 `/redeem` 的做法,见 `apps/frontend/src/App.tsx`)
|
||||
- 后端拼链接**只**用抖音配置的 `claimLinkBase`,**不要**经过 `buildClaimUrl()`
|
||||
(后者读全局 `orders.claimBaseUrl`,默认值是 hash 形式 `…/#/claim`)
|
||||
- 后台面板对 `claimLinkBase` 里出现 `#` 的情况会直接报错提示
|
||||
- 领取页走**独立 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` 链接(发出去收不回来),旧页面**保留可用**。
|
||||
|
||||
## 4. 商品图怎么来
|
||||
|
||||
图片按 affiliate_dash 的**英文 sku** 命名,放在前端 `apps/frontend/public/images/products/`(Vite publicDir,原样对外):
|
||||
图片按 affiliate_dash 的**英文 sku** 命名,放在前端 `apps/frontend/public/images/products/`(Vite publicDir):
|
||||
|
||||
```text
|
||||
内部 SKU(平台 sku_order_list[].code) 英文 sku(= 图片文件名)
|
||||
@@ -63,32 +56,26 @@
|
||||
套装-浪漫天命 → suit_romantic_destiny.webp
|
||||
```
|
||||
|
||||
映射复用**已有**的权威表 `services/platforms/affiliate-dash/sku-mapping.ts`(`AFFILIATE_DASH_SKU_MAPPING`),
|
||||
由 `product-image.ts` 的 `resolveProductImageUrl()` 输出 `/images/products/<英文sku>.webp`。
|
||||
映射不到返回空串,前端显示占位图(不猜、不拼不存在的文件)。
|
||||
由 `services/platforms/affiliate-dash/product-image.ts` 的 `resolveProductImageUrl()` 输出
|
||||
`/images/products/<英文sku>.webp`;映射不到返回空串。领取详情接口会把结果放在
|
||||
`product.imageUrl`,领取页渲染成居中大图,**空值或加载失败都降级为占位块**(不出现破图)。
|
||||
|
||||
单测里有一条**守卫**:映射表每个 sku 都必须有对应图片文件,新增商品漏图会直接测试失败。
|
||||
单测里有守卫:映射表每个 sku 都必须有对应图片文件,新增商品漏图会直接测试失败。
|
||||
|
||||
## 5. 配置项(后台「抖音码速达接入配置」面板)
|
||||
|
||||
| 字段 | 作用 | 留空/关闭时 |
|
||||
| --- | --- | --- |
|
||||
| `claimLinkBase` | 皮肤订单领取页基址,如 `https://<域名>/claim` | 回退到通用 `certLink` |
|
||||
| `messageTemplate` | 付款后发给买家的领取消息,支持 `{link}` `{sku}` `{orderNo}` `{productTitle}` | 用默认文案 |
|
||||
| `claimMessageEnabled` | 是否在付款后发领取消息 | 关闭则只把链接写进卡券 |
|
||||
| `claimLinkBase` | 领取页基址,如 `https://<域名>/claim` | 回退通用 `certLink` |
|
||||
| `messageTemplate` | 付款后发给买家的消息,支持 `{link}` `{sku}` `{orderNo}` `{productTitle}` | 用默认文案 |
|
||||
| `claimMessageEnabled` | 是否在付款后发这条领取消息 | 关闭则只把链接写进卡券 |
|
||||
| `certLink` | **通用兜底**兑换链接(非皮肤订单、或解析不出领取链接时用) | — |
|
||||
|
||||
## 6. 兼容性
|
||||
⚠️ 调后台保存接口必须带**全量配置**(保存是整体替换,只有 `secret` 传空表示不修改),
|
||||
脚本里要写成 `{ ...当前配置, 新字段 }`。`claimLinkBase` 带 `#` 会被前端与 preflight 判为误配。
|
||||
|
||||
- 旧订单的卡券里已经写死了旧链接,**永远指向 `/redeem`**(收不回来)→ 旧页面**保留可用**,不删。
|
||||
- 新订单才会走领取链接;`certLink` 继续作为兜底,非皮肤订单行为完全不变。
|
||||
- 买家游戏账号存在 `fulfillment_tasks.claim_submission_json`:
|
||||
**不写** `work_orders.material_json`(那是发给打手的工单素材,写进去等于泄露买家隐私)。
|
||||
## 6. 下一步(尚未实现)
|
||||
|
||||
## 7. 下一步(尚未实现)
|
||||
|
||||
领取成功后任务停在「已领取」,真正把物品发到买家账号仍需要履约:
|
||||
|
||||
- 皮肤类是否全自动发放(对接发货平台/卡密),还是先出一张人工工单;
|
||||
- 失败/退款场景要能 `coupons.cancelVerify` 冲正并把链接置回可用;
|
||||
- 接单平台类订单的领取链接沿用同一套 token + 页面,还是另有形态。
|
||||
- 领取完成后的**实际发货**(谁来把物品发到买家账号):皮肤类是否全自动、还是先出人工工单;
|
||||
- 失败/退款场景用 `coupons.cancelVerify` 冲正,并让领取链接回到可用;
|
||||
- 接单平台类订单的领取链接形态(当前只做了皮肤)。
|
||||
|
||||
Reference in New Issue
Block a user