fix: restore wechat payment oauth and jsapi flow
This commit is contained in:
@@ -309,6 +309,20 @@ const previewImage = ref('')
|
||||
|
||||
const paymentPhase = ref<'select' | 'paying'>('select')
|
||||
|
||||
interface CreateOrderOptions {
|
||||
openid?: string
|
||||
paymentType?: string
|
||||
isResume?: boolean
|
||||
}
|
||||
|
||||
interface WeixinJSBridgeLike {
|
||||
invoke(
|
||||
action: string,
|
||||
payload: Record<string, unknown>,
|
||||
callback: (result: Record<string, unknown>) => void,
|
||||
): void
|
||||
}
|
||||
|
||||
function emptyPaymentState(): PaymentRecoverySnapshot {
|
||||
return {
|
||||
orderId: 0,
|
||||
@@ -326,6 +340,48 @@ function emptyPaymentState(): PaymentRecoverySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
function readRouteQueryValue(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return typeof value[0] === 'string' ? value[0] : ''
|
||||
}
|
||||
return typeof value === 'string' ? value : ''
|
||||
}
|
||||
|
||||
function getWeixinJSBridge(): WeixinJSBridgeLike | undefined {
|
||||
return (window as Window & { WeixinJSBridge?: WeixinJSBridgeLike }).WeixinJSBridge
|
||||
}
|
||||
|
||||
function waitForWeixinJSBridge(timeoutMs = 4000): Promise<WeixinJSBridgeLike | null> {
|
||||
const existing = getWeixinJSBridge()
|
||||
if (existing) return Promise.resolve(existing)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false
|
||||
const finish = (bridge: WeixinJSBridgeLike | null) => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
document.removeEventListener('WeixinJSBridgeReady', handleReady)
|
||||
document.removeEventListener('onWeixinJSBridgeReady', handleReady)
|
||||
window.clearTimeout(timer)
|
||||
resolve(bridge)
|
||||
}
|
||||
const handleReady = () => finish(getWeixinJSBridge() ?? null)
|
||||
const timer = window.setTimeout(() => finish(getWeixinJSBridge() ?? null), timeoutMs)
|
||||
document.addEventListener('WeixinJSBridgeReady', handleReady, false)
|
||||
document.addEventListener('onWeixinJSBridgeReady', handleReady, false)
|
||||
})
|
||||
}
|
||||
|
||||
async function invokeWechatJsapiPayment(payload: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
const bridge = await waitForWeixinJSBridge()
|
||||
if (!bridge) {
|
||||
throw new Error('WeixinJSBridge is unavailable')
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
bridge.invoke('getBrandWCPayRequest', payload, (result) => resolve(result || {}))
|
||||
})
|
||||
}
|
||||
|
||||
const paymentState = ref<PaymentRecoverySnapshot>(emptyPaymentState())
|
||||
|
||||
function persistRecoverySnapshot(snapshot: PaymentRecoverySnapshot) {
|
||||
@@ -560,25 +616,32 @@ async function confirmSubscribe() {
|
||||
await createOrder(selectedPlan.value.price, 'subscription', selectedPlan.value.id)
|
||||
}
|
||||
|
||||
async function createOrder(orderAmount: number, orderType: OrderType, planId?: number) {
|
||||
async function createOrder(orderAmount: number, orderType: OrderType, planId?: number, options: CreateOrderOptions = {}) {
|
||||
submitting.value = true
|
||||
errorMessage.value = ''
|
||||
try {
|
||||
const result = await paymentStore.createOrder(buildCreateOrderPayload({
|
||||
const requestType = normalizeVisibleMethod(options.paymentType || selectedMethod.value) || options.paymentType || selectedMethod.value
|
||||
const payload = buildCreateOrderPayload({
|
||||
amount: orderAmount,
|
||||
paymentType: selectedMethod.value,
|
||||
paymentType: requestType,
|
||||
orderType,
|
||||
planId,
|
||||
origin: typeof window !== 'undefined' ? window.location.origin : '',
|
||||
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
||||
})) as CreateOrderResult & { resume_token?: string }
|
||||
})
|
||||
if (options.openid) {
|
||||
payload.openid = options.openid
|
||||
}
|
||||
payload.is_mobile = isMobileDevice()
|
||||
|
||||
const result = await paymentStore.createOrder(payload) as CreateOrderResult & { resume_token?: string }
|
||||
const openWindow = (url: string, features = POPUP_WINDOW_FEATURES) => {
|
||||
const win = window.open(url, 'paymentPopup', features)
|
||||
if (!win || win.closed) {
|
||||
window.location.href = url
|
||||
}
|
||||
}
|
||||
const visibleMethod = normalizeVisibleMethod(selectedMethod.value) || selectedMethod.value
|
||||
const visibleMethod = normalizeVisibleMethod(requestType) || requestType
|
||||
const stripeMethod = visibleMethod === 'wxpay' ? 'wechat_pay' : 'alipay'
|
||||
const stripeRouteUrl = result.client_secret
|
||||
? router.resolve({
|
||||
@@ -599,6 +662,11 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
||||
stripeRouteUrl,
|
||||
})
|
||||
|
||||
if (decision.kind === 'wechat_oauth' && decision.oauth?.authorize_url) {
|
||||
window.location.href = decision.oauth.authorize_url
|
||||
return
|
||||
}
|
||||
|
||||
if (decision.kind === 'unhandled') {
|
||||
errorMessage.value = t('payment.result.failed')
|
||||
appStore.showError(errorMessage.value)
|
||||
@@ -617,6 +685,16 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
||||
window.location.href = decision.paymentState.payUrl
|
||||
return
|
||||
}
|
||||
if (decision.kind === 'wechat_jsapi' && decision.jsapi) {
|
||||
const jsapiResult = await invokeWechatJsapiPayment(decision.jsapi as Record<string, unknown>)
|
||||
const errMsg = String(jsapiResult.err_msg || '').toLowerCase()
|
||||
if (errMsg.includes('cancel')) {
|
||||
appStore.showInfo(t('payment.qr.cancelled'))
|
||||
} else if (errMsg && !errMsg.includes('ok')) {
|
||||
appStore.showError(t('payment.result.failed'))
|
||||
}
|
||||
return
|
||||
}
|
||||
if (decision.kind === 'redirect_waiting' && decision.paymentState.payUrl) {
|
||||
if (isMobileDevice()) {
|
||||
window.location.href = decision.paymentState.payUrl
|
||||
@@ -640,6 +718,50 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
||||
}
|
||||
}
|
||||
|
||||
async function resumeWechatPaymentFromQuery() {
|
||||
const openid = readRouteQueryValue(route.query.openid)
|
||||
if (readRouteQueryValue(route.query.wechat_resume) !== '1' || !openid) {
|
||||
return
|
||||
}
|
||||
|
||||
const paymentType = normalizeVisibleMethod(readRouteQueryValue(route.query.payment_type)) || 'wxpay'
|
||||
const orderType = readRouteQueryValue(route.query.order_type) === 'subscription' ? 'subscription' : 'balance'
|
||||
const planId = Number.parseInt(readRouteQueryValue(route.query.plan_id), 10)
|
||||
const rawAmount = Number.parseFloat(readRouteQueryValue(route.query.amount))
|
||||
const orderAmount = Number.isFinite(rawAmount) && rawAmount > 0
|
||||
? rawAmount
|
||||
: (orderType === 'subscription'
|
||||
? (checkout.value.plans.find(plan => plan.id === planId)?.price ?? 0)
|
||||
: validAmount.value)
|
||||
|
||||
selectedMethod.value = paymentType
|
||||
if (orderType === 'balance' && orderAmount > 0) {
|
||||
amount.value = orderAmount
|
||||
}
|
||||
if (orderType === 'subscription' && Number.isFinite(planId) && planId > 0) {
|
||||
selectedPlan.value = checkout.value.plans.find(plan => plan.id === planId) ?? null
|
||||
}
|
||||
|
||||
const nextQuery = { ...route.query }
|
||||
delete nextQuery.wechat_resume
|
||||
delete nextQuery.openid
|
||||
delete nextQuery.state
|
||||
delete nextQuery.scope
|
||||
delete nextQuery.payment_type
|
||||
delete nextQuery.amount
|
||||
delete nextQuery.order_type
|
||||
delete nextQuery.plan_id
|
||||
await router.replace({ path: route.path, query: nextQuery })
|
||||
|
||||
if (orderAmount > 0) {
|
||||
await createOrder(orderAmount, orderType, Number.isFinite(planId) && planId > 0 ? planId : undefined, {
|
||||
openid,
|
||||
paymentType,
|
||||
isResume: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const res = await paymentAPI.getCheckoutInfo()
|
||||
@@ -672,6 +794,7 @@ onMounted(async () => {
|
||||
removeRecoverySnapshot()
|
||||
}
|
||||
}
|
||||
await resumeWechatPaymentFromQuery()
|
||||
if (checkout.value.balance_disabled) {
|
||||
activeTab.value = 'subscription'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user