server/laf-cloud/functions/goldMiner/LEGACY-PAYMENT.md

103 lines
14 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.

# 黄金矿工旧 iOS 客服支付接入
本分支基于现有 `wx/KeFuInfo`、`wx/checkIos` 和提供的 `order3.html` 扩展。未改动游戏前端仓库,未部署或调用真实支付。下载目录中的原文件未修改,可部署副本为 [web/order3.html](web/order3.html)。
## 客户端和客服入口
1. 游戏客户端先以登录态请求 `POST /goldMiner/index`:
```json
{
"action": "create_order",
"channel": "legacy_ios",
"uid": "玩家ID",
"token": "登录token",
"itemid": "当期配置的gold_miner商品ID",
"createRequestId": "本次购买请求ID"
}
```
成功外壳为 `{code:1,data,msg}`,data 包含 `outTradeNo, periodId, priceFen, productId, quantity:1, channel, expiresAt, sessionFrom`。价格、用户、期和数量均来自服务端。每次调用都生成新订单(createRequestId 相同也如此);未解锁时允许重新下单及切换原生/客服渠道。过期或已关闭订单不能继续付款。
`sessionFrom` 包含旧客服渠道需要的 `count: 1` 和 `price`(单价,单位分),由后端按当前订单价格填写。前端原样传递该字符串即可,不需要自行拼接价格。`wx/KeFuInfo` 解析并规范这两个字段后交给黄金矿工发送函数,核对与订单一致,再使用 `count * price / 100` 生成客服卡片金额,支付页参数使用同一份 count(映射为 quantity)和 price。旧请求缺少字段时从订单补齐;无效金额返回 INVALID_INPUT,与订单不一致返回 ORDER_SNAPSHOT_CONFLICT,不生成支付链接。升级需同时发布 `wx/KeFuInfo` 和 `goldMiner/legacyPayment`。
2. 保存返回的 `outTradeNo`,将 `data.sessionFrom` **原样**传入微信 `openCustomerServiceConversation` 的 `sessionFrom`。不能继续沿用旧客户端自己生成订单号、拼接价格的黄金矿工路径。expiresAt 为本期截止时间。sessionFrom 中 goldMinerTicket 仅为兼容原入口的渠道标识,不再是签名票据;活动结束后不能重新购买该期。
3. 现有客服网关将 `FromUserName`、`SessionFrom` 转发到 `wx/KeFuInfo`。黄金矿工按订单记录核对归属 openid、商品和环境,不再校验客服票据签名;支付链接仍只发给订单本人。普通商品保留现有流程。
当前通用 `PostImg` 生成黄金矿工链接后,将本次秒级 time 写入主订单 iosTime,并保存完整活动订单兼容副本。商品展示名与真实商品 ID 分开处理,兼容副本沿用主订单中的商品 ID 和活动快照。
4. 新支付链接的 `time` 是生成链接时的秒级时间字符串,与该次签名 `body.timeStamp` 相同;页面原样提交 `wx/checkIos`,匹配对应订单 `iosTime`。订单 `time` 仍为毫秒创建时间。旧订单没有 iosTime 时,仅兼容原先的毫秒创建时间链接;不能把秒级 time 乘 1000 替代。也支持游戏内以 `uid/token/outTradeNo` 调用该接口,返回严格布尔值 `true/false`。
5. `checkIos` 只校验和关联订单。`order` 在预下单时就是主记录,`iosOrder` 保留完整兼容副本;主记录存在时不覆盖支付状态,不丢失 goldMiner 快照。主记录缺失时只从已验证的服务端副本恢复。同一操作可重试,付款早于转单也不会回退订单。
6. 支付成功后,用已有 `wx/getPayInfo`、`wx/iosgetPayInfo` 或 `wx/getOrderReward` 携带 `uid/token/outTradeNo` 查询。这些接口可触发商户主动查单。返回 `rewardDelivery=goldMiner.claim` 时进入活动领奖协议;禁止套用普通金币商品的通用补发。
客户端预下单调用是必需接入点,本提交提供后端和页面,未修改 MatchMaster 游戏代码。现有客服网关需原样转发 `SessionFrom`;仍需微信实际会话联调确认网关没有截断或改写参数。
## 支付确认与恢复
- `/goldMiner/merchantNotify` 仅接收通知并返回 SUCCESS,不解密、不查询数据库、不确认付款。通知正文中的交易状态、订单号、金额均不作为付款依据。新旧订单均由游戏查单接口、登录恢复或每分钟任务主动查询微信结果。
- 后端用现有商户私钥签名请求微信 HTTPS 查单接口,不再额外验证微信响应签名;仍核对查单返回的 appid、mchid、openid、订单号、CNY 金额、JSAPI 类型和支付时间,只有 SUCCESS 才持久化 confirmedAt/transactionId/paidAt 并解锁。禁止从客户端或通知正文直接写入付款成功。HTTP 请求不跟随重定向。
- 历史 confirmationSource=merchant_notify 保留,新确认统一为 merchant_query。支付成功后前端需要继续调用支付查询接口;通知接口返回 SUCCESS 不代表活动已解锁。网络故障、租约或退避期间可能需要再次查询。
- 微信通知地址不能携带查询参数,配置 GOLD_MINER_MERCHANT_NOTIFY_URL 时使用不带查询串的 HTTPS 地址。依据:[微信支付回调通知注意事项](https://pay.wechatpay.cn/doc/v3/merchant/4012075420)。
- 登录最多查 2 笔待处理订单,任务每轮最多查 5 笔;30 秒订单租约避免重复并发请求,失败退避从 10 秒到最多 5 分钟。网络超时和查单信息不匹配时保留订单。接口的 `PAYMENT_PENDING` 不能被当作金币补发授权。
- 未支付旧期订单经可信查单后关闭;关单与付款竞态会重新查单,成功付款走原活动履约。支付确认延迟时,有达标奖励补发原期,无奖励顺延紧接下一期;重复回调和查询不会重复发资格。冲突保留 `manual_review`,不自动顺延第三期。
- `goldMiner.paymentChannel` 标识 `legacy_ios/native`,历史缺失该字段的活动订单按原生处理。新增商户快照、预支付状态、租约、重试和支付确认字段均放在原有 `goldMiner` 对象内,无新增通关事件集合。主订单保留完整原有活动快照。
## 环境配置和发布
不再读取 GOLD_MINER_WECHATPAY_PUBLIC_KEYS、GOLD_MINER_WECHATPAY_API_V3_KEY 和 GOLD_MINER_ORDER_TICKET_SECRET,三项均可不配置。微信要求的商户请求签名、支付页 paySign 仍沿用现有商户私钥生成。
| 环境变量 | 内容 |
| --- | --- |
| `PAYMENT_APP_ENV` | `test` 或 `production`,与所在后端和订单一致 |
| `GOLD_MINER_LEGACY_ENABLED` | 仅字符串 `true` 开放旧渠道预下单和发支付链接,默认关闭;关闭后仍处理已付款订单 |
| `WX_MCH_ID` | 微信支付商户号 |
| `WX_MINIGAME_APP_ID` | 支付 appid,必须与玩家 openid 和商户绑定关系相符 |
| `WX_MCH_CERT_SERIAL_NO` | 商户 API 证书序列号,用于请求签名 |
| `WX_PAY_PRIVATE_KEY_BUCKET` | 私有存储桶,内含 `apiclient_key.pem`,沿用现有商户签名配置 |
| `GOLD_MINER_MERCHANT_NOTIFY_URL` | 可选覆盖;默认使用支付应用域名加 `/goldMiner/merchantNotify` |
| `GOLD_MINER_PAY_PAGE_URL` | 可选覆盖;默认沿用 `https://pay.nika4games.com/order3.html` |
| `GOLD_MINER_CHECK_IOS_URL` | 可选覆盖;默认使用支付应用域名加 `/wx/checkIos` |
支付应用域名默认复用已有配置:正式服读取 WX_PAY_NOTIFY_URL 的域名,未配置则沿用旧支付页中的正式服域名 https://q6rvwvtnga.sealoshzh.site;测试服读取 TEST_WX_PAY_NOTIFY_URL 的域名,不能自动退回正式服。这里复用的是域名,商户通知路径仍为 /goldMiner/merchantNotify,不把加密商户通知发送到原生 payCallBack。三个 GOLD_MINER_*_URL 均可省略或留空;显式填写时仍须为正确的 HTTPS 地址。测试服若既无测试回调地址,又未覆盖通知/转单地址,会明确提示缺少 TEST_WX_PAY_NOTIFY_URL。
这些默认值在创建订单时写入 merchantSnapshot,后续修改配置不改变已有订单;调整地址后应重新创建订单进行联调。默认支付页地址仍需部署仓库中的新版 order3.html。
依赖已有 `cloud.shared` 中的 `wxaccess_token` 和微信有效客服会话;失效时接口返回 `CUSTOMER_SERVICE_UNAVAILABLE`,有效 prepay 会话下可重发同一链接,不再次向微信下单。预下单超时或会话过期时会先查单恢复付款结果;未付款则返回 ORDER_RECREATE_REQUIRED,游戏重新调用 create_order,使用新订单及 sessionFrom。已关闭订单返回 ORDER_CLOSED,也应重新下单。环境密钥不得放进客户端、Postman 或 Git。
先按 [总部署清单](README.md#8-管理配置与部署) 发布依赖和入口,再运行 `setup_indexes`(包含商户流水唯一索引和查单索引)、绑定每分钟任务、部署支付页,最后启用开关。除 goldMiner 目录外,本次需更新:
- `login.ts`
- `wx/KeFuInfo.ts`、`wx/checkIos.ts`
- `wx/getPayInfo.ts`、`wx/iosgetPayInfo.ts`、`wx/getOrderReward.ts`
如果测试服尚未部署上一版黄金矿工,还必须同步总部署清单中的 `userLevel`、原生下单、原生回调等文件。现有 YAML 方法未变;专属通知新增 YAML 必须一起发布。
支付页优先使用后端写入链接的 `checkUrl`,普通旧链接未提供时保留原生产地址。必须部署本仓库副本后才可用测试服链接;Downloads 原页硬编码生产地址。支付页只接受转单接口明确返回 true,并等待 WeixinJSBridge 就绪。小游戏支付参数继续由后端签名。
支付页“该订单已过期”目前代表 checkIos 返回非 true,不一定实际超时。排查时先核对页面实际请求的 checkUrl 与订单所在环境,再查看 `[GoldMiner checkIos] rejected` 或 `[checkIos] order not found` 日志。日志区分订单缺失、环境不匹配、身份/时间参数不匹配和订单关闭;新黄金矿工链接 time 必须等于订单 iosTime(秒),不能替换为订单创建时间;没有 iosTime 的历史订单才按原毫秒创建时间校验。checkIos 仍返回布尔值,保持旧页面兼容;日志不记录账号、token 或支付签名。
排查新卡片仍携带旧参数时,按同一订单号核对 `[KeFuInfo] payment route`、`[GoldMiner customer link] sending`、`[GoldMiner customer link] sent` 三条日志;sending 记录真实发送参数中的 linkTime、orderTime、signatureTime 和必需字段是否存在。普通发送路径另记 `[KeFuInfo PostImg] sending payment link`。日志不包含完整链接、openid、access_token、nonceStr 或 paySign。若本次发送没有对应入口日志,应继续核对客服网关实际调用的环境和函数;若发送记录正确而卡片参数不同,应检查是否读取了其他卡片或网关缓存。
`PAYMENT_APP_ENV=test` 只隔离应用和订单,不是微信商户免扣款沙箱;新商户请求仍到微信正式 API。上线前需要实际微信账号验证 appid/openid、客服转发、支付目录/域名、CORS、商户证书和通知地址配置,仓库测试无法替代这些部署条件。测试及正式订单的通知地址各自保存在订单快照,不走原生测试回调转发。
## 验证
云函数沙箱不要求提供全局 `AbortSignal`。商户请求和客服消息发送均设置 5 秒等待上限,覆盖响应正文读取;超时不代表微信已经取消请求。预下单超时后保留 `creating` 状态,后续先查单,未付款时要求创建新订单,不复用旧订单号再次向微信下单。客服消息发送超时保留已有预支付信息,重试可能再次发送同一支付链接。
`CUSTOMER_SERVICE_UNAVAILABLE` 的 msg 区分当前环境 `wxaccess_token` 缓存缺失、网络请求失败、响应非 JSON、微信拒绝发送。微信拒绝发送时返回 HTTP 状态及数字 errcode,并在服务端记录订单号和这些状态;不透传原始响应、access_token 或支付签名。若仅客服发送失败且预支付信息仍有效,可重新触发同一订单的客服发送,不会再次向微信预下单;若随后返回 `ORDER_RECREATE_REQUIRED` 则创建新订单。此诊断更新只需发布 `goldMiner/legacyPayment`。
修复 `AbortSignal is not defined` 时需发布 `goldMiner/merchant` 和 `goldMiner/legacyPayment`。旧订单已进入 `creating` 状态的,查单确认后可能返回 `ORDER_RECREATE_REQUIRED`;此时重新调用 `create_order`,使用新返回的订单号及 `sessionFrom`。无需新增环境变量或修改前端价格参数。
```powershell
node --test laf-cloud/functions/goldMiner/tests/*.test.mjs
node --test --test-name-pattern='gold miner' laf-cloud/tests/login-wucai-state.test.mjs
```
本地使用模拟数据库与微信 HTTPS 查单结果,未真实支付或发送客服消息。覆盖三项配置缺失、客服与转单归属、价格、重复下单、伪造通知不能解锁、历史订单主动查单恢复、查询租约、期末关单竞态、跨期履约、支付页及 Postman 链路。
参考协议:[微信 JSAPI 下单](https://pay.wechatpay.cn/doc/v3/merchant/4012791870)、[微信支付通知](https://pay.wechatpay.cn/doc/v3/merchant/4012791861)、[Laf 请求上下文](https://doc.laf.run/zh/cloud-function/request.html)。
本次升级需发布 goldMiner/merchant、goldMiner/legacyPayment、goldMiner/merchantNotify、goldMiner/config 及对应 YAML,入口依赖按部署平台规则更新。保留每分钟 goldMiner/jobs。现有前端仍原样传递 sessionFrom 和支付链接参数,无需新增密钥或字段。升级前已发送的签名 time 链接需重新获取新订单的支付链接;已付款旧订单的查询、恢复及活动权益不受影响。普通商品和原生支付签名流程不变。
本次 iosTime 修复发布顺序:先发布 goldMiner/legacyPayment,再发布 wx/KeFuInfo 和 wx/checkIos。无需新建集合、索引或环境变量,不需要改支付页请求格式。生成另一笔订单不影响当前订单的时间校验;重发同一订单时 iosTime 更新,请使用最新链接。旧通用链接若此前没有保存订单 iosTime,需重新获取链接。保存 iosTime 不修改主订单 time、支付状态或履约状态。