|
|
@@ -1,6 +1,6 @@
|
|
|
# OMG(歐買尬/FunPoint)支付 — 前端接入文档
|
|
|
|
|
|
-**适用端**:客户 App(uni-app) | **后端分支**:`OmgPayController` 已完成 | **日期**:2026-08-06
|
|
|
+**适用端**:客户 App(uni-app) | **后端分支**:`OmgPayController` 已完成 | **日期**:2026-08-14
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -59,6 +59,7 @@ OMG(欧买尬/FunPoint)是**网页托管式支付网关**(AIO = All-In-One
|
|
|
| 5 | (可选)取消订单退款:调 `/pay/omg/refund` | 订单取消逻辑 |
|
|
|
| 6 | 告诉后端两个前端地址(见 §6 配置依赖) | 沟通 |
|
|
|
| 7 | 所有面向用户文字走 i18n 4 语言 | zh/tw/en/vi |
|
|
|
+| 8 | iOS 联调时同时收集 `[OMG-RETURN]` 与 `[OMG-APP]` 日志 | HBuilderX + Safari Web Inspector |
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -296,6 +297,203 @@ const poll = (ddId) => {
|
|
|
|
|
|
> 轮询建议:每 2s 一次,最多 15 次(30s);超过仍未成功提示「支付结果确认中,请稍后在订单列表查看」。
|
|
|
|
|
|
+### 4.4 iOS / uni-app App 唤起全链路诊断日志
|
|
|
+
|
|
|
+后端中转页会在浏览器控制台输出统一前缀 `[OMG-RETURN]`,覆盖页面加载、自动唤起、手动触摸/点击、页面可见性和脚本异常。App 端需要按下面代码输出 `[OMG-APP]`,两组日志必须在同一次支付中一起收集。
|
|
|
+
|
|
|
+所有日志只保留 Scheme 的协议/主机/路径、参数名和订单号后四位,不打印 token、CheckMacValue、OMG 表单或完整订单号。
|
|
|
+
|
|
|
+#### 4.4.1 `App.vue`:App 生命周期和 Scheme 参数
|
|
|
+
|
|
|
+```js
|
|
|
+const OMG_APP_LOG = '[OMG-APP]'
|
|
|
+let newIntentRegistered = false
|
|
|
+
|
|
|
+function summarizeSchemeArgument(raw) {
|
|
|
+ const value = typeof raw === 'string' ? raw : ''
|
|
|
+ if (!value) return { present: false }
|
|
|
+
|
|
|
+ const url = value.match(/^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)([^?#]*)/i)
|
|
|
+ const idMatch = value.match(/[?&]ddId=([^&#]*)/i)
|
|
|
+ let ddId = ''
|
|
|
+ if (idMatch) {
|
|
|
+ try { ddId = decodeURIComponent(idMatch[1]) } catch (e) { ddId = '' }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ present: true,
|
|
|
+ protocol: url ? url[1].toLowerCase() + ':' : 'unknown',
|
|
|
+ host: url ? url[2] : '',
|
|
|
+ pathname: url ? url[3] : '',
|
|
|
+ orderRef: ddId ? '***' + ddId.slice(-4) : ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function currentRouteSummary() {
|
|
|
+ const pages = getCurrentPages()
|
|
|
+ const page = pages.length ? pages[pages.length - 1] : null
|
|
|
+ return {
|
|
|
+ route: page && page.route ? page.route : '',
|
|
|
+ optionKeys: page && page.options ? Object.keys(page.options) : []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function logRuntimeArguments(source) {
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ const runtimeReady = typeof plus !== 'undefined' && plus.runtime
|
|
|
+ console.info(OMG_APP_LOG, source, {
|
|
|
+ runtimeReady: !!runtimeReady,
|
|
|
+ launcher: runtimeReady ? plus.runtime.launcher : '',
|
|
|
+ argument: summarizeSchemeArgument(runtimeReady ? plus.runtime.arguments : ''),
|
|
|
+ route: currentRouteSummary()
|
|
|
+ })
|
|
|
+ // #endif
|
|
|
+}
|
|
|
+
|
|
|
+function registerNewIntentLogger() {
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ if (newIntentRegistered || typeof document === 'undefined') return
|
|
|
+
|
|
|
+ const register = () => {
|
|
|
+ if (newIntentRegistered) return
|
|
|
+ document.addEventListener('newintent', () => logRuntimeArguments('newintent'), false)
|
|
|
+ newIntentRegistered = true
|
|
|
+ console.info(OMG_APP_LOG, 'newintent_listener_registered')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof plus !== 'undefined') register()
|
|
|
+ else document.addEventListener('plusready', register, false)
|
|
|
+ // #endif
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ onLaunch(options) {
|
|
|
+ console.info(OMG_APP_LOG, 'onLaunch', {
|
|
|
+ path: options && options.path ? options.path : '',
|
|
|
+ queryKeys: options && options.query ? Object.keys(options.query) : []
|
|
|
+ })
|
|
|
+ registerNewIntentLogger()
|
|
|
+ logRuntimeArguments('onLaunch_runtime')
|
|
|
+ },
|
|
|
+ onShow(options) {
|
|
|
+ console.info(OMG_APP_LOG, 'onShow', {
|
|
|
+ path: options && options.path ? options.path : '',
|
|
|
+ queryKeys: options && options.query ? Object.keys(options.query) : [],
|
|
|
+ route: currentRouteSummary()
|
|
|
+ })
|
|
|
+ logRuntimeArguments('onShow_runtime')
|
|
|
+ },
|
|
|
+ onHide() {
|
|
|
+ console.info(OMG_APP_LOG, 'onHide', { route: currentRouteSummary() })
|
|
|
+ },
|
|
|
+ onError(error) {
|
|
|
+ console.error(OMG_APP_LOG, 'app_error', {
|
|
|
+ errorType: typeof error,
|
|
|
+ route: currentRouteSummary()
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+App 已经在前台时,第三方 Scheme 正常唤起至少应看到 `newintent` 或新的 `onShow_runtime`,其中 `argument.present=true`,且 `protocol=com.twanmsdyh.app:`。App 从未启动时,应看到 `onLaunch_runtime` 携带同样的脱敏参数摘要。
|
|
|
+
|
|
|
+#### 4.4.2 `omgCheckout.vue`:支付子 WebView 生命周期
|
|
|
+
|
|
|
+App-vue 的 `<web-view>` 在 App 平台是父页面下的子 WebView,不要依赖组件的 `@load/@error`。在现有 `omgCheckout.vue` 的 `onReady` 和 `methods` 中加入以下代码:
|
|
|
+
|
|
|
+```js
|
|
|
+function summarizeWebviewUrl(raw) {
|
|
|
+ const value = typeof raw === 'string' ? raw : ''
|
|
|
+ const url = value.match(/^([a-z][a-z0-9+.-]*):\/\/([^/?#]*)([^?#]*)/i)
|
|
|
+ return {
|
|
|
+ protocol: url ? url[1].toLowerCase() + ':' : '',
|
|
|
+ host: url ? url[2] : '',
|
|
|
+ pathname: url ? url[3] : ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export default {
|
|
|
+ data: () => ({
|
|
|
+ src: '',
|
|
|
+ omgWebviewLoggerAttached: false
|
|
|
+ }),
|
|
|
+ onLoad(opt) {
|
|
|
+ this.src = '/static/omgPay.html?' + decodeURIComponent(opt.pay)
|
|
|
+ console.info('[OMG-APP]', 'checkout_onLoad', {
|
|
|
+ hasPayPayload: !!opt.pay,
|
|
|
+ payloadLength: opt.pay ? String(opt.pay).length : 0
|
|
|
+ })
|
|
|
+ },
|
|
|
+ onReady() {
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ ;[100, 500, 1200].forEach(delay => {
|
|
|
+ setTimeout(() => this.attachOmgWebviewLogger(delay), delay)
|
|
|
+ })
|
|
|
+ // #endif
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ console.info('[OMG-APP]', 'checkout_onShow')
|
|
|
+ },
|
|
|
+ onHide() {
|
|
|
+ console.info('[OMG-APP]', 'checkout_onHide')
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ console.info('[OMG-APP]', 'checkout_onUnload')
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ attachOmgWebviewLogger(delay) {
|
|
|
+ // #ifdef APP-PLUS
|
|
|
+ if (this.omgWebviewLoggerAttached) return
|
|
|
+ const parent = this.$scope && this.$scope.$getAppWebview
|
|
|
+ ? this.$scope.$getAppWebview()
|
|
|
+ : null
|
|
|
+ const children = parent ? parent.children() : []
|
|
|
+ const child = children && children.length ? children[0] : null
|
|
|
+
|
|
|
+ if (!child) {
|
|
|
+ console.warn('[OMG-APP]', 'checkout_webview_missing', { delayMs: delay })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ this.omgWebviewLoggerAttached = true
|
|
|
+ const detail = event => ({
|
|
|
+ event,
|
|
|
+ id: child.id || '',
|
|
|
+ url: summarizeWebviewUrl(child.getURL ? child.getURL() : '')
|
|
|
+ })
|
|
|
+ ;['loading', 'loaded', 'error', 'show', 'hide', 'close'].forEach(event => {
|
|
|
+ child.addEventListener(event, () => {
|
|
|
+ const payload = detail(event)
|
|
|
+ if (event === 'error') console.error('[OMG-APP]', 'checkout_webview_event', payload)
|
|
|
+ else console.info('[OMG-APP]', 'checkout_webview_event', payload)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ console.info('[OMG-APP]', 'checkout_webview_logger_attached', detail('attached'))
|
|
|
+ // #endif
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 4.4.3 真机日志采集步骤
|
|
|
+
|
|
|
+1. 使用 HBuilderX 运行 iOS 真机,先清空控制台并过滤 `OMG-APP`。
|
|
|
+2. iPhone 打开“设置 → Safari → 高级 → Web 检查器”;Mac Safari 在“开发”菜单选择该设备和支付子 WebView,过滤 `OMG-RETURN`。
|
|
|
+3. 从 App 发起一笔 OMG 信用卡支付,支付完成后等待中转页停留至少 3 秒,再点击一次“返回 App”。
|
|
|
+4. 导出从 `[OMG-RETURN] page_loaded` 到最后一条 `auto_open_observation` 的浏览器日志,以及同一时段全部 `[OMG-APP]` 日志。
|
|
|
+5. 日志不得包含 token、CheckMacValue、HashKey、HashIV、完整订单号或银行卡资料;发现后先删除敏感内容再传给后端排查。
|
|
|
+
|
|
|
+#### 4.4.4 日志判读
|
|
|
+
|
|
|
+| 日志表现 | 失败边界 |
|
|
|
+|----------|----------|
|
|
|
+| 没有 `[OMG-RETURN] page_loaded` | OMG 没有进入 `/pay/omg/result`,或 Safari 检查器选错 WebView |
|
|
|
+| 有 `page_loaded`、`auto_open_start`,3 秒后 `stillVisible=true`,App 没有 `onHide/newintent/onShow_runtime` | 自定义 Scheme 被支付子 WebView 拒绝或未交给 iOS |
|
|
|
+| 中转页出现 `visibilitychange/pagehide`,但 App 没有任何新日志 | iOS 没有找到可处理该 Scheme 的安装包,重点检查 iOS `urltypes` 与重新打包结果 |
|
|
|
+| App 出现 `newintent/onShow_runtime` 且 `argument.present=true`,但路由没变化 | App 已收到 Scheme,路由解析或 `navigateTo/redirectTo` 逻辑有问题 |
|
|
|
+| App 已进入 `pages/OrderList/paySuccess/paySuccess`,但结果不更新 | 回跳已成功,继续检查 `/pay/omg/query` 请求与返回 |
|
|
|
+
|
|
|
+> `[OMG-RETURN]` 日志来自后端返回的子 WebView 页面,通常需要 Safari Web Inspector 查看;`[OMG-APP]` 来自 uni-app 逻辑层,可在 HBuilderX 真机控制台查看。两者不是同一个 JavaScript 执行环境。
|
|
|
+
|
|
|
## 5. 状态码 / 付款方式速查
|
|
|
|
|
|
### `payStatus`(订单 + 流水)
|