合并本轮全部文档改动(原 5 个 docs 提交压成 1 个)。 口径与索引: - 新增 docs/README.md 文档总索引:三来源口径(91 + 快手 send-code = kuaishou-91)、 「一个订单 = 骨架 + 买家凭证 + 一个执行载体」、成本只改一处、文档状态机 - 过期阶段文档移入 docs/archive/2026-10-08/(保留内容,不删除) 分析报告: - docs/项目全量分析报告-2026-10-17.md:架构、五条业务链路、P0/P1/P2 风险与改进路线 - docs/载体判定与成本中心落地-复核与草案-2026-10-17.md:生产只读克隆量化、上一轮结论复核与 4 处修正 方案文档: - docs/成本中心与三块履约模型.md:三块执行载体、标准商品、成本版本与快照、人工支出登记 - docs/财务系统集成方案.md:复式记账模型、分录表、分期落地 - docs/影子总账说明.md:影子期与转正期的边界、对账与转正标准 SQL 草案(docs/drafts/,未接入迁移): - 载体判定与人工块量化.sql:量化查询、carrier_type 判定与回填、守恒校验 - 成本中心与销售事实-DDL草案.sql:product_catalog / channel_product_mappings / cost_items / cost_versions / cost_snapshots / cost_exceptions / sales_daily_facts - 人工支出登记-DDL草案.sql:manual_fulfillment_cases / manual_expense_records / manual_expense_events 业务口径确认(2026-10-17): - 收入 = 用户实付(orders.total_amount),与快手 paymentFen 数据核验 100% 一致 - 打手报酬 = 支出/成本(最终提现打款),按总额法 - 人工块以「支出可追溯」为先,SLA 监管延后 - 退款/仲裁暂未接入,先预留字段与反向冲正关系 三份草案已在一次性沙箱库按依赖顺序零错误应用(10 张新表、27 个 CHECK 约束)。
9.8 KiB
91卡券接入开发设计
1. 目标
来源口径(2026-10-17):
91卡券是kuaishou-91来源的订单骨架通道;快手 send-code 是同一来源的数据补全与兜底通道。二者共享匹配上下文与店铺键空间,不能拆成两个来源或两个适配器。
基于以下对外接口文档完成当前项目接入:
目标能力:
91卡券调用我方异步下单接口;- 我方将订单作为独立接入平台订单写入当前项目;
- 已配置商品自动走现有 affiliate-dash 履约链路;
- 未配置商品进入后台待补全队列,可手动补齐履约信息后重试;
- 我方生成领取链接
https://你的域名/#/claim/{token}; 91卡券调用查询订单接口;- 我方在
cards中返回领取链接,由91卡券自动发货给买家。
2. 设计原则
91卡券是kuaishou-91来源的订单骨架通道,不是新的履约执行器;快手 send-code 是同来源的补全/兜底通道。- 外部售卖、订单输入和自动发货通道仍由 91 承担;匹配层的来源键统一为
kuaishou-91。 - 当前项目真正的交付物仍然是
claimUrl。 - 查询接口返回
20的判断标准是“领取链接已生成并可发”,不是“快手最终兑换完成”。 buyNum > 1时,必须拆成多个 task,并返回多个 card。- 未命中履约配置时不丢单,先返回
10并沉淀到后台待补全队列。
3. 来源建模
新增独立来源标识:
provider = '91kaquan'platform = 'kuaishou'shopId = '91kaquan'shopName = '91卡券'
原因:
- 避免与其他平台回调语义混淆;
- 避免复用历史后台拉单来源;
- 便于后续单独做日志、排障和绑定配置。
4. 接口设计
4.1 路由
新增独立公开路由组:
POST /api/v1/open/91/orders/createPOST /api/v1/open/91/orders/query
接口使用独立公开路由组,原因:
91需要自己的响应结构:code/message/data;- 与后台管理接口的统一响应结构隔离;
- 便于按 91 的签名、时间戳和错误码规范单独演进。
4.2 返回格式
对 91 路由统一返回:
{
"code": 200,
"message": "接口调用成功",
"data": {}
}
业务失败也返回 data.orderStatus = 30 的业务响应。
仅在真正的协议级错误下返回:
{
"code": 400,
"message": "验签失败",
"data": null
}
5. 配置设计
新增运行时配置项:
platforms: {
ninetyone: {
userId: '',
secret: '',
version: '1.0',
shopId: '91kaquan',
shopName: '91卡券',
timestampToleranceSeconds: 600,
cardsEncoding: 'aes-256-ecb-base64'
}
}
建议环境变量:
KAQUAN91_USER_IDKAQUAN91_SECRETKAQUAN91_VERSIONKAQUAN91_SHOP_IDKAQUAN91_SHOP_NAMEKAQUAN91_TIMESTAMP_TOLERANCE_SECONDSKAQUAN91_CARDS_ENCODING
6. 签名设计
签名规则沿用文档约定:
- 仅使用请求 JSON body 中实际传入的参数参与签名;
- 除
sign外,所有 body 参数按 ASCII 升序排序; - 拼接为 QueryString;
- 前后加商户密钥;
- 计算大写 MD5;
- 已传入的空值参数参与签名;
- 不额外加入
userId、商户号、内部配置项或未传入的可选字段。
需要实现:
- 生成签名原串;
- 验签;
- 校验
timestamp是否超出容忍窗口。
7. 下单设计
7.1 输入映射
91 下单请求映射为内部 source event:
platformOrderId = orderNoprovider = '91kaquan'platform = 'kuaishou'shopId = runtimeConfig.platforms.ninetyone.shopIdshopName = runtimeConfig.platforms.ninetyone.shopNamepayStatus = 'paid'orderStatus = 'paid'items = [{ externalSkuCode: productNo, externalItemId: productNo, externalSkuName: productNo, quantity: buyNum }]
7.2 为什么直接标记 paid
91 调用异步卡密下单接口时,业务上已经代表买家付款完成,当前项目需要立即进入履约任务创建。
7.3 对现有链路的复用
调用现有:
upsertOrderFromSourceresolveOrderItemForFulfillmentsyncDeliveryTasksForOrder
这样可直接复用现有:
- 91 商品名到 affiliate-dash sku 的匹配(
resolveAffiliateDashSkuByProductNo,含 sku 透传兜底); - affiliate-dash 履约 task 创建;
- 按数量拆 task(
delivery-task-service.ts按unit_index为每个数量各建一个 task); - claim token / 领取链接生成。
7.4 成功判定
下单接口成功判定标准:
- 请求合法;
- 订单已成功写入;
- 商品已匹配时至少创建出 1 个 task;
- 商品未匹配时进入待补全队列。
满足以上条件即返回:
orderStatus = 10
不在下单接口返回 20。
8. 查询设计
8.1 查询目标
查询接口的职责不是看订单是否最终兑换完成,而是判断是否已经具备“可自动发货给买家”的内容。
这里的可交付内容就是 claimUrl。
8.2 claimUrl 判定
对订单下的每个 task,调用 resolveTaskDeliveryLink(task)(apps/backend/src/services/fulfillment/delivery-link-service.ts),由该 task 对应的履约执行器解析可交付链接:
- 读取
primary_claim_token,为空则回退claim_token; - 同时读取
primary_claim_expires_at/claim_expires_at作为过期时间; - 有 token 才返回
claimUrl = buildClaimUrl(token),否则返回null(视为暂不可交付)。
说明:kuaishou_ct_assisted(乐玩)/ kuaishou-feifei 通道已下线,其历史 task 由只读 stub 处理,不再生成领取链接;领取链接现由 affiliate-dash 执行器的 resolveDeliveryLink 提供。
8.3 查询状态判定
30:- 订单不存在;
- 没有匹配到任务;
- task 进入失败/人工处理态且无法生成领取链接。
10:- 订单存在;
- 商品履约配置尚未补齐;
- 任务已创建;
- 但并非所有 task 都已准备好
claimUrl。
20:- 所有 task 都已有可交付的领取链接。
9. cards 设计
9.1 card 字段映射
每个 task 映射为一个 card:
cardNo = claimUrlcardPwd = ''expireTime = claim_expires_atjumpLink = claimUrl
9.2 编码方案
按 卡密加密说明.md 实现:
- 算法:
AES - 模式:
ECB - 填充:
PKCS7Padding - 数据块:
128 位 - 输出:
Base64 - 密钥:开放平台
AppSecret,长度固定32个字符
Node 侧实现等价为:
aes-256-ecb- 开启自动填充
- 明文为
JSON.stringify(cards) - 输出 Base64 字符串
编码层仍然保持独立模块,后续如果联调方要求兼容别的编码方式,只替换该模块即可。
9.3 多数量处理
若 buyNum = N:
- 内部创建
N个 task; - 查询成功时返回
N个 card; - 每个 card 对应一个独立领取链接。
10. 商品匹配设计
当前项目的 91 下单流程直接使用 productNo 做履约匹配:
- 默认把
productNo当作商品名(中文),经 affiliate-dash 的 SKU 映射表(apps/backend/src/services/platforms/affiliate-dash/sku-mapping.ts)转成 affiliate-dash 商品的英文 sku; - 若映射表未命中,再用
productNo与 affiliate-dash 商品列表的 sku 做透传兜底匹配(matchAffiliateDashSkuPassthrough); - 如果
productNo使用商品名----店铺编号格式,前半段作为商品名匹配、后半段作为快手小店shopId(parseOpen91ProductNo); - 匹配成功后由履约路由选中 affiliate-dash 执行器,多数量在
delivery-task-service.ts里按数量拆成多个 task。
最终目标是让 91 卡券只传商品名,系统按 SKU 映射与透传兜底自动创建 affiliate-dash 履约任务。
11. 日志与排障
建议所有 91 请求单独打日志,至少记录:
- requestId
- orderNo
- productNo
- query/create
- 验签结果
- 命中 SKU
- orderId
- task 数量
- 最终返回的
orderStatus
12. 代码落点
建议新增:
apps/backend/src/routes/open-91.ts(挂载于apps/backend/src/app.ts:103,前缀/api/v1/open/91)apps/backend/src/services/open-91/config.tsapps/backend/src/services/open-91/payload.tsapps/backend/src/services/open-91/crypto.tsapps/backend/src/services/open-91/response.tsapps/backend/src/services/open-91/order-create-service.tsapps/backend/src/services/open-91/order-query-service.tsapps/backend/src/services/open-91/consume-on-deliver.ts
需要修改:
apps/backend/src/config/defaults.tsapps/backend/src/config/runtime.tsapps/backend/src/config/env-overrides.tsapps/backend/src/types/runtime-config.tsapps/backend/src/services/platforms/ninetyone/order-service.tsapps/backend/src/services/fulfillment/product-resolution-service.tsapps/backend/src/index.ts
13. 测试范围
至少补这些单测:
- 验签:
- 正常签名通过;
- 错误签名失败;
- 空值参数参与签名。
- 下单:
- 业务请求映射为内部 source event;
- 商品未配置时返回
10,并进入后台待补全队列。
- 查询:
- 全部 task 都有链接时返回
20; - 部分 task 缺链接时返回
10; - 无订单时返回
30。
- 全部 task 都有链接时返回
- cards:
- 单卡编码正确;
- 多卡数量与 task 数量一致。
14. 当前阶段不做的事
- 不实现
callbackUrl主动回调; - 不在
91查询成功后回写快手侧最终兑换结果; - 不实现除
aes-256-ecb-base64之外的卡密加密算法; - 不新增独立顶级菜单;91 待补全订单目前只有后端管理接口(
/api/v1/admin/platform-config/ninetyone/orders查询、.../:id/retry重试、.../:id/fail标记失败),前端暂无专属页面。