# 黄金矿工旧 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、支付状态或履约状态。