From d793749134f1e395f7c2685576377ee2c8bd42f0 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Thu, 10 Sep 2026 14:53:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(app):=20=E5=9C=A8=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=89=8B=E5=8A=BF=E5=86=85=E5=90=8C=E6=AD=A5=E8=B0=83=E8=B5=B7?= =?UTF-8?q?=E8=AE=A2=E9=98=85=EF=BC=8C=E9=A2=84=E7=BA=A6=E9=A1=B5=E9=A6=96?= =?UTF-8?q?=E6=AC=A1=E8=BF=9B=E5=85=A5=E5=AE=9A=E4=BD=8D=E5=88=B0=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E6=97=B6=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 微信要求授权框落在 tap 同步栈,先 await 会丢失弹层;订阅失败不再打断预约或支付。 Co-authored-by: Cursor --- .../src/components/BookingConfirmPopup.vue | 4 +- packages/app/src/pages/booking/index.vue | 56 ++++++++-- packages/app/src/pages/card/detail.vue | 10 +- packages/app/src/utils/wechat-subscription.ts | 102 ++++++++++-------- 4 files changed, 115 insertions(+), 57 deletions(-) diff --git a/packages/app/src/components/BookingConfirmPopup.vue b/packages/app/src/components/BookingConfirmPopup.vue index 5877e55..666eb0b 100644 --- a/packages/app/src/components/BookingConfirmPopup.vue +++ b/packages/app/src/components/BookingConfirmPopup.vue @@ -159,9 +159,7 @@ async function handleConfirm() { try { await requestBookingCreatedSubscriptionMessage() } catch (err: unknown) { - const message = err instanceof Error ? err.message : '订阅消息授权失败' - uni.showToast({ title: message, icon: 'none' }) - return + console.warn('[subscribe] booking confirm failed', err) } finally { requestingSubscribe.value = false } diff --git a/packages/app/src/pages/booking/index.vue b/packages/app/src/pages/booking/index.vue index b48265a..83ee40c 100644 --- a/packages/app/src/pages/booking/index.vue +++ b/packages/app/src/pages/booking/index.vue @@ -22,6 +22,8 @@ {{ filteredSlots.length }} 个时段 - + > + + @@ -79,14 +85,14 @@ diff --git a/packages/app/src/pages/card/detail.vue b/packages/app/src/pages/card/detail.vue index 0d51501..83f533e 100644 --- a/packages/app/src/pages/card/detail.vue +++ b/packages/app/src/pages/card/detail.vue @@ -357,7 +357,7 @@ import { get, post } from '../../utils/request' import { formatPrice, getCardCoverClass } from '../../utils/format' import { getSystemLayout } from '../../utils/system' import { useUserStore } from '../../stores/user' -import { requestOrderPaidSubscriptionMessage } from '../../utils/wechat-subscription' +import { requestBookingCreatedSubscriptionMessage } from '../../utils/wechat-subscription' import CustomNavBar from '../../components/CustomNavBar.vue' interface MyOrderStatusResponse { @@ -888,9 +888,14 @@ async function doPurchase() { paymentRedirecting.value = false paymentConfirmationSession.value++ pendingOrderId.value = '' - uni.showLoading({ title: '创建订单...' }) try { + // 必须在 tap 同步栈里调起订阅框;失败不打断支付。 + await requestBookingCreatedSubscriptionMessage().catch((error) => { + console.warn('[subscribe] purchase pre-subscribe failed', error) + }) + + uni.showLoading({ title: '创建订单...' }) const result = await post('/payment/create-order', { cardTypeId: card.value.id, }) @@ -910,7 +915,6 @@ async function doPurchase() { }) pendingOrderId.value = result.order.id - await requestOrderPaidSubscriptionMessage().catch(() => undefined) await settlePaidOrder(result.order.id) } catch (err: unknown) { uni.hideLoading() diff --git a/packages/app/src/utils/wechat-subscription.ts b/packages/app/src/utils/wechat-subscription.ts index b553950..181dfdc 100644 --- a/packages/app/src/utils/wechat-subscription.ts +++ b/packages/app/src/utils/wechat-subscription.ts @@ -86,20 +86,27 @@ function normalizeResult(result?: TemplateResult): SubscriptionMessageRequestIte return null } -async function fetchTemplateConfig(): Promise { +function getTemplateConfigSync(): SubscriptionMessageTemplateConfig | null { if (cachedConfig) { return cachedConfig } const stored = uni.getStorageSync(TEMPLATE_CONFIG_STORAGE_KEY) as SubscriptionMessageTemplateConfig | '' if (!stored || !Array.isArray(stored.templates)) { - throw new Error('订阅消息模板尚未初始化,请重新进入页面后重试') + return null } - const config: SubscriptionMessageTemplateConfig = { + cachedConfig = { templates: stored.templates.filter((item) => item.templateId), } - cachedConfig = config + return cachedConfig +} + +async function fetchTemplateConfig(): Promise { + const config = getTemplateConfigSync() + if (!config) { + throw new Error('订阅消息模板尚未初始化,请重新进入页面后重试') + } return config } @@ -136,37 +143,11 @@ async function reportResults(requests: SubscriptionMessageRequestItem[]): Promis await post('/user/subscription-messages/report', payload as unknown as Record) } -export async function requestSubscriptionMessage(scene: SubscriptionMessageScene): Promise { - if (!isMpWeixin()) { - return [] - } - - const config = await fetchTemplateConfig() - const templates = getTemplatesByScene(config, scene) - if (templates.length === 0) { - console.error('[subscribe] no templates matched scene', stringifyDebugPayload({ scene, config, debugContext: getSubscribeDebugContext() })) - return [] - } - - const templateIds = templates.map((item) => item.templateId) - const debugContext = getSubscribeDebugContext() - console.log('[subscribe] requestSubscribeMessage:start', stringifyDebugPayload({ scene, templateIds, templates, debugContext })) - - const result = await new Promise((resolve, reject) => { - uni.requestSubscribeMessage({ - tmplIds: templateIds, - success: (res) => { - console.log('[subscribe] requestSubscribeMessage:success', stringifyDebugPayload({ scene, response: res, templateIds, debugContext })) - resolve(res as RequestSubscribeMessageSuccess) - }, - fail: (err) => { - console.error('[subscribe] requestSubscribeMessage:fail', stringifyDebugPayload({ scene, error: err, templateIds, debugContext })) - reject(buildSubscribeError(err as RequestSubscribeMessageFail, scene, templateIds)) - }, - }) - }) - - const requests = templates +function normalizeSubscribeResults( + templates: SubscriptionMessageTemplate[], + result: RequestSubscribeMessageSuccess, +): SubscriptionMessageRequestItem[] { + return templates .map((item) => { const normalized = normalizeResult(result[item.templateId]) if (!normalized) { @@ -180,18 +161,53 @@ export async function requestSubscriptionMessage(scene: SubscriptionMessageScene } }) .filter((item): item is SubscriptionMessageRequestItem => item !== null) - - console.log('[subscribe] requestSubscribeMessage:normalized', stringifyDebugPayload({ scene, result, requests, templateIds, debugContext })) - - await reportResults(requests) - return requests } -export async function requestOrderPaidSubscriptionMessage(): Promise { - return requestSubscriptionMessage(SubscriptionMessageScene.BOOKING_CREATED) +/** + * 在当前调用栈同步调起 `uni.requestSubscribeMessage`。 + * 微信要求授权框必须落在 tap / 支付 success 的同步栈里,因此这里不能先 `await`。 + */ +export function requestSubscriptionMessage(scene: SubscriptionMessageScene): Promise { + if (!isMpWeixin()) { + return Promise.resolve([]) + } + + const config = getTemplateConfigSync() + if (!config) { + return Promise.reject(new Error('订阅消息模板尚未初始化,请重新进入页面后重试')) + } + + const templates = getTemplatesByScene(config, scene) + if (templates.length === 0) { + console.error('[subscribe] no templates matched scene', stringifyDebugPayload({ scene, config, debugContext: getSubscribeDebugContext() })) + return Promise.resolve([]) + } + + const templateIds = templates.map((item) => item.templateId) + const debugContext = getSubscribeDebugContext() + console.log('[subscribe] requestSubscribeMessage:start', stringifyDebugPayload({ scene, templateIds, templates, debugContext })) + + return new Promise((resolve, reject) => { + uni.requestSubscribeMessage({ + tmplIds: templateIds, + success: (res) => { + const response = res as RequestSubscribeMessageSuccess + const requests = normalizeSubscribeResults(templates, response) + console.log('[subscribe] requestSubscribeMessage:success', stringifyDebugPayload({ scene, response, templateIds, debugContext })) + console.log('[subscribe] requestSubscribeMessage:normalized', stringifyDebugPayload({ scene, result: response, requests, templateIds, debugContext })) + void reportResults(requests) + .then(() => resolve(requests)) + .catch(reject) + }, + fail: (err) => { + console.error('[subscribe] requestSubscribeMessage:fail', stringifyDebugPayload({ scene, error: err, templateIds, debugContext })) + reject(buildSubscribeError(err as RequestSubscribeMessageFail, scene, templateIds)) + }, + }) + }) } -export async function requestBookingCreatedSubscriptionMessage(): Promise { +export function requestBookingCreatedSubscriptionMessage(): Promise { return requestSubscriptionMessage(SubscriptionMessageScene.BOOKING_CREATED) }