Files
order_site/docs/91卡券/6.开发设计.md
T
2026-05-04 22:05:54 +08:00

7.4 KiB
Raw Blame History

91卡券接入开发设计

1. 目标

基于以下对外接口文档完成当前项目接入:

目标能力:

  1. 91卡券 调用我方异步下单接口;
  2. 我方将订单接入当前项目,走现有快手 Cloud 履约链路;
  3. 我方生成领取链接 https://221329.cc.cd/#/claim/{token}
  4. 91卡券 调用查询订单接口;
  5. 我方在 cards 中返回领取链接,由 91卡券 自动发货给买家。

2. 设计原则

  • 91卡券 作为新的订单来源,不复用 agisokhhao
  • 91卡券 不是新的履约执行器,而是外部售卖和自动发货通道。
  • 当前项目真正的交付物仍然是 claimUrl
  • 查询接口返回 20 的判断标准是“领取链接已生成并可发”,不是“快手最终兑换完成”。
  • buyNum > 1 时,必须拆成多个 task,并返回多个 card。

3. 来源建模

新增独立来源标识:

  • provider = '91kaquan'
  • platform = 'kuaishou'
  • shopId = '91kaquan'
  • shopName = '91卡券'

原因:

  • 避免与 agiso webhook 语义混淆;
  • 避免复用 khhao 的后台拉单来源;
  • 便于后续单独做日志、排障和绑定配置。

4. 接口设计

4.1 路由

新增独立公开路由组:

  • POST /api/v1/open/91/orders/create
  • POST /api/v1/open/91/orders/query

不放进现有 /api/v1/webhooks,因为:

  • 91 不是 webhook 来源;
  • 91 需要自己的响应结构:code/message/data
  • 与现有项目 buildSuccessPayload(code=0) 不兼容。

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: 'base64json'
  }
}

建议环境变量:

  • NINETYONE_USER_ID
  • NINETYONE_SECRET
  • NINETYONE_VERSION
  • NINETYONE_SHOP_ID
  • NINETYONE_SHOP_NAME
  • NINETYONE_TIMESTAMP_TOLERANCE_SECONDS
  • NINETYONE_CARDS_ENCODING

6. 签名设计

签名规则沿用文档约定:

  1. sign 外,所有参数按 ASCII 升序排序;
  2. 拼接为 QueryString
  3. 前后加商户密钥;
  4. 计算大写 MD5
  5. userId 不在请求体中,但参与签名;
  6. 空值参数参与签名。

需要实现:

  • 生成签名原串;
  • 验签;
  • 校验 timestamp 是否超出容忍窗口。

7. 下单设计

7.1 输入映射

91 下单请求映射为内部 source event

  • platformOrderId = orderNo
  • provider = '91kaquan'
  • platform = 'kuaishou'
  • shopId = runtimeConfig.platforms.ninetyone.shopId
  • shopName = runtimeConfig.platforms.ninetyone.shopName
  • payStatus = 'paid'
  • orderStatus = 'paid'
  • items = [{ externalSkuCode: productNo, externalItemId: productNo, externalSkuName: productNo, quantity: buyNum }]

7.2 为什么直接标记 paid

91 调用异步卡密下单接口时,业务上已经代表买家付款完成,当前项目需要立即进入履约任务创建。

7.3 对现有链路的复用

调用现有:

  • upsertOrderFromSource
  • resolveOrderItemForFulfillment
  • syncDeliveryTasksForOrder

这样可直接复用现有:

  • 商品匹配;
  • 履约绑定;
  • 快手 Cloud task 创建;
  • claim token 生成。

7.4 成功判定

下单接口成功判定标准:

  • 请求合法;
  • 商品已匹配;
  • 订单已成功写入;
  • 至少创建出 1 个 task。

满足以上条件即返回:

  • orderStatus = 10

不在下单接口返回 20

8. 查询设计

8.1 查询目标

查询接口的职责不是看订单是否最终兑换完成,而是判断是否已经具备“可自动发货给买家”的内容。

这里的可交付内容就是 claimUrl

8.2 claimUrl 判定

对订单下的每个 task

  1. 优先读取 primary_claim_token
  2. 若没有,则读取 claim_token
  3. 若仍没有,且是 kuaishou_ct_assisted,调用现有 ensureTaskClaimLink(task) 补生成;
  4. buildClaimUrl(token) 组装最终链接。

8.3 查询状态判定

  • 30
    • 订单不存在;
    • 没有匹配到任务;
    • task 进入失败/人工处理态且无法生成领取链接。
  • 10
    • 订单存在;
    • 任务已创建;
    • 但并非所有 task 都已准备好 claimUrl
  • 20
    • 所有 task 都已有可交付的领取链接。

9. cards 设计

9.1 card 字段映射

每个 task 映射为一个 card

  • cardNo = claimUrl
  • cardPwd = ''
  • expireTime = claim_expires_at
  • jumpLink = 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. 商品匹配设计

当前项目的订单商品匹配依赖:

  • provider
  • platform
  • shopId
  • externalSkuCode / externalItemId / externalSkuName

因此接入 91 后,运营侧需要新增对应绑定规则,使:

  • provider = '91kaquan'
  • platform = 'kuaishou'
  • shopId = '91kaquan'
  • externalSkuCode = productNo

最终匹配到现有快手 Cloud 履约配置。

11. 日志与排障

建议所有 91 请求单独打日志,至少记录:

  • requestId
  • orderNo
  • productNo
  • query/create
  • 验签结果
  • 命中 SKU
  • orderId
  • task 数量
  • 最终返回的 orderStatus

12. 代码落点

建议新增:

  • apps/backend/src/routes/open-91.js
  • apps/backend/src/services/open-91/shared.js
  • apps/backend/src/services/open-91/order-create-service.js
  • apps/backend/src/services/open-91/order-query-service.js

需要修改:

  • apps/backend/config/default.cjs
  • apps/backend/src/config/runtime.js
  • apps/backend/src/types/runtime-config.js
  • apps/backend/src/index.js

13. 测试范围

至少补这些单测:

  1. 验签:
    • 正常签名通过;
    • 错误签名失败;
    • 空值参数参与签名。
  2. 下单:
    • 业务请求映射为内部 source event
    • 商品未配置时返回 30
  3. 查询:
    • 全部 task 都有链接时返回 20
    • 部分 task 缺链接时返回 10
    • 无订单时返回 30
  4. cards
    • 单卡编码正确;
    • 多卡数量与 task 数量一致。

14. 当前阶段不做的事

  • 不实现 callbackUrl 主动回调;
  • 不在 91 查询成功后回写快手侧最终兑换结果;
  • 不实现除 aes-256-ecb-base64 之外的卡密加密算法;
  • 不新增专门的后台管理页面。