Files
frontend-app/docs/douyin-pay-integration.md
T

130 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 抖音 App 支付接入与打包
2026-09-09,基于 develop / b350f1c 修复。
## 本次故障
普通 uni-app App 中调用 uni.requestPayment({ provider: "toutiao" }) 无法找到该通道,
会报 requestPayment:fail service not found。配置 Scheme 白名单不会注册支付 SDK。
后端 prepayId 不能由前端直接当成 pay_token 拼接收银台 URL。
## 统一调用
订单页、订单补付、充值使用 utils/douyin-pay.js 的 appDypayFun(payment),传入
/order/payment 等接口的 data 对象(含 orderNum、douyin),不要传整个 HTTP response。
utils/payUtils.js 保留 douyinPayFun(douyin, orderId, orderNum) 兼容入口,
也委托给同一原生调用与查单逻辑。
适配器支持 douyin 的旧扁平字段以及新 orderInfo 对象/JSON,
最终传给 SDK 的字段为 appid、partnerid、prepayid、package、noncestr、timestamp、sign。
签名仍由服务端产生,前端不自行签名或改写签名字段。
uni_modules/tb-douyin-pay 是本地源码 UTS 插件,不是 uni.requestPayment provider;
无需将它添加成 manifest 的 toutiao 支付通道。
Android 原生依赖由 utssdk/app-android/config.json 声明;
iOS SDK 位于 utssdk/app-ios/Frameworks/DypaySDK.xcframework。
需要将整个插件目录纳入版本控制,不能只提交 utils 和页面。
## 打包与验收
1. 在支持插件配置的 HBuilderX(插件声明最低 4.36)打开 frontend-app。
2. 安装对应 UTS 编译与 App 打包扩展,Android 打包需能解析插件 Maven 依赖。
3. 制作包含本插件的自定义调试基座或完整 Android/iOS 安装包,使用平台登记的包名和签名。
4. 安装新的原生包。单独更新 WGT 或服务器部署无法给旧安装包加入 SDK。
5. 从真实订单调用支付;测试页也可粘贴 /order/payment 成功返回 JSON。
6. 覆盖成功、取消、网络失败、重复点击、回前台、冷启动恢复场景。
仅服务端 /payment/queryResult 返回 state=2 才提示支付成功。
SDK 成功或跳回 App 本身不作为到账依据。结果未知时保留待查订单并阻止重复发起。
人工续付接口返回 paid 时仍走原查单接口,以便补同步业务订单;closed 只解除本地待查记录,不标记成功。
测试页不再提供模拟 pay_token 链接,不在页面日志输出完整签名。
H5/小程序不会调用原生 App SDK。
## 验证边界
2026-09-09 实测:14 项自动化测试通过,6 个 Vue 文件的 SFC/脚本/模板检查通过。
HBuilderX 5.24 执行 App appResource 导出成功,产物位于 unpackage/resources,
包含 tb-douyin-pay 的 Android 原生源码/配置和 iOS SDK 源码/框架。
编译有既有 CSS Autoprefixer 注释警告,不影响此次资源导出。
这次没有构建、安装完整 APK/IPA,也没有进行真机交易。
npm run test:douyin 覆盖参数适配、旧调用入口、服务端确认、重复支付及异常恢复。
这些自动化测试使用模拟 SDK/接口,不会产生真实交易,不能替代完整原生包与真机验收。
## 关闭 App 后继续未支付订单(2026-09-09 补丁)
旧实现把保存的订单一直当作“结果未知”,只查单不允许重新打开收银台。
现在冷启动仍然只查单;用户再次点击抖音支付时,调用
`POST /payment/douyin/resume`(表单参数 `orderNum`),由当前登录身份校验订单归属。
- 服务端确认 `NOTPAY` 且本地订单仍可支付、未过期:复用原支付订单号、金额、到期时间,
获取新的 SDK 签名参数;用户确认“继续上一笔支付”后打开收银台。
- 返回 `paid` 或 `closed`:不调 SDK,只清理该账户对应的本地待查记录。
- `USERPAYING`、未知渠道状态、查单失败、续付接口不可用:仍保留记录,不创建新支付。
- 取消弹窗、账号切换、重复点击:不会发起第二笔支付。
- 只保存原订单号,不缓存签名;兼容修复前已保存的订单号,覆盖购物及充值。
接口返回 `data = { state, orderNum, payment }`,其中 state 为 unpaid/paid/closed/pending,
只有 unpaid 才含 payment(与原 /order/payment 的 SDK 参数结构一致)。
续付接口不创建支付订单、不重新扣抵扣券,也不会自动取消、退款或更改订单到期时间。
**发布顺序:先部署后端续付接口,再更新 App。只更新 App 无法解除旧后端的锁定。**
本次无 SQL、无新增密钥配置。现有原生插件未变化,但仍需使用已包含 SDK 的安装包。
本补丁涉及 App 的 `utils/douyin-pay.js`、`tests/douyin-integration.test.cjs` 及本文档;
后端涉及 `DouyinPaymentResumeService`、`DouyinPayResumeDto`、`DouyinPayService`、
`QueryOrderResult`、`PayController` 和 `DouyinPaymentResumeServiceTest`。
本地验证:`npm run test:douyin` 的 22 项测试通过;后端
`mvn -o -pl hashmall-frontend -am test -Dtest=DouyinPaymentResumeServiceTest,DouyinPayInfoDtoTest,DouyinAppPaySignerTest -Dsurefire.failIfNoSpecifiedTests=false`
编译成功,所选 10 项测试通过(含新增 7 项续付测试)。
提交目标:后端 hashmall/hashmall 的 dev_douyin_tmp,App 为 charles/frontend-app 的 dev_codex。
尚未部署或进行真机实付;以上测试均使用模拟渠道,不产生真实交易。
## 过期关单及支付安全修复(2026-09-09 第二次补丁)
本节覆盖前文旧续付补丁对“过期仍 pending”和“不关单”的行为描述。
- 后端先查单,过期且 NOTPAY 时请求渠道关单、再次确认,成功后结束本地支付与商品/充值订单。查单失败不放行。
- `/payment/douyin/resume` 新增 reason:QUERY_FAILED、CLOSE_UNCONFIRMED、RECONCILIATION_REQUIRED、CHANNEL_PENDING;App 显示相应提示,不暴露后台密钥或完整渠道响应。
- closed 解除缓存的旧支付单,用户确认后按当前页面重新请求支付;如果原商品订单已取消,需返回商品页重新下单。
- 原生错误码不再被完全隐藏,但 SDK 错误/取消不能单独用于判断渠道订单已关闭。
- 插件版本 0.2.0 新增 resetDypay。在服务端确认 paid/closed/unpaid 后重置 UTS/Swift 锁,恢复原生回调丢失场景。迟到回调不作为支付成功依据。
- 续付请求超时 60 秒,查单 30 秒;其他请求默认值不变。
**本次修改了原生插件,必须重新打包 APK/IPA 或自定义基座;不要只对旧基座发布 WGT。**
先更新后端 frontend/paycenter/job/mqconsumer 及实际承载退款的服务,再更新 App。
无 SQL 迁移。历史已关闭但已收款的异常单保留人工核对,不自动恢复发货或重复付款。
前端 `npm run test:douyin` 新增过期旧单放行、取消/切换账号、明确诊断、原生锁恢复等用例;测试不产生真实交易。
最终验证:27 项前端支付测试全部通过;后端 41 项选定支付测试通过,包含真实 H2 事务回滚验证。
HBuilderX 5.24 App 资源编译及导出成功(有既有 CSS 注释警告)。
资源导出通过不代表 Android/iOS 原生构建或真机交易已验证,发布前仍须完成验收。
## 与现有支付交互对齐(2026-09-09 第三次补丁)
本节取代前文“显式确认/继续支付/重新支付弹窗”的交互说明,遵循用户要求,不新增确认弹窗。
- 用户点击支付即继续:原单有效时直接续付;渠道确认旧单关闭后,直接继续当前页面的下单请求。保留服务端防重复支付校验。
- 微信、支付宝和抖音共用现有支付成功页 `pages/order_package/order_submit_ok/order_submit_ok`,不新建成功页,不仅显示成功 Toast。
- 抖音正常回调、再次点击查到已支付、冷启动查到已支付,均在服务端确认后跳转成功页。
- 从抖音返回 App 时,即使 SDK 回调丢失,也允许 onShow 查单结束等待;与迟到 SDK 回调竞争时只处理一次。查单失败或 pending 不跳成功、不解锁。
- 页面栈满时使用 redirectTo 进入同一成功页;两种跳转均失败则保留待查记录,下次重试跳转,不再次付款。
- 本次仅修改前端 JavaScript、测试及文档,不改后端、接口、SQL、原生插件和支付页面样式。仍依赖上一补丁的后端与原生插件 0.2.0。
本地 33 项支付测试通过,涵盖无弹窗、成功跳转、丢失回调、回调竞争、账号切换、页面栈满以及微信/支付宝成功页回归。尚未进行真机实付。
HBuilderX 5.24 App 资源编译及导出成功,仅有既有 CSS Autoprefixer 注释警告。
## 失败与杀进程恢复页面对齐(2026-09-09 第四次补丁)
按用户要求直接复用微信、支付宝的支付失败页及其“确认”操作,不增加弹窗,不新增页面。
- SDK 取消/调用失败后,先查询支付结果;已确认支付成功仍进入成功页,其余本次未完成的支付进入现有失败页。
- 支付时杀死 App,重新打开后读取原订单并查单:已支付进入成功页;已关闭/未完成进入失败页,不自动唤起 SDK。
- 旧单关闭的点击直接结束在失败页,不在失败页背后同一次操作再创建新单;后续用户主动点击才继续新支付。
- 页面跳转只结束本次客户端支付交互,不代表服务端订单被置为失败。未知/网络异常仍保留原订单号并用已有 Toast 提示待确认,后续到账仍可查到成功;不自动关单、退款或丢弃待查记录。
- 同一进程中同一未完成订单只自动展示一次失败页,避免每次回前台又强制跳回。新的主动支付尝试重新计数。
- 成功/失败共用导航封装,页面栈满时尝试 redirectTo;跳转失败保留恢复记录。微信、支付宝仍使用原来的成功/失败页面。
39 项前端测试通过,包含杀进程后新运行上下文恢复、取消/原生错误、离线恢复、失败跳转重试、迟到成功及微信/支付宝失败页回归。
本次只改 JavaScript、测试和文档,不改后端、原生插件、SQL、接口或页面样式。尚未真机实付。
HBuilderX 5.24 App 资源编译与导出成功,只有既有 CSS Autoprefixer 注释警告。