7.4 KiB
7.4 KiB
91卡券接入开发设计
1. 目标
基于以下对外接口文档完成当前项目接入:
目标能力:
91卡券调用我方异步下单接口;- 我方将订单接入当前项目,走现有快手 Cloud 履约链路;
- 我方生成领取链接
https://221329.cc.cd/#/claim/{token}; 91卡券调用查询订单接口;- 我方在
cards中返回领取链接,由91卡券自动发货给买家。
2. 设计原则
91卡券作为新的订单来源,不复用agiso、khhao。91卡券不是新的履约执行器,而是外部售卖和自动发货通道。- 当前项目真正的交付物仍然是
claimUrl。 - 查询接口返回
20的判断标准是“领取链接已生成并可发”,不是“快手最终兑换完成”。 buyNum > 1时,必须拆成多个 task,并返回多个 card。
3. 来源建模
新增独立来源标识:
provider = '91kaquan'platform = 'kuaishou'shopId = '91kaquan'shopName = '91卡券'
原因:
- 避免与
agisowebhook 语义混淆; - 避免复用
khhao的后台拉单来源; - 便于后续单独做日志、排障和绑定配置。
4. 接口设计
4.1 路由
新增独立公开路由组:
POST /api/v1/open/91/orders/createPOST /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_IDNINETYONE_SECRETNINETYONE_VERSIONNINETYONE_SHOP_IDNINETYONE_SHOP_NAMENINETYONE_TIMESTAMP_TOLERANCE_SECONDSNINETYONE_CARDS_ENCODING
6. 签名设计
签名规则沿用文档约定:
- 除
sign外,所有参数按 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
这样可直接复用现有:
- 商品匹配;
- 履约绑定;
- 快手 Cloud task 创建;
- claim token 生成。
7.4 成功判定
下单接口成功判定标准:
- 请求合法;
- 商品已匹配;
- 订单已成功写入;
- 至少创建出 1 个 task。
满足以上条件即返回:
orderStatus = 10
不在下单接口返回 20。
8. 查询设计
8.1 查询目标
查询接口的职责不是看订单是否最终兑换完成,而是判断是否已经具备“可自动发货给买家”的内容。
这里的可交付内容就是 claimUrl。
8.2 claimUrl 判定
对订单下的每个 task:
- 优先读取
primary_claim_token; - 若没有,则读取
claim_token; - 若仍没有,且是
kuaishou_ct_assisted,调用现有ensureTaskClaimLink(task)补生成; - 用
buildClaimUrl(token)组装最终链接。
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. 商品匹配设计
当前项目的订单商品匹配依赖:
providerplatformshopIdexternalSkuCode/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.jsapps/backend/src/services/open-91/shared.jsapps/backend/src/services/open-91/order-create-service.jsapps/backend/src/services/open-91/order-query-service.js
需要修改:
apps/backend/config/default.cjsapps/backend/src/config/runtime.jsapps/backend/src/types/runtime-config.jsapps/backend/src/index.js
13. 测试范围
至少补这些单测:
- 验签:
- 正常签名通过;
- 错误签名失败;
- 空值参数参与签名。
- 下单:
- 业务请求映射为内部 source event;
- 商品未配置时返回
30。
- 查询:
- 全部 task 都有链接时返回
20; - 部分 task 缺链接时返回
10; - 无订单时返回
30。
- 全部 task 都有链接时返回
- cards:
- 单卡编码正确;
- 多卡数量与 task 数量一致。
14. 当前阶段不做的事
- 不实现
callbackUrl主动回调; - 不在
91查询成功后回写快手侧最终兑换结果; - 不实现除
aes-256-ecb-base64之外的卡密加密算法; - 不新增专门的后台管理页面。