7.7 KiB
7.7 KiB
91卡券接入开发设计
1. 目标
基于以下对外接口文档完成当前项目接入:
目标能力:
91卡券调用我方异步下单接口;- 我方将订单作为独立接入平台订单写入当前项目;
- 已配置商品自动走现有快手 Cloud 履约链路;
- 未配置商品进入后台待补全队列,可手动补齐履约信息后重试;
- 我方生成领取链接
https://你的域名/#/claim/{token}; 91卡券调用查询订单接口;- 我方在
cards中返回领取链接,由91卡券自动发货给买家。
2. 设计原则
91卡券作为独立订单来源。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: 'base64json'
}
}
建议环境变量:
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
这样可直接复用现有:
- 商品匹配;
- 履约绑定;
- 快手 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/src/config/defaults.tsapps/backend/src/config/runtime.jsapps/backend/src/types/runtime-config.jsapps/backend/src/index.js
13. 测试范围
至少补这些单测:
- 验签:
- 正常签名通过;
- 错误签名失败;
- 空值参数参与签名。
- 下单:
- 业务请求映射为内部 source event;
- 商品未配置时返回
10,并进入后台待补全队列。
- 查询:
- 全部 task 都有链接时返回
20; - 部分 task 缺链接时返回
10; - 无订单时返回
30。
- 全部 task 都有链接时返回
- cards:
- 单卡编码正确;
- 多卡数量与 task 数量一致。
14. 当前阶段不做的事
- 不实现
callbackUrl主动回调; - 不在
91查询成功后回写快手侧最终兑换结果; - 不实现除
aes-256-ecb-base64之外的卡密加密算法; - 不新增独立顶级菜单,后台入口复用“平台配置 -> 91卡券接入”。