From a37aa35d888e83926269843acb9dfe364095b3bc Mon Sep 17 00:00:00 2001 From: "douglas.ou" Date: Wed, 11 Mar 2026 10:32:12 +0800 Subject: [PATCH] fix(wechat): use navigateTo for mini program WebView payment Replace postMessage-based bridge payment with wx.miniProgram.navigateTo to jump to a native mini program pay page. postMessage only delivers on web-view back/share and cannot be used for real-time payment invocation. - Add WeChat JSSDK loading logic for mini program environment - Detect navigateTo availability instead of unreliable getCapabilities() handshake - Remove dependency on ensureWechatMiniProgramBridge from PaymentModal Co-Authored-By: Claude Opus 4.6 --- .../public/events/components/PaymentModal.tsx | 16 +-- .../wechat-payment-client-context.ts | 107 ++++++++++++++---- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/apps/web/src/modules/public/events/components/PaymentModal.tsx b/apps/web/src/modules/public/events/components/PaymentModal.tsx index 72639644..ee43d908 100644 --- a/apps/web/src/modules/public/events/components/PaymentModal.tsx +++ b/apps/web/src/modules/public/events/components/PaymentModal.tsx @@ -28,7 +28,6 @@ import { type WechatPayPayload, type WechatPaymentChannel, } from "@community/lib-shared/payments/wechat-payment"; -import { ensureWechatMiniProgramBridge } from "./wechat-payment-client-context"; export interface PaymentOrderData { orderId: string; @@ -129,21 +128,16 @@ const invokeMiniProgramBridgePay = async ( ); } - const requestPayment = ensureWechatMiniProgramBridge()?.requestPayment; - if (!requestPayment) { + const navigateTo = (window as any).wx?.miniProgram?.navigateTo; + if (typeof navigateTo !== "function") { throw new WechatBridgeError( WECHAT_BRIDGE_ERROR_CODES.BRIDGE_NOT_SUPPORTED, + "wx.miniProgram.navigateTo not available", ); } - const result = await requestPayment(params); - if (!result.ok) { - const code = - result.errCode === "PAY_CANCELLED" - ? WECHAT_BRIDGE_ERROR_CODES.PAY_CANCELLED - : WECHAT_BRIDGE_ERROR_CODES.PAY_FAILED; - throw new WechatBridgeError(code, result.errMsg); - } + const encodedParams = encodeURIComponent(JSON.stringify(params)); + navigateTo({ url: `/pages/pay/pay?params=${encodedParams}` }); }; export function PaymentModal({ diff --git a/apps/web/src/modules/public/events/components/wechat-payment-client-context.ts b/apps/web/src/modules/public/events/components/wechat-payment-client-context.ts index c8645507..a4d3a61d 100644 --- a/apps/web/src/modules/public/events/components/wechat-payment-client-context.ts +++ b/apps/web/src/modules/public/events/components/wechat-payment-client-context.ts @@ -32,6 +32,7 @@ interface MiniProgramBridgeEnvelope { interface WechatMiniProgramRuntime { postMessage?: (params: { data: MiniProgramBridgeEnvelope }) => void; + navigateTo?: (params: { url: string }) => void; } interface WechatRuntime { @@ -49,6 +50,7 @@ const BRIDGE_NAME = "HW_MINI_PAYMENT_BRIDGE"; const BRIDGE_GET_CAPABILITIES = "HW_MINI_BRIDGE_GET_CAPABILITIES"; const BRIDGE_REQUEST_PAYMENT = "HW_MINI_BRIDGE_REQUEST_PAYMENT"; const BRIDGE_RESPONSE_TYPE = "HW_MINI_BRIDGE_RESPONSE"; +const WECHAT_JSSDK_SRC = "https://res.wx.qq.com/open/js/jweixin-1.6.0.js"; const CAPABILITIES_TIMEOUT_MS = 5_000; const REQUEST_PAYMENT_TIMEOUT_MS = 35_000; @@ -62,6 +64,7 @@ const pendingBridgeRequests = new Map< >(); let messageListenerBound = false; +let wechatJSSDKLoadPromise: Promise | null = null; const isObject = (value: unknown): value is Record => typeof value === "object" && value !== null; @@ -201,6 +204,74 @@ const bindMiniProgramMessageListener = () => { messageListenerBound = true; }; +const isWeChatUserAgent = () => { + if (typeof window === "undefined") { + return false; + } + + return window.navigator.userAgent.toLowerCase().includes("micromessenger"); +}; + +const ensureWechatJSSDK = async () => { + if (typeof window === "undefined" || typeof document === "undefined") { + return; + } + + if (!isWeChatUserAgent()) { + return; + } + + if (typeof window.wx?.miniProgram?.postMessage === "function") { + return; + } + + if (wechatJSSDKLoadPromise) { + return wechatJSSDKLoadPromise; + } + + wechatJSSDKLoadPromise = new Promise((resolve, reject) => { + const existingScript = document.querySelector( + `script[src="${WECHAT_JSSDK_SRC}"]`, + ) as HTMLScriptElement | null; + + if (existingScript) { + if ( + existingScript.dataset.hwLoaded === "true" || + typeof window.wx !== "undefined" + ) { + resolve(); + return; + } + existingScript.addEventListener("load", () => resolve(), { + once: true, + }); + existingScript.addEventListener( + "error", + () => reject(new Error("Failed to load WeChat JSSDK")), + { once: true }, + ); + return; + } + + const script = document.createElement("script"); + script.src = WECHAT_JSSDK_SRC; + script.async = true; + script.onload = () => { + script.dataset.hwLoaded = "true"; + resolve(); + }; + script.onerror = () => reject(new Error("Failed to load WeChat JSSDK")); + document.head.appendChild(script); + }); + + try { + await wechatJSSDKLoadPromise; + } catch (error) { + wechatJSSDKLoadPromise = null; + throw error; + } +}; + const callMiniProgramBridge = (params: { type: string; payload?: unknown; @@ -316,29 +387,23 @@ export const buildWechatPaymentClientContext = return { environmentType }; } - const bridge = ensureWechatMiniProgramBridge(); - if (!bridge?.getCapabilities) { - return { - environmentType, - miniProgramBridgeSupported: false, - }; - } - try { - const capabilities = await bridge.getCapabilities(); - return { - environmentType, - miniProgramBridgeSupported: - capabilities.supportsRequestPayment === true, - miniProgramBridgeVersion: capabilities.bridgeVersion, - shellVersion: capabilities.shellVersion, - }; - } catch { - return { - environmentType, - miniProgramBridgeSupported: false, - }; + await ensureWechatJSSDK(); + } catch (error) { + console.warn("WeChat JSSDK init failed", error); } + + // Use navigateTo detection instead of postMessage-based capability + // handshake, since postMessage only delivers on web-view back/share + // and cannot be used for real-time communication. + const hasNavigateTo = + typeof window.wx?.miniProgram?.navigateTo === "function"; + + return { + environmentType, + miniProgramBridgeSupported: hasNavigateTo, + miniProgramBridgeVersion: hasNavigateTo ? "1.3.0" : undefined, + }; }; export const buildWechatPaymentClientContextQuery = (