Explorar o código

文档:明确 OMG iOS WebView 返回交接方案

qmj hai 2 semanas
pai
achega
eb9fa51299
Modificáronse 1 ficheiros con 179 adicións e 0 borrados
  1. 179 0
      specs/020-omg-payment-rebuild/omg-ios-webview-return-fix.md

+ 179 - 0
specs/020-omg-payment-rebuild/omg-ios-webview-return-fix.md

@@ -0,0 +1,179 @@
+# OMG 支付 iOS WebView 返回 App 交接设计
+
+## 最终决策(2026-08-17)
+
+本方案只约定后端结果页与用户端 App 支付页之间的交接方式。支付状态查询、轮询、提示文案和最终业务页面跳转由前端继续实现,不属于本次后端修改范围。
+
+- 用户端实际支付页是 `pages/OrderList/buy/omgCheckout`。
+- 该页面通过 renderjs 创建并提交 HTML Form,OMG 收银台会加载到当前页面 WebView,而不是 `<web-view>` 子组件。
+- iOS 主返回流程由 `omgCheckout` 获取当前页面 `WebviewObject`,监听原生 `loaded` 事件并读取当前 URL。
+- 当前 URL 精确进入 `https://foodieapi.waimai-paotui.com/pay/omg/result` 时,视为收到 `OMG_RETURNED` 交接信号。
+- `/pay/omg/result` 继续返回 HTML 页面。现有提示内容、“返回 App”按钮和 App Scheme 可以保留,作为外部浏览器、Android 或监听失效时的兼容兜底。
+- iOS 主流程不依赖返回页中的 `uni.webView.redirectTo`、`uni.postMessage`、`plus.webview.currentWebview`、`parent.evalJS` 或 App Scheme。
+- 返回页到达只表示浏览器流程已经返回,不表示支付成功。前端不得仅凭该信号展示支付成功。
+- `omgCheckout` 是 OMG 专用页面,不需要额外增加 `payChannel=omg`。
+
+后续如与 2026-08-14 的旧示例或旧描述存在差异,以本节和本文当前内容为准。
+
+## 1. 问题与根因
+
+OMG 信用卡付款完成后,iOS 会停留在后端返回的“正在返回 App”页面,自动返回和手动按钮都可能没有效果。
+
+真机日志已经确认:
+
+```text
+[OMG-RETURN] ios_parent_redirect_exception
+ReferenceError: Can't find variable: sync
+
+[OMG-RETURN] bridge_redirect_exception
+ReferenceError: Can't find variable: sync
+```
+
+同一个 App Scheme 可以从 iOS 外部浏览器打开 App,说明 Scheme 注册和目标路由有效。失败边界位于 App 内 OMG WKWebView 的 Bridge 或同 App Scheme 交接。
+
+因此,iOS 主流程不能继续依赖远程结果页主动控制 App 路由。可靠的交接点是 App 原生 WebView 自身的 `loaded` 事件。
+
+## 2. 实际页面结构
+
+`pages/OrderList/buy/omgCheckout` 不是承载 `<web-view>` 子组件的普通宿主页。它在 renderjs 中执行:
+
+```text
+接收 omgCheckoutPayload
+        ↓
+创建 HTML Form
+        ↓
+POST 到 payload.gatewayUrl
+        ↓
+当前页面 WebView 被 OMG 收银台内容替换
+```
+
+所以前端需要监听:
+
+```js
+this.$scope.$getAppWebview()
+```
+
+而不是:
+
+```js
+this.$scope.$getAppWebview().children()[0]
+```
+
+renderjs 只负责提交 OMG Form。URL 监听和 App 路由接管必须放在普通 `<script>` 的 App 逻辑层中。
+
+## 3. 返回交接流程
+
+```text
+用户在 OMG 收银台完成或结束支付流程
+        |
+        v
+OMG POST /pay/omg/result
+        |
+        v
+后端校验返回资料并返回现有 HTML 结果页
+        |
+        v
+omgCheckout 当前 WebView 触发 loaded
+        |
+        v
+omgCheckout 通过 getURL() 读取当前地址
+        |
+        +-- 不是 /pay/omg/result --> 忽略
+        |
+        +-- 精确匹配结果页 -------> 触发一次 OMG_RETURNED
+                                      |
+                                      v
+                              前端接管后续业务
+```
+
+这里所说的“结果页通知外层”是指:结果页加载使原生 WebView 自动触发 `loaded`,外层 App 逻辑再读取 URL。结果页不需要主动调用 `postMessage`。
+
+## 4. 后端结果页边界
+
+`POST /pay/omg/result` 继续存在,并继续返回 `text/html` 页面,不能删除接口或改为 `204 No Content`。
+
+后端继续负责:
+
+- 接收 OMG 的 `application/x-www-form-urlencoded` Client POST。
+- 按现有规则校验返回资料。
+- 返回禁止缓存的安全 HTML 页面。
+- 保留现有页面提示内容。
+- 可以保留“返回 App”按钮及 App Scheme 作为兼容兜底。
+
+后端结果页不负责保证 iOS App 内路由成功。即使保留现有 Bridge、父 WebView 或 Scheme 尝试,它们也只能作为非关键兜底,失败不得影响页面正常返回。
+
+结果页不得直接宣称支付成功,也不得在 HTML、URL 或日志中暴露 token、完整 `formFields`、`CheckMacValue` 或其他支付凭证。
+
+## 5. 前端交接契约
+
+前端应在 renderjs 提交 Form 后、支付完成前,保证当前页面 WebView 已经绑定 `loaded` 监听。监听绑定后应立即读取一次当前 URL,防止目标页面在绑定前已经加载完成。
+
+结果页匹配规则:
+
+```text
+https://foodieapi.waimai-paotui.com/pay/omg/result
+https://foodieapi.waimai-paotui.com/pay/omg/result?...query...
+https://foodieapi.waimai-paotui.com/pay/omg/result#...hash...
+```
+
+必须同时精确匹配协议、域名和路径,不能只判断 URL 包含 `omg` 或 `result`。
+
+匹配后只触发一次 `OMG_RETURNED`,防止 `loaded` 重复触发造成重复查询或重复跳转。`omgCheckout` 已经保存 `orderId`,结果页不需要重新传递订单号。
+
+页面销毁时,前端需要移除 `loaded` 监听器并清理本页面创建的定时器。具体支付查询、状态处理和路由由前端实现。
+
+## 6. 安全约束
+
+- `/pay/omg/result` 到达不等于支付成功。
+- 只有后端支付状态查询确认成功后,前端才能展示支付成功页面。
+- token 只能由 App 请求层发送给后端,不能传给 OMG 页面或拼入结果页 URL。
+- 日志不得包含 token、完整 `formFields`、`CheckMacValue` 或完整回调 URL。
+- 必须防止重复 `loaded` 事件造成重复业务处理。
+
+## 7. 兼容策略
+
+| 通道 | 定位 | 说明 |
+|---|---|---|
+| 当前 WebView `loaded` + 精确 URL | iOS 主流程 | 不依赖远程页面 Bridge,由 `omgCheckout` 接管 |
+| 现有“返回 App”按钮 | 兼容兜底 | 页面外观可以保留,不作为 iOS 成功判据 |
+| App Scheme | 外部浏览器/Android 兜底 | 同 App iOS WKWebView 中不保证有效 |
+| 远程 Bridge、`parent.evalJS` | 非关键旧兜底 | 已有真机失败证据,不能承担主流程 |
+
+保留旧页面和按钮不等于继续依赖旧方案。iOS 是否能够自动返回,必须以 `omgCheckout` 的原生 WebView 监听为准。
+
+## 8. 预期日志
+
+前端至少应能观察到以下交接日志;字段名称可按 App 现有规范调整:
+
+```text
+[OMG-APP] webview_loaded {"isOmgResultPage":true}
+[OMG-APP] result_page_detected {"orderRef":"***7389"}
+```
+
+结果页旧兜底失败日志可以继续存在,但不得影响上述交接:
+
+```text
+[OMG-RETURN] ios_parent_redirect_exception ...
+[OMG-RETURN] bridge_redirect_exception ...
+```
+
+## 9. 验收清单
+
+1. 使用 iPhone 打开 App 并进入 `pages/OrderList/buy/omgCheckout`。
+2. 确认当前页面 WebView 的 `loaded` 监听在支付完成前已绑定。
+3. 完成一笔 OMG 支付,确认后端仍返回现有 HTML 结果页。
+4. 确认结果页仍可显示原有提示和“返回 App”按钮。
+5. 确认 `omgCheckout` 能读取到精确的 `/pay/omg/result` URL。
+6. 确认同一次返回只触发一次 `OMG_RETURNED`。
+7. 确认 OMG 收银台中间页面、取消页面及其他地址不会触发返回交接。
+8. 确认后续支付状态处理由前端接管,并且不会仅凭结果页到达显示支付成功。
+9. 使用 Android 和外部浏览器复核现有按钮及 Scheme 兜底未被破坏。
+
+## 10. 本次范围
+
+本次只确认和记录交接设计,不修改用户端 App 代码,也不修改支付查询、轮询、提示文案或最终路由实现。
+
+参考资料:
+
+- [uni-app WebView 组件](https://uniapp.dcloud.net.cn/component/web-view.html)
+- [HTML5+ Webview API](https://www.html5plus.org/doc/zh_cn/webview.html)