Files
order_site/docs/ affiliate_dash_发货平台/affiliate-dash对接.md
T
yml2213 4e9c416235 affiliate-dash 领取界面优化(合并多次迭代为一次提交)
- 第 2 步绑定页:移除金额/单号/商品信息区,移除标题上方 affiliate-dash 标签
- 二维码区参考旧设计:左右竖排红字 Q区用Q扫/V区用V扫,去浅蓝背景,放大至 220px
- 第 4 步结果页:废弃 canvas 生成发货成功图,改用旧版结果卡片(绿勾+兑换成功+信息网格+商品明细)
- 开发 Mock:新增 affiliate-dash 领取五步 mock(uid/bind/submitted/completed/failed,不调真实平台)
2026-08-05 19:39:15 +08:00

55 KiB
Raw Blame History

affiliate_dash 商户接入对接文档(方案 B:统一领取页深度集成)

文档对象:order_site 后端 / 前端对接开发 版本:v1(评审稿) 对接方向:order_site 作为 affiliate_dash 的一个商户,走商户开放接口 /api/client/v1,由 affiliate_dash 完成皮肤/道具履约,order_site 保持自己的统一领取页并自建发货交互。


0. 一句话结论

order_site 在 affiliate_dash 开通一个商户账号 + API 客户端,下单时调用 POST /api/client/v1/orders 幂等建单扣款,领取页通过 delivery 系列接口(查询 → 绑定 → 轮询 → 提交)在本站页面内完成发货交互,最终由 affiliate_dash 回调 order.shipping.updated 驱动 order_site 任务状态与快手电子凭证核销闭环。


1. 角色与职责边界

承担 不承担
order_site 91 进单、商品匹配、履约任务、统一领取页、玩家信息收集、回调接收、电子凭证核销、人工兜底 不直接对接皮肤源头;不管理 affiliate_dash 钱包
affiliate_dash 商户建单扣款、发货数据查询、绑定、对源头履约、回调推送 不接触 91 卡券,不生成 order_site 的 claimUrl

对接方式与现有 kuaishou-feifei 执行器同构:order_site 新增一个 affiliate_dash executor,复用一个 preparePaidTask + resolveDeliveryLink,对外仍然只返回本站统一领取链接。


2. 对接前准备(affiliate_dash 侧,管理员手动完成)

说明
商户账号 affiliate_dash 后台新建商户(如 order_site),状态 active
API 客户端 商户后台「API 密钥」新建,scopes 至少含 products:readorders:readorders:writeshipping:readwallet:read;保存 app_key / secretsecret 仅创建时展示一次)
回调配置 商户后台「回调」配置接收 URL(指向 order_site 回调路由),订阅事件 order.createdorder.shipping.updatedorder.cancelled,保存回调 secret
钱包充值 affiliate_dash 无真实支付,下单即扣商户钱包积分;上线前需充值足够积分
环境 记录 BASE_URL(如 https://affiliate.example.com),确认网络可达;时间偏差容差 ±300 秒,服务器需 NTP 同步

3. 鉴权与签名

3.1 请求签名(order_site → affiliate_dash /api/client/v1

Header

Header 必填 说明
X-App-Key 商户 API 客户端 app_key
X-Timestamp Unix 秒时间戳
X-Nonce 随机串 8~96 位;同 Key 有效期内不可重复(服务端持久化防重放)
X-Sign 见下

签名字符串(body 先 sha256,与源头侧 /api/open/v1 的原始 body 拼串不同,勿混用;参数名是 app_key 而非旧接口的 api_key,写错会 401):

app_key=<app_key>&body_sha256=<sha256hex(body)>&method=<GET|POST>&nonce=<nonce>&path=<仅路径>&timestamp=<unix秒>

规则:

  1. 参数按 ASCII 字典序排序(固定为上面顺序);
  2. value 原样拼接不做 URL encodeGET 请求 body 为空字符串(body_sha256 为空串的 sha256);
  3. X-Sign = hex(HMAC-SHA256(secret, 签名字符串)),小写。

TypeScript 参考:

import { createHmac, createHash } from 'node:crypto'

function sha256Hex(input: string): string {
  return createHash('sha256').update(input, 'utf8').digest('hex')
}

function hmacSha256Hex(secret: string, content: string): string {
  return createHmac('sha256', secret).update(content, 'utf8').digest('hex')
}

export function buildClientSign(params: {
  appKey: string
  appSecret: string
  method: string
  path: string
  body: string
  timestamp: string
  nonce: string
}): string {
  const content = [
    `app_key=${params.appKey}`,
    `body_sha256=${sha256Hex(params.body)}`,
    `method=${params.method.toUpperCase()}`,
    `nonce=${params.nonce}`,
    `path=${params.path}`,
    `timestamp=${params.timestamp}`,
  ].sort().join('&')
  return hmacSha256Hex(params.appSecret, content)
}

3.2 回调验签(affiliate_dash → order_site

HeaderX-Event-IDX-TimestampX-Sign

content = body_sha256=<sha256hex(原始body)>&timestamp=<X-Timestamp>
X-Sign  = hex(HMAC-SHA256(回调secret, content))

校验顺序:X-Timestamp 在 ±300 秒内 → 按 X-Event-ID 幂等去重 → 重算签名比对(timingSafeEqual)→ 处理业务。校验失败返回 4xx,成功尽快返回 2xxaffiliate_dash 会重试最多 16 次,间隔为固定递增序列 15s/15s/30s/3m/10m/20m/30m/30m/30m/1h/3h/3h/3h/6h/6h,非严格指数退避)。


4. 商品映射

  • 拉取:GET /api/client/v1/products(分页 page/size),关键字段:
字段 说明
list[].sku 下单时作为 sku 传入
list[].display_name 商户展示名
list[].price_amount 单价(积分)
list[].stock 库存,-1 不限
list[].status active 可售
  • order_site 侧在履约配置(fulfillment_profiles.config_json)维护映射:91 productNo / order_site 商品 → affiliate_dash sku
  • 建议:admin 配置页支持手动关联 + 定时同步(商品下架/缺货在领取前拦截,转人工)。

5. 完整时序(方案 B

91 卡券下单
  └→ order_site 建内部订单 + 履约 taskexecutor_key = affiliate_dash
       └→ preparePaidTask
            POST /api/client/v1/orders
              { client_order_no: <order_site 内部单号>, sku, quantity, buyer_reference, data: { 91单号, 预期游戏账号 } }
            → affiliate_dash 扣钱包积分,建单(order_no, order_status=paid
            → order_site 从 201 响应**同步**拿到 order_no,写入 task 上下文,task → link_generated
            → 异步回调 order.created 仅作对账/补发(幂等,不改变 task 状态)
       └→ resolveDeliveryLink:返回本站统一 claimUrl
91 把 claimUrl 发给用户
用户打开 order_site 领取页(executor_key=affiliate_dash 分支)
  └→ 展示商品/预期账号
  └→ 用户输入/确认游戏 UID → POST /orders/{order_no}/delivery/bind
       → 展示 bind_url / qr_url(或跳转绑定)
  └→ 轮询 GET /orders/{order_no}/delivery/bind-result2~3 秒)
       → bound=true(含 mismatch 提示)→ 展示角色信息
  └→ POST /orders/{order_no}/delivery/submit { game_account, bind_uuid }
       → affiliate_dash 进入 delivering,内部对源头发货
affiliate_dash 履约完成(delivered / ship_failed
  └→ 回调 order.shipping.updated
       → order_site 验签 + 幂等 → 更新 task 状态
       → deliveredtask → redeemed/completed,触发快手电子凭证核销
       → ship_failedtask → retry_pending/manual_review,展示失败原因

6. 接口明细(order_site 实际使用的端点)

6.1 创建订单 POST /api/client/v1/ordersscope orders:write

body 类型 必填 说明
client_order_no string 幂等单号,同一商户下唯一;建议用 order_site 内部单号
sku string affiliate_dash 商品 sku
quantity int 默认 1
buyer_reference string 买家标识/备注(可填 91 单号或用户标识)
data object 透传业务数据(≤2048B);约定字段 game_account/game_channel/role_name 会用于发货处理;回调原样带回

响应 data.orderorder_noclient_order_noorder_status=paid)、can_shipcannot_ship_reasonamountbase_amountservice_fee_amountcurrency 等。

注意:

  • 成功创建 HTTP 201;命中幂等返回 200idempotent=true + 既有订单,不重复扣款
  • 无真实支付,下单即扣钱包积分,余额不足返回 400 —— order_site 必须处理该降级;
  • 下单后以 order_no 为准进行后续查询/发货/回调匹配。

6.2 查询订单 GET /api/client/v1/orders/{order_no}orders:read

返回订单当前状态、金额、失败原因等(⚠️ 实际响应不含「发货链接有效期」字段,有效期仅在 delivery-link 接口返回);用于 order_site 主动对账/兜底轮询。

6.3 获取发货链接 GET /api/client/v1/orders/{order_no}/delivery-linkorders:read/shipping:read

返回 delivery_url(可直接打开的 affiliate_dash Web 发货页)。方案 B 下作为备选(如自建页临时不可用时直接跳转)。

6.4 查询发货数据 GET /api/client/v1/orders/{order_no}/deliveryorders:read/shipping:read

自建发货页的渲染数据:statuscan_shipcannot_ship_reasonproductbuyer_namegame_channelgame_uidrole_namepay_scoredata(下单透传,用于预填玩家账号)、good(商品展示详情)。

6.5 发起绑定 POST /api/client/v1/orders/{order_no}/delivery/bindorders:write

body 类型 必填 说明
game_account string 玩家编号 / UID

返回 bind_uuid(提交发货凭证)、bind_url(绑定跳转地址)、qr_url(绑定二维码)。同一订单重复绑定以最新凭证为准。

6.6 查询绑定结果 GET /api/client/v1/orders/{order_no}/delivery/bind-result?bind_uuid=..orders:read/shipping:read

字段 说明
bound 是否完成绑定;未绑定 false,自建页每 2~3 秒轮询
game_account / role_name / game_channel 绑定成功后返回
expected_game_account 下单 data.game_account 透传的预期账号(有传才返回)
mismatch 绑定账号与预期不一致时为 true提交发货会被拒绝

6.7 提交发货 POST /api/client/v1/orders/{order_no}/delivery/submitorders:write

body 类型 必填 说明
game_account string 玩家编号 / UID
bind_uuid string 绑定凭证

返回 order_nostatus=delivering)、messageprovider_order_no。同一订单重复提交直接返回既有结果。最终成败以回调为准

6.8 余额查询 GET /api/client/v1/walletwallet:read

可选:下单前预检余额,或在订单创建 400 时告警。返回 available_balancecurrency 等。


7. 回调契约

事件 触发时机
order.created 商户下单成功(扣款完成,order_status=paid
order.shipping.updated 发货状态变化(delivering / delivered / ship_failed
order.cancelled 订单取消/退款

payload(统一结构):

{
  "event_id": "…",
  "event": "order.shipping.updated",
  "occurred_at": "…",  // 真实实现含此字段
  "data": {
    "order_no": "FO20260730000123",
    "client_order_no": "shop-10001",
    "product_sku": "suit_pink_sheep",
    "quantity": 1,
    "base_amount": 100,
    "service_fee_amount": 1,
    "amount": 101,
    "currency": "POINT",
    "order_status": "delivered",
    "can_ship": false,
    "cannot_ship_reason": "",
    "provider_order_no": "…",
    "failure_reason": "",
    "data": { "91单号": "…", "game_account": "4808146277" }
  }
}

order_site 处理要求:

  1. 验签(见 3.2);
  2. X-Event-ID / event_id 幂等(DB 唯一键或去重表);
  3. order_status → task 状态迁移(见下);
  4. 尽快返回 2xx;失败重试可能重复投递(最多 16 次指数退避)。

8. 状态映射

affiliate_dash order_status order_site task 状态 / 动作
paid 已建单待履约(task link_generated,领取页可开始发货)
delivering 履约中(task redeeming,领取页展示处理中)
delivered 履约成功(task → redeemed / completed;触发快手电子凭证核销)
ship_failed 履约失败(task → retry_pending;可重新 delivery/submit 或转 manual_review
cancelled 已取消/退款(task → closed,通知 91/用户)

⚠️ 状态机合法性(对照 apps/backend/src/domain/task-status.tsTASK_TRANSITIONS):

  • affiliate_dash 流程中 task 须经过 link_generated → claimed → waiting_binding → redeeming 才能进入履约态:link_generated/claimed 不能直接跳 redeeming(不在转移表)。落地时:领取页打开置 claimed、提交绑定置 waiting_bindingdelivery/submit 成功或收到 delivering 回调置 redeeming
  • redeeming → redeemed/completed/retry_pending/manual_review 均合法;redeeming → closed 不在转移表(updateTask 实际不校验转移表,属软约束,仍建议先经 manual_reviewclosed)。
  • retry_pending/manual_review → redeeming 合法,支持 ship_failed 重试闭环。

9. 错误与降级

场景 affiliate_dash 返回 order_site 处理
钱包余额不足 创建订单 400 task → manual_review + 告警「affiliate_dash 余额不足」,充值后重试
商品下架/缺货 创建订单 400 匹配阶段拦截,转人工
下单超时/网络失败 无响应 用同 client_order_no 幂等重试;仍失败转 manual_dispatch
绑定账号不匹配 mismatch=truesubmit 被拒 领取页提示买家绑回预期账号或重新下单
ship_failed 回调 failure_reason 领取页/后台展示原因,支持重试或转人工

10. 幂等与一致性设计

  • 下单幂等client_order_no = order_site 的 task_no(格式 DT + 12 hex,唯一且稳定,见 §14.2),重复请求安全;
  • 回调幂等X-Event-ID 去重;
  • 提交发货幂等:重复 delivery/submit 返回既有结果;
  • 对账兜底order_site 定时用 GET /orders/{order_no} 对账(可选,回调为主);
  • 时间:双方服务器 NTP 同步,容差 ±300 秒。

11. order_site 配置清单(履约配置扩展)

fulfillment_profiles 新增 profile

{
  "profile_key": "affiliate_dash",
  "name": "affiliate_dash 履约",
  "executor_key": "affiliate_dash",
  "requires_claim": true,
  "auto_dispatch": false,
  "config_json": {
    "baseUrl": "https://affiliate.example.com",
    "appKey": "ak_…",
    "appSecret": "sk_…",
    "callbackSecret": "…",
    "skuMapping": { "91商品编码": "affiliate_dash sku" },
    "fallbackExecutor": "manual_dispatch",
    "precheckBalance": true
  }
}

商品匹配规则(复用 fulfillment-routing.ts):91 productNo → 命中 affiliate_dash 映射 → executor_key = affiliate_dash;未命中走现有 kuaishou / manual 流程。


12. 对接计划(分阶段)

阶段 内容 交付物 验收点
0. affiliate_dash 侧准备(手动) 建商户、API 客户端、回调配置、充值 商户可登录、app_key/secret 可用 用 curl 调 GET /products
1. 平台客户端与签名 services/platforms/affiliate-dash/:签名、请求封装、验签 client.tsverify-callback.ts 单元测试:签名向量、验签通过/篡改拒绝
2. executor affiliate-dash-executor.tspreparePaidTask(建单+存 order_no+ resolveDeliveryLink(统一 claimUrl);注册进 registry.ts executor + 注册 测试订单:91 进单 → task 状态 link_generatedaffiliate_dash 出现订单
3. 履约配置与商品映射 新增 profile、admin 配置页(sku 映射/余额预检) 配置页 + 迁移 配置页可保存,路由规则生效
4. 领取页(方案 B 自建) 前端按 executor 分发到 affiliate-dash 流程:delivery 查数据 → 输入 UID → bind → 轮询 bind-resultsubmit;异常分支(mismatch/失败重试) 领取页分支 端到端:用户领取 → 绑定 → 提交 → affiliate_dash 显示 delivering
5. 回调路由与状态同步 POST /webhooks/affiliate-dash:验签 + 幂等 + task 状态迁移 + 核销触发 回调路由 发货完成回调 → task redeemed + 电子凭证核销闭环
6. 联调与上线 错误/降级演练、对账、监控告警 上线 checklist 全链路绿灯,余额不足/绑定不匹配演练通过

13. 参考代码位置

  • order_site executor 模式:apps/backend/src/services/fulfillment/executors/kuaishou-feifei-executor.ts(最接近,同为「建单 + 统一领取链接」)、registry.ts(注册)、types.ts(接口定义)
  • order_site 平台对接样例:apps/backend/src/services/platforms/kuaishou-feifei/
  • order_site 履约配置:apps/backend/src/repositories/fulfillment-profile-repo.tsapps/backend/src/routes/admin/platform-config/fulfillment-routing.ts
  • affiliate_dash 接口契约(在线文档/字段定义):frontend/src/openapi/endpoints.ts;鉴权实现:backend/internal/middleware/open_auth.go(商户侧)、backend/internal/service/callback.go(回调签名 BuildCallbackSign

14. 深度分析(契约验证结论 · v1 → v2)

本节基于对 affiliate_dash 实际代码(backend/internal/middleware/open_auth.gobackend/internal/service/callback.gobackend/internal/handler/open_v1.gobackend/internal/service/delivery.gofrontend/src/openapi/endpoints.ts 等)与 order_site 履约体系(domain/task-status.tsservices/fulfillment/executors/*services/claim/*)的逐项核对。以下结论是阶段 1~6 实现的直接依据。

14.1 契约验证对照(文档 vs affiliate_dash 实际实现)

# 契约项 结论
1 签名串参数名:文档原写 api_key=,实现为 app_key=open_auth.go:194 BuildOpenV1Sign;前端在线文档 OpenApiDocs.tsx:425-449 与调试页 ApiDebugger.tsx:65 均用 app_key= 已在本版修正;按旧写法签名必然 401
2 body 先 sha256、参数字典序、四头、nonce 持久化防重放(长度 8~96)、±300sOPEN_SIGN_SKEW=300)、scopesproducts:read/orders:read/orders:write/shipping:read/wallet:read 一致
3 回调 content=body_sha256=<..>&timestamp=<..>、头 X-Event-ID/X-Timestamp/X-Sign、事件枚举 order.created/order.shipping.updated/order.cancelled 一致
4 回调重试 16 次 ⚠️ 次数一致(CALLBACK_MAX_ATTEMPTS=16),但为固定递增间隔序列而非严格指数退避(callback.go:30-46
5 POST /orders 字段、新建 201 / 幂等 200 + idempotent=true、余额不足 400、order 响应字段(order_no/order_status/can_ship/cannot_ship_reason/amount/base_amount/service_fee_amount/currency + client_order_no/product/quantity/fee_type/buyer_reference/provider_order_no/failure_reason/data/result/created_at/delivered_at/cancelled_at 一致
6 GET /products 字段与分页 page/sizestatus 恒为 active);GET /orders/{order_no}delivery-link → delivery_url/expires_atdelivery → status/can_ship/cannot_ship_reason/product/buyer_name/game_channel/game_uid/role_name/pay_score/data/goodbind → bind_uuid/bind_url/qr_urlbind-result → bound/game_account/role_name/game_channel/expected_game_account/mismatchsubmit → order_no/status/message/provider_order_nowallet → available_balance/currency 一致(唯一修正:查询订单响应不含「发货链接有效期」,见 §6.2)
7 回调 payload 字段 + 顶层 occurred_at(真实存在,§7 已补) 一致
8 order_status 五枚举 paid/delivering/delivered/ship_failed/cancelledmismatch 提交被拒为 HTTP 400 一致(语义细节见下)

mismatch 语义细节bind-result.mismatch=true 判定的是「绑定账号 ≠ 下单 data.game_account(预期账号)」(delivery.go:227-233);submit 的拒绝条件是「绑定账号 ≠ 请求体提交的 game_account」(delivery.go:354-357)。正常流程提交预期账号时两者等价;若页面让用户提交的是绑定账号本身,则 mismatch=true 时也能提交通过 —— 对账/风控时留意。

14.2 order_site 落地要点

  • client_order_no 用 task_noutils/random.ts randomId('DT', 6)DT + 12 hex,≤64 字符满足 affiliate_dash 限制)。order_site 自身无 client_order_no 字段,本地下单幂等由 orders UNIQUE(provider, platform, shop_id, platform_order_id) + listTasks(order.id) 已建不重建承担;affiliate_dash 侧幂等由 client_order_no=task_no 承担,preparePaidTask 失败重试可安全重放同一 task_no。
  • preparePaidTask 同步拿到 order_no201 响应即含),写入 context_json.affiliateDash.orderNo;无需等回调。失败必须降级 MANUAL_REVIEW + deps.notifyTaskAutoManualReview(范式:kuaishou-feifei-executor.ts:36-51)。
  • 状态迁移路径:见 §8 注 —— 领取页打开 link_generated → claimed,绑定 → waiting_bindingsubmit 成功/回调 delivering → redeemingdelivered → redeemed+核销),ship_failed → retry_pendingcancelled → manual_review → closed
  • 回调幂等affiliate_dash 投递带 X-Event-IDorder_site 现无独立去重表 —— 建议 migration 013_affiliate_dash.sql 新建 webhook_events(event_id UNIQUE) 去重表;事件处理用状态合并式更新(仿 syncKuaishouFeifeiTaskStatus,重复通知可重入)。接收方注意 app.ts:31-38express.json 已保存 req.rawBody(验签必需),挂载回调路由时确认 rawBody 可用。
  • 核销联动delivered 回调 → 复用 consumeKuaishouIndustryVouchersForTaskplatforms/kuaishou-industry/voucher-service.ts:143),成功 → redeemed/completed,失败 → manual_review
  • 领取页分发flowType 扩展 'affiliate_dash'buildClaimDetailPayloadservices/claim/kuaishou-cloud-claim-context.ts:89-92)按 executor_key 增加分支,返回 affiliate_dash 专用 payload(商品/预期账号/当前状态/二维码相关);前端 ClaimPage.tsx:337-373 增加渲染分支。

14.3 改动文件清单

后端(16 项)

# 文件 改动
1 services/fulfillment/executors/types.ts FULFILLMENT_EXECUTOR_KEYSAFFILIATE_DASH;按需加 isAffiliateDashExecutor 守卫
2 services/fulfillment/executors/affiliate-dash-executor.ts(新) 仿 kuaishou-feifei-executor.ts{ key, preparePaidTask, resolveDeliveryLink }
3 services/fulfillment/executors/registry.ts EXECUTORS Map 注册
4 services/fulfillment/affiliate-dash/index.ts(新) 业务实现:prepare / sync 状态合并 + flow normalize
5 services/platforms/affiliate-dash/(新) config.ts + http-client.ts(签名 §3.1 + 请求封装 + 超时)+ order-service.ts + notify-service.ts(验签,timingSafeEqual 模式)
6 services/fulfillment/routing-config-service.ts ROUTABLE_EXECUTOR_KEYS / DEFAULT_EXECUTOR_PRIORITY 加入 affiliate_dash
7 services/fulfillment/product-resolution-service.ts candidates 加 affiliate_dash 匹配项 + item_snapshot 上下文
8 services/bootstrap/fulfillment-bootstrap-service.ts CORE_PROFILES 加 affiliate_dash profile
9 services/fulfillment/planner.ts 动态 profile 解析 + buildFulfillmentTaskContextaffiliateDash
10 services/claim/kuaishou-cloud-claim-context.ts 详情 payload 加 affiliate_dash 分支
11 services/claim/kuaishou-cloud-claim-service.ts executor_key 分发加分支
12 repositories/task-repo.ts 仿 findKuaishouFeifeiTaskByOrder 加按 context_json #>> '{affiliateDash,orderNo}' 查任务
13 routes/affiliate-dash.ts(新)+ app.ts webhook 路由,挂载 /api/v1/open/affiliate-dash
14 db/migrations/013_affiliate_dash.sql(新) webhook_events 去重表(可选:affiliate 流水表)
15 routes/admin/platform-config/ + services/admin/platform-config/ 平台配置读写(appKey/secret 等)
16 services/admin/write/ admin 手动重试/转人工按 executor 分发分支

前端(6 项)

# 文件 改动
17 types/claim.ts ClaimAffiliateDashFlowInfo + flowType 扩展
18 pages/claim/ClaimPage.tsx + claim-snapshot.ts isAffiliateDashFlow 判定与渲染分支
19 pages/claim/ClaimAffiliateDashSteps.tsx(新) 领取步骤(查 delivery → 输 UID → bind → 轮询 bind-result → submit
20 services/claim.ts claim 侧操作 API
21 pages/admin/AdminTasksPage.tsx executor label/color 显示
22 domain/task-status.ts 如需新增状态/转移同步(按 §8 注已有路径则无需)

14.4 剩余风险与待确认决策点

说明
自建页 vs delivery_url 兜底 方案 B 以自建页为主,delivery-link 作降级跳转;需确认前端是否允许 iframe 内嵌 affiliate_dash H5(扫码场景无碍,跳转场景有跨域限制)
91 拆单 一个 91 单拆多 unit → 每 task 独立 client_order_no=task_no),互不影响
对账任务 回调为主 + 可选定时 GET /orders/{order_no} 对账(回调丢失兜底)
admin 手动重试 ship_failed / manual_review 的手动「重新 submit」入口按 executor 分发(文件清单 #16
回调订阅确认 阶段 0 配置回调时确认 affiliate_dash 后台事件订阅粒度(order.created 是否必须订阅,或仅 shipping.updated 即可)

15. 阶段 1 落地记录(v2.1 · 已完成)

产出文件(apps/backend/src/):

文件 内容
services/platforms/affiliate-dash/config.ts get/assertAffiliateDashConfigruntime + saved 合并);常量 AFFILIATE_DASH_EXECUTOR_KEY='affiliate_dash'AFFILIATE_DASH_WEBHOOK_PATH='/api/v1/open/affiliate-dash'
services/platforms/affiliate-dash/source-config-service.ts saved config 读写(APP_CONFIG_KEYS.affiliateDash,密钥存 DB 不进 git;含 skuMapping 归一)
services/platforms/affiliate-dash/sign.ts buildClientSign(§3.1,参数名 app_key=+ buildCallbackSign/verifyCallbackSign(§3.2timingSafeEqual+ createClientSignHeaders
services/platforms/affiliate-dash/http-client.ts affiliateDashRequestGET/POST、四头自动签名、AbortController 超时 502/504、错误归一、logExternalHttpPacket 全链路日志)
services/platforms/affiliate-dash/verify-callback.ts verifyAffiliateDashCallback(时间容差 ±300s → 验签 → 事件解析;X-Event-ID 幂等留待阶段 5
services/platforms/affiliate-dash/order-service.ts 建单/查询/delivery-link/delivery/bind/bind-result/submit/wallet 全端点 + 字段映射
services/platforms/affiliate-dash/product-service.ts listAffiliateDashProducts + listAllAffiliateDashProducts(翻页拉全)
配置接入 runtime-config.tsdefaults.tsenv-overrides.tsAFFILIATE_DASH_*)、app-config-keys.ts
测试 sign.test.ts(黄金向量)、verify-callback.test.ts(通过/篡改/超容差/缺头)、http-client.test.ts;12/12 通过,全量 211 通过,tsc --noEmit 通过

真实联调验收listAffiliateDashProducts 直连线上 https://skin.khhao.com → total=33 商品,字段映射正确,签名链路与 affiliate_dash BuildOpenV1Sign 一致。

联调中发现并修复:签名 path 必须为纯路径(不含 query string);首次实现把 /products?page=1&size=5 整串参与签名导致线上 401「签名校验失败」,已改为 URL.pathname 参与签名(§3.1 表头 path=<仅路径> 属实)。


16. 阶段 2 落地记录(v2.2 · 已完成)

产出文件(apps/backend/src/services/fulfillment/):

文件 内容
executors/types.ts FULFILLMENT_EXECUTOR_KEYS.AFFILIATE_DASH='affiliate_dash' + isAffiliateDashExecutor 守卫
executors/affiliate-dash-executor.ts(新) { key, preparePaidTask, resolveDeliveryLink } 仿 feifei:补 claim token → prepareAffiliateDashTask;失败降级 MANUAL_REVIEW + notifyTaskAutoManualReviewresolveDeliveryLink 返回本站统一 claimUrl
executors/registry.ts EXECUTORS Map 注册 affiliateDashExecutor
affiliate-dash/index.ts(新) isAffiliateDashTasknormalizeAffiliateDashFlowcontext_json affiliateDash 块)、prepareAffiliateDashTask(幂等建单、client_order_no=task_nodata 透传 91单号/game_account、写 LINK_GENERATED + task event)、syncAffiliateDashTaskStatus(状态合并:delivering→redeeming、delivered→核销→redeemed/manual_review、ship_failed→retry_pending、cancelled→closed)、buildAffiliateDashClientOrderNo
测试 registry.test.ts 新增 affiliate_dash 映射 + 动作暴露断言(4→4 项)

真实建单联调(线上 skin.khhao.com

步骤 结果
建单 lucky_coin_x220 积分) FO20260805145602108a92eac8dpaidcan_ship=true
同 client_order_no 幂等重试 同单号 + idempotent=true,不重复扣款
GET /orders/{order_no} 查询 paid,金额/状态正确
GET /orders/{order_no}/delivery data 透传(91单号 + game_account)正确

联调中发现并修复POST /orders 响应是 data.order(非 data 直接),首次实现 map 错了层级导致 orderNo 全空;已改为取 data.order 并暴露 idempotent 标志。幂等校验要求同单号参数完全一致,否则 400「参数与原订单不一致」——重试时必须复用原参数。


17. 阶段 3 落地记录(v2.3 · 已完成)

产出文件(apps/backend/src/ + apps/frontend/src/):

文件 内容
services/bootstrap/fulfillment-bootstrap-service.ts CORE_PROFILES 追加 affiliate_dash profilerequiresClaim: true, external_platform
services/fulfillment/routing-config-service.ts ROUTABLE_EXECUTOR_KEYS + DEFAULT_EXECUTOR_PRIORITYAFFILIATE_DASH(路由可命中)
services/fulfillment/planner.ts buildFulfillmentTaskContextaffiliateDash 块(flowType/sku/productName/orderNo/... 与 AffiliateDashFlow 对齐);resolveDynamicFulfillmentProfile 加 AFFILIATE_DASH 分支 + 新增 resolveDynamicAffiliateDashProfileconfigId affiliate_dash:${sku}
services/fulfillment/product-resolution-service.ts resolveConfiguredItemCandidate 91 分支加 affiliate_dash 候选(resolveAffiliateDashSkuByProductNokey=snapshot.productNo+ resolvedSkuCode/resolvedSkuName/snapshot.affiliateDash/matchMode
services/fulfillment/affiliate-dash/index.ts prepareAffiliateDashTask 下单前 preflightAffiliateDashWallet(余额 ≤0 → affiliate_dash_wallet_not_enough 提前拦截;查询失败不阻塞)
config/env-overrides.ts AFFILIATE_DASH_SKU_MAPPING_JSON env 注入(仿 KUAISHOU_FEIFEI_PRODUCT_RULES_JSON
routes/admin/platform-config/affiliate-dash.ts(新)+ platform-config.ts GET/POST 配置、POST matchproductNo→sku)、POST products、POST wallet
services/admin/platform-config/affiliate-dash-service.ts(新) get/update/match/listProducts/walletaudit 不落密钥
前端 types/admin/.../affiliate-dash.ts + services/admin/platform-config/affiliate-dash.ts + 两处 index export 类型与 API 封装
前端 AdminPlatformFulfillmentPage.tsx ROUTING_EXECUTORS 加 affiliate_dashgold+ Tabs 面板 AffiliateDashFulfillmentPanel(配置表单/密钥、skuMapping 行编辑、映射测试、商品目录、钱包余额)

联调验证(模拟 91 进单 → resolveOrderItemForFulfillment

  • productNo='X91-LUCKY-90' + skuMapping 命中 → selectedExecutorKey='affiliate_dash'matchMode='affiliate_dash_sku_mapping'resolvedSkuCode='lucky_coin_x90'、snapshot.affiliateDash 完整
  • 未命中映射的商品 → 不选 affiliate_dash,回退

验证:前后端 typecheck 通过;后端全量 212 测试通过。


18. 阶段 4 落地记录(v2.4 · 已完成)

产出文件:

后端(apps/backend/src/

文件 内容
services/claim/kuaishou-cloud-claim-context.ts buildClaimDetailPayloadaffiliate_dash 分支 + buildAffiliateDashClaimDetailPayloadflowType/affiliateDash 块/product/task/order/orderItem/result
services/claim/kuaishou-cloud-claim-service.ts getKuaishouCloudClaimDetail affiliate_dash 分支:syncAffiliateDashTaskStatus + refreshAffiliateDashBindState(有 bindUuid 时拉 bind-result 实时刷新 bound/账号/mismatch,可重入);submitClaimUid affiliate_dash 分支 → bindAffiliateDashClaimForTaskbind 拿 bindUuid/二维码,task → WAITING_BINDING);新增 submitAffiliateDashClaimsubmit → REDEEMING + task event
services/fulfillment/affiliate-dash/index.ts AffiliateDashFlowbound/boundAccount/gameChannel 字段(bind-result 状态)
routes/claims.ts 新增 POST /:token/affiliate-dash/submitclaimWriteRateLimit

前端(apps/frontend/src/

文件 内容
types/claim.ts ClaimAffiliateDashFlowInfo + ClaimDetailData.affiliateDash
services/claim.ts submitAffiliateDashClaim(token, {gameAccount, bindUuid})
pages/claim/claim-snapshot.ts isAffiliateDashFlow、hasRedeemResult/currentStep/progressText/resultTitle/resultDescription 的 affiliate_dash 分支(step: 无UID=1/绑定中=2/已绑定待提交=3/结果=4)
pages/claim/claim-poll.ts affiliate_dash 轮询(未绑定用 BINDING_PREPARE_POLL_MS,已绑定用 FEIFEI_POLL_MS
pages/claim/ClaimAffiliateDashSteps.tsx(新) AffiliateDashClaimPanel(二维码/绑定链接、bound 状态、mismatch 警告、提交发货按钮)+ AffiliateDashResultStep
pages/claim/ClaimPage.tsx 分发分支 + handleSubmitAffiliateDash + openBindUrl/二维码生成支持 affiliateDash.bindUrl

验证:前后端 typecheck 通过;后端全量 212 测试通过;claim 详情分流脚本验证(flowType=affiliate_dash、affiliateDash 块完整、product 正确、其他 flow 为 null)。

领取闭环状态迁移(与 §8 状态机一致):输 UID → submitClaimUidbind 触发,task link_generated → waiting_binding)→ 轮询详情(bind-result 刷新 bound)→ 提交发货(submitAffiliateDashClaimtask waiting_binding → redeeming)→ 回调 delivered → redeemed + 核销。


19. 核销时机优化(v2.4.1 · 已完成)

需求:核销时机从「delivered 回调」提前到「给用户链接(91 卡信息到手、建单成功)」——参考 kuaishou-feifei 的「给用户链接即核销」模式。

改动apps/backend/src/services/fulfillment/affiliate-dash/index.ts):

位置 行为
prepareAffiliateDashTask 建单成功(orderNo 拿到、task → link_generated、91 侧即可取 claimUrl)后立即调用 consumeKuaishouIndustryVouchersForTask:有凭证 → 核销并写回 kuaishouIndustryVoucher 上下文(consumeStatus=success);无凭证 → not_required核销失败不阻塞下单与链接发放(consumeStatus=failed + warn 日志,delivered 兜底)
syncAffiliateDashTaskStatusdelivered 分支) alreadyConsumed 短路:consumeStatus ∈ {success, not_required} 时不再重复调核销,直接 redeemed;failed/空 才补核销(原逻辑保留为兜底)

效果:核销由「发货完成后(分钟~小时级)」提前到「建单成功(秒级)」;91 卡信息一经消耗即核销,delivered 回调只做状态收敛。已核销成功的凭证在 delivered 时 resolveTaskVouchers 返回空,自然走 redeemed。

验证typecheck 通过;后端全量 212 测试通过。


20. 阶段 5 落地记录(v2.5 · 已完成)

回调路由打通:挂载 POST /api/v1/open/affiliate-dash(与线上回调 URL https://ks.khhao.com/api/v1/open/affiliate-dash 一致)。

文件 内容
db/migrations/013_affiliate_dash_webhook_events.sql webhook_events 表 + (provider, event_id) 唯一索引(回调幂等去重)
repositories/webhook-event-repo.ts insertWebhookEventOnceON CONFLICT (provider, event_id) DO NOTHING,冲突返回 inserted=false
repositories/task-repo.ts findAffiliateDashTaskByOrder:按 context_json #>> '{affiliateDash,orderNo}' / clientOrderNo 查任务(仿 findKuaishouFeifeiTaskByOrder 同构 SQL
services/platforms/affiliate-dash/notify-service.ts handleAffiliateDashNotify:验签(X-Timestamp 容差→重算签名)→ 查任务(未匹配 404 触发重试)→ 幂等闸门(重复 eventId 直接 200 跳过)→ createTaskEvent('affiliate_dash_notify_received')syncAffiliateDashTaskStatus(状态合并式可重入)
routes/affiliate-dash.ts POST /rate limit 300/minreq.rawBody 验签(express.json verify 已存),全链路日志
app.ts 挂载 /api/v1/open/affiliate-dash

处理顺序设计:去重闸门放在「找到任务」之后 —— 任务未建/已删时返回 404 让对方重试,而不是被 eventId 去重永久拦截;找到任务后冲突才视为重复投递直接 200。

验证typecheck 通过;后端全量 212 测试通过;脚本验证前置错误分支——缺签名头 401 / 时间戳超容差 401 / 验签失败 401 / 缺订单号 400 全部正确;「任务不存在→404」及幂等/同步路径依赖真实 DB,留待阶段 6 端到端演练(本沙箱无 postgres)。


21. 履约匹配逻辑深度分析(v2.6)

21.1 匹配链路全貌

91 订单 itemninetyone/order-service.ts:40-95
  ├─ externalSkuCode = productNo      ← 91 商品编码(可能含 "----店铺ID" 后缀,parseOpen91ProductNo 拆分)
  ├─ externalSkuName = productName    ← 91 商品名
  ├─ snapshot.productNo = productNo
  └─ snapshot.productName = productName
        │
        ▼ resolveConfiguredItemCandidateproduct-resolution-service.ts:212
  ├─ cloudtentaclesMatch = 商品名归一化模糊匹配(网络 listSku
  ├─ kuaishouFeifeiMatch = 商品名规则 contains
  └─ affiliateDashMatch  = skuMapping[productNo] 精确映射(resolveAffiliateDashSkuByProductNo
        │
        ▼ resolveFulfillmentRouterouting-config-service.ts:136
  ① 商品路由规则 rulesexact/contains,按商品名)→ ② 全局优先级
     kuaishou_cloud → kuaishou_feifei → affiliate_dash → ③ manual_review 兜底
        │
        ▼ planner.resolveDynamicAffiliateDashProfileplanner.ts:397
  snapshot.affiliateDash.sku → profileconfigId: "affiliate_dash:<sku>"

21.2 痛点清单

# 痛点 影响 证据
P1 优先级与可信度倒挂affiliate_dash 是精确编码映射(运营显式配置、高可信),却排在 cloudtentacles / feifei 的名字模糊匹配之后 91 商品名若同时像 cloudtentacles 商品,会被 cloud 抢单走错通道;映射白配 DEFAULT_EXECUTOR_PRIORITYrouting-config-service.ts:74-78);阶段 3 联调能走对仅因当时 cloud/feifei 未命中
P2 productNo 未拆分 ---- 后缀resolveAffiliateDashSkuByProductNo 直接用 snapshot.productNo 原值查映射,而 open-91 的 productNo 可能为 商品名----店铺IDparseOpen91ProductNo 专门拆它,但该函数未进解析链) 带后缀的 91 编码精确匹配 miss → 直接落 manual_review product-resolution-service.ts:305-330ninetyone/order-service.ts:30
P3 匹配键无归一化skuMapping key 只做原样精确匹配,无 trim/大小写/变体处理 编码大小写变体(x91-lucky-90 vs X91-LUCKY-90miss product-resolution-service.ts:317-318
P4 缺名字兜底affiliate_dash 有 33 个商品(含 displayName),但匹配只有编码精确映射;cloudtentacles / feifei 都有名字匹配,唯独 affiliate_dash 没有 新 91 商品未配映射即 manual_review,无法像 feifei 那样按名字自动兜底 product-service.ts:58-75displayName 未被用于匹配)
P5 余额/健康度不参与路由决策affiliate_dash 候选 available 只看映射命中;余额不足时选中 → prepare 预检失败 → manual_review,不会自动切下一通道 单通道阻塞,无自动降级 阶段 3 仅在 prepare 前预检(getAffiliateDashWallet
P6 双轨配置冲突:路由规则(商品名→executor)与 skuMapping(编码→sku)两套独立配置,同一商品可能被两种规则重复描述、运营维护两处 配置漂移、规则打架 routing-config-service.ts:159-177 vs product-resolution-service.ts:305-330
P7 重复计算hasConfiguredOrderItems(下单前)与 resolveOrderItemForFulfillment(履约时)各跑一次 resolveConfiguredItemCandidatecloudtentacles 名字匹配是网络调用listSku 同一订单解析两次、重复拉商品列表 product-resolution-service.ts:181-195cloudtentacles-name-match-service.ts:111
P8 命名语义误导skuMappingproductNo→sku)实际 key 是 91 外部编码、value 是 affiliate_dash sku;文档/代码里 productNo 一词混用两套体系 运营配置时易搞反 product-resolution-service.ts:40-45;文档 §12

21.3 优化建议(分档)

  • 快赢档(P1/P2/P3,改动集中 product-resolution-service.ts,低风险)
    • P1resolveConfiguredItemCandidate 中 affiliateDashMatch 精确命中时直接优先选择 affiliate_dash(映射命中视为运营显式意图),不受全局优先级影响;或把 DEFAULT_EXECUTOR_PRIORITY 调整为 affiliate_dash 前置。建议加开关「映射命中优先」(默认开)。
    • P2productNo 先走 parseOpen91ProductNo 拆掉 ----店铺ID 再用 productName 段查映射。
    • P3:映射查询前 trim + 大小写归一;可选支持 contains 前缀匹配。
  • 中档(P4affiliate_dash 商品 displayName 归一化名字匹配兜底(仿 cloudtentacles 匹配器),开关控制,避免误配。
  • 后档(P5-P8:候选层余额/健康度(带缓存)、路由与映射配置合一、解析结果缓存去重、命名修正。

21.4 待验证点

  • 91 卡券(91kaquan/kuaishou)真实 productNo 是否带 ----店铺ID 后缀(决定 P2 是否为实际故障)。
  • 33 个 affiliate_dash 商品 displayName 与 91 商品名的重合度(决定 P4 兜底收益)。

22. 履约匹配优化实施(v2.7 · 已完成)

背景确认91 商品编码全部为中文名(如「幸运币90个」「星际漫游服装礼包」),无独立编码体系;运营按「中文名 → affiliate_dash sku」配置显式映射表(33 条,见 §22.3)。

22.1 匹配策略(两级)

  1. 指定匹配(显式映射)最高优先skuMapping 命中(91 中文名 → affiliate_dash sku)即强制走 affiliate_dash,不受 cloudtentacles / feifei 名字匹配抢占、不受全局优先级影响。受通道 enabled 约束:affiliate_dash 停用或商品不可用时回退原逻辑(规则 → 全局优先级)。
  2. 默认匹配(名字匹配):无指定匹配时,三平台按商品名匹配,按可配置优先级选择(defaultExecutorPriority,现状 cloud → feifei → affiliate_dash,管理后台可改顺序)。

22.2 改动清单

文件 改动
routing-config-service.ts resolveFulfillmentRoute 新增 preferredExecutorKey 参数:指定匹配在规则匹配之前优先尝试,命中即选;停用/不可用回退
product-resolution-service.ts 91 分支:映射命中且 preferredMatchEnabled!==false 时传 preferredExecutorKey=AFFILIATE_DASHmatchAffiliateDashSku 归一化匹配(NFKC 全角→半角、去空白、小写),先原样精确再归一化遍历
配置层 preferredMatchEnabled 字段(默认 true):types/runtime-config.ts、defaults.ts、source-config-service.ts、config.ts merge 全链
测试 routing-config-service.test.ts +4preferred 优先/停用回退/不可用回退/无 preferred 走优先级);product-resolution-service.test.ts 新建 +5(精确/全角/空格/大小写/未命中)

安全性:「不耽误生产」保证——现有 cloud/feifei 商品未配置映射表(或映射表为空),preferredExecutorKey 为空走原逻辑,行为零变化;映射表只影响显式配置的 91 商品,且可通过 preferredMatchEnabled=false 一键回退。

22.3 91 中文名 → affiliate_dash sku 映射表(可直接导入配置)

{
  "套装-Alan Walker": "suit_alan_walker",
  "套装-暗影哥特": "suit_shadow_gothic",
  "黑色高级特训官上衣": "top_black_elite_trainer",
  "M416-仓鼠灰灰": "m416_hamster_gray",
  "萌熊伴侣背包": "bag_cute_bear",
  "套装-双彩绵绵": "suit_dual_fluffy",
  "套装-糯粉咩咩": "suit_pink_sheep",
  "套装-恋恋初桃": "suit_first_peach",
  "套装-浪漫天命": "suit_romantic_destiny",
  "西部牛仔大礼包": "pack_western_cowboy",
  "烟雾弹-糯粉咩咩": "smoke_pink_sheep",
  "破片手榴弹-糯粉咩咩": "frag_pink_sheep",
  "套装-仓鼠灰灰": "suit_hamster_gray",
  "套装-萌熊伴侣": "suit_cute_bear",
  "糯粉咩咩背包": "bag_pink_sheep",
  "糯粉咩咩头盔": "helmet_pink_sheep",
  "仓鼠灰灰背包": "bag_hamster_gray",
  "仓鼠灰灰头盔": "helmet_hamster_gray",
  "套装-西部谜踪": "suit_western_mystery",
  "国宝胖达头盔": "helmet_panda_treasure",
  "套装-胖达圆圆": "suit_panda_round",
  "套装-胖达团团": "suit_panda_tuan",
  "熔岩游骑兵礼包": "pack_lava_ranger",
  "套装-狂沙舞者": "suit_sand_dancer",
  "星际漫游服装礼包": "pack_star_roam_outfit",
  "星际漫游枪械礼包": "pack_star_roam_weapon",
  "套装-绵云熊熊": "suit_cloud_bear",
  "荣耀勋章2个": "honor_medal_x2",
  "荣耀勋章30个": "honor_medal_x30",
  "荣耀勋章90个": "honor_medal_x90",
  "幸运币2个": "lucky_coin_x2",
  "幸运币30个": "lucky_coin_x30",
  "幸运币90个": "lucky_coin_x90"
}

22.4 行为验证

用上述映射表模拟 91 商品名解析,全部符合预期:

输入 executor sku
幸运币90个 affiliate_dash lucky_coin_x90
星际漫游服装礼包 affiliate_dash pack_star_roam_outfit
幸运币90个(全角) affiliate_dash lucky_coin_x90
幸运币 90 个(带空格) affiliate_dash lucky_coin_x90
神秘新商品(未映射) (无)回退

验证typecheck 通过;后端全量 221 测试通过(新增 9 个)。


23. 透传优化:91 编码 == affiliate_dash sku 自动命中(v2.8 · 已完成)

需求:91 商品编码可直接设置为英文(= affiliate_dash sku,如 suit_alan_walker),映射表未命中时自动透传,无需在 order_site 维护映射表。

23.1 匹配层级(resolveAffiliateDashSkuByProductNo,改为 async

  1. ① 指定映射matchAffiliateDashSku):skuMapping 精确/归一命中 → matchMode='affiliate_dash_sku_mapping'
  2. ② 透传matchAffiliateDashSkuPassthrough):映射未命中时,productNo 与 affiliate_dash 商品 sku 完全一致 → matchMode='affiliate_dash_sku_passthrough'
  3. 两层任一命中都触发「指定匹配最高优先」(preferredExecutorKey=affiliate_dash),规则/全局优先级不抢占

商品列表缓存getAffiliateDashSkuSet() 内存缓存 5 分钟(listAllAffiliateDashProducts 拉全量 33 个 sku);拉取失败返回上次缓存或空集合——匹配 miss 走其他通道,不阻塞下单

23.2 行为验证(线上商品列表)

输入(91 编码) executor sku matchMode
suit_alan_walker(真实商品) affiliate_dash suit_alan_walker affiliate_dash_sku_passthrough
suit_cloud_bear(真实商品) affiliate_dash suit_cloud_bear affiliate_dash_sku_passthrough
not_a_real_sku (无)回退

验证typecheck 通过;后端全量 224 测试通过(透传纯函数测试 +3)。


24. affiliate-dash 配置位置重构(v2.9 · 已完成)

需求affiliate_dash 平台配置原先杂糅在「履约配置」页(AdminPlatformFulfillmentPage 的 affiliate-dash tab),与履约路由/通道混在一起;项目已有独立的「平台配置」页(AdminPlatformShopsPage,按平台管理来源接入、凭据与店铺能力),应归位。

改动(纯前端 UI 位置移动,后端与数据通路零变化——后端路由与前端 API 本就挂在 /api/v1/admin/platform-config/affiliate-dash/*):

文件 改动
AdminPlatformShopsPage.tsx 新增 affiliateDash tablabel「affiliate-dash」);AffiliateDashPlatformPanel 组件整体迁入(自管理状态:对接配置/密钥/钱包/商品目录/映射表/映射测试),加载函数并入 loadConfigsPromise.all
AdminPlatformFulfillmentPage.tsx 移除 affiliate-dash tab、AffiliateDashFulfillmentPanel 组件及全部 affiliate 相关 import/类型

页面归属

  • 平台配置AdminPlatformShopsPage):affiliate-dash、kuaishou-feifei、kuaishou-lewan、行业电子凭证、内部通知 —— 平台接入凭据
  • 履约配置AdminPlatformFulfillmentPage):履约路由、kuaishou-feifei 履约、kuaishou-lewan 履约 —— 通道路由与履约参数

验证:前端 tsc -b --noEmit 通过;后端未改动。


25. 履约配置页 affiliate-dash 覆盖分析(v2.10 · 已完成)

需求:确认履约配置页是否也需要 affiliate-dash 相关内容(账号归平台配置页)。

分析结论

配置项 归属 现状
对接凭据(appKey/appSecret/callbackSecret/baseUrl/timeout/容差) 平台配置 平台配置页 affiliate-dash tab
钱包 / 商品目录 / 映射测试 平台配置 平台配置页 affiliate-dash tab
91 编码 → sku 映射表 平台配置 平台配置页 affiliate-dash tab
履约路由:通道开关(affiliate_dash enabled 履约配置 履约路由面板已含(ROUTING_EXECUTORS
通道优先级defaultExecutorPriority 含 affiliate_dash,可上移/下移) 履约配置 履约路由面板已含
商品路由规则(商品名 → affiliate_dash 履约配置 履约路由面板规则编辑器已含
指定匹配优先开关preferredMatchEnabled 平台配置 ⚠️ 新增字段未暴露 UI → 本次已补

结论:「账号在平台配置、路由在履约配置」的职责划分已成立——履约路由面板在阶段 3 就已纳入 affiliate_dash(通道开关/优先级/规则)。本次仅修复一个真实缺口:preferredMatchEnabled(映射命中优先,默认 true)此前只能靠默认值或环境变量,后台改不了;现已在平台配置 affiliate-dash tab 增加开关,前后端 effective/source 均透出。

未新增:履约配置页不加独立「affiliate-dash 履约面板」——feifei/lewan 有独立面板是因为履约参数多(productRules、deliveryItems 等),affiliate_dash 履约参数只有路由+优先级+规则,已覆盖,加面板属重复。

验证:前端 tsc -b --noEmit 通过;后端 typecheck 通过;224 测试全绿。


26. affiliate-dash 领取界面优化(v2.11 · 已完成)

需求:精简 affiliate_dash 领取页四步的信息展示。

步骤 改动
第 1 步 不动(与其他平台一致)
第 2 步(绑定领取账号) 信息区只保留三项:affiliate-dash 单号 / 领取商品 / 金额(移除「填写 UID」「订单号」tile);二维码右侧「打开绑定链接」按钮移除(二维码直接展示)
第 3 步(确认并提交发货) 移除信息区(含三项),仅保留已绑定提示 + 提交按钮
第 4 步(结果页) 移除信息区(领取商品/单号/订单状态/发货状态/金额全部移除),仅保留结果标题 + 描述

实现ClaimAffiliateDashSteps.tsx —— AffiliateDashClaimPanel 移除 onOpenBindUrl prop 与按钮、info grid 精简为三项且仅在未绑定(第 2 步)显示;AffiliateDashResultStep 移除 info gridClaimPage.tsx 同步移除 onOpenBindUrl 传参(openBindUrl 函数保留,lewan 仍用)。

附带修复product-resolution-service.ts 透传拉取失败 warn 日志补 logIntegration import(上一轮遗漏导致后端 typecheck 报错)。

验证:后端 typecheck 通过、224 测试全绿;前端 tsc -b --noEmit 通过。


27. 开发 Mock 支持 affiliate-dashv2.12 · 已完成)

需求:开发 Mock 页新增 affiliate-dash 领取 mock,无需真实 affiliate-dash 平台即可走完领取页四步。

能力

后台「开发 Mock」新增 3. affiliate-dash 领取5 个步骤可生成):

step 领取页表现
uid 第一步:待填 UID(无二维码)
bind 第二步:已填 UID + mock 绑定二维码(前端用 bindUrl 现生成)
submitted 第三步:已绑定待「提交发货」(点击后 mock 短路直接模拟发货成功)
completed 第四步:发货成功结果
failed 第四步:发货失败结果(ship_failed

实现

  • mock 标记AffiliateDashFlow 新增 mock: { enabled, orderNo, createdAt } | nullnormalize 透传),dev-mock 创建任务时写入。
  • 三处平台调用短路mock 任务不请求真实 affiliate-dash):
    • syncAffiliateDashTaskStatus:直接用本地快照(flow 自身 orderStatus/failureReason)收敛状态,不走 getAffiliateDashOrder
    • refreshAffiliateDashBindStateclaim-service):直接返回 task,不查 bind-result
    • submitAffiliateDashClaim:直接置 delivered + providerOrderNo=MOCKAD-*,不调 submitAffiliateDashDelivery
  • dev-mock-servicecreateAffiliateDashMockClaim(仿 feifei:建 order/item/task + claim token + mock 上下文);getDevMockStatus platforms 增补;createBaseOrderItem.executor 支持 'affiliate_dash'
  • 路由/前端POST /api/v1/admin/dev-mock/affiliate-dashMock 页新增 Card + AffiliateDashMockFormstep/uid/productName/productSku/orderNo)。

验证:后端 typecheck 通过、224 测试全绿;前端 tsc -b --noEmit 通过;normalizeAffiliateDashFlow mock 字段透传脚本验证通过(mock 保留 / 非 mock 为 null)。