/** * 微信 H5 内 JSAPI 支付(与 PersonalWealthAnalysis 月度详批 doDirectPay 一致) * oauth2 code → createOrder → WeixinJSBridge.getBrandWCPayRequest */ import { paymentApi } from '@/api'; const APPID = 'wx1ca1ac7ad12123ac'; const PENDING_KEY = 'wx_pending_partner_apply'; declare const uni: any; export function isWechatBrowser(): boolean { if (typeof window === 'undefined') return false; return /MicroMessenger/i.test(navigator.userAgent || ''); } function getUrlCode(): string | null { if (typeof window === 'undefined') return null; let search = window.location.search; if (search === '' && window.location.hash.indexOf('?') > -1) { search = '?' + (window.location.hash.split('?')[1] || ''); } if (!search) return null; const reg = new RegExp('(^|&)code=([^&]*)(&|$)'); const r = search.substr(1).match(reg); return r ? decodeURIComponent(r[2]) : null; } function cleanCodeFromUrl() { try { const url = new URL(window.location.href); let changed = false; if (url.searchParams.has('code') || url.searchParams.has('state')) { url.searchParams.delete('code'); url.searchParams.delete('state'); changed = true; } if (url.hash.includes('?')) { const [hashPath, hashQuery] = url.hash.split('?'); const hashParams = new URLSearchParams(hashQuery || ''); if (hashParams.has('code') || hashParams.has('state')) { hashParams.delete('code'); hashParams.delete('state'); const nextHashQuery = hashParams.toString(); url.hash = nextHashQuery ? `${hashPath}?${nextHashQuery}` : hashPath; changed = true; } } if (changed) { window.history.replaceState(null, '', url.toString()); } } catch {} } export interface WechatJsapiPayParams { description: string; /** 与财运详批接口一致:元(如 99、18.8) */ totalAmountYuan: number; businessType: string; businessId: number; } export type WechatJsapiPayResult = | { ok: true; outTradeNo?: string } | { ok: false; redirected?: boolean; msg?: string }; function savePartnerPending(state: WechatJsapiPayParams) { try { localStorage.setItem(PENDING_KEY, JSON.stringify({ ...state, ts: Date.now() })); } catch {} } function getPartnerPending(): (WechatJsapiPayParams & { ts?: number }) | null { try { const raw = localStorage.getItem(PENDING_KEY); if (!raw) return null; const s = JSON.parse(raw); if (Date.now() - (s.ts || 0) > 5 * 60 * 1000) { localStorage.removeItem(PENDING_KEY); return null; } return s; } catch { return null; } } function clearPartnerPending() { try { localStorage.removeItem(PENDING_KEY); } catch {} } /** * 合伙人 / 支付弹窗:微信内走 JSAPI;无 code 时写 pending 并跳转授权页 */ export async function payWithWechatJsapiH5(params: WechatJsapiPayParams): Promise { if (!isWechatBrowser()) { uni.showToast({ title: '请在微信中打开', icon: 'none' }); return { ok: false, msg: 'not_wechat' }; } const code = getUrlCode(); if (!code) { savePartnerPending(params); const redirectUri = encodeURIComponent(window.location.href); window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APPID}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect`; return { ok: false, redirected: true }; } const stored = getPartnerPending(); const merged: WechatJsapiPayParams = { description: params.description || stored?.description || '推广合伙人权益', totalAmountYuan: Number(params.totalAmountYuan ?? stored?.totalAmountYuan ?? 0), businessType: params.businessType || stored?.businessType || 'partner_apply', businessId: Number(params.businessId || stored?.businessId || 0), }; cleanCodeFromUrl(); clearPartnerPending(); if (!merged.businessId) { uni.showToast({ title: '订单信息丢失,请重新发起支付', icon: 'none' }); return { ok: false, msg: 'no_business_id' }; } try { uni.showLoading({ title: '创建订单中...' }); const orderRes = await paymentApi.createOrder({ description: merged.description, total_amount: merged.totalAmountYuan, business_type: merged.businessType as any, business_id: merged.businessId, pay_type: 'jsapi', code, }); uni.hideLoading(); if (!orderRes?.appId || !orderRes?.paySign) { uni.showToast({ title: '获取支付参数失败', icon: 'none' }); return { ok: false, msg: 'no_pay_params' }; } const pp = orderRes; return await new Promise((resolve) => { const invoke = () => { (window as any).WeixinJSBridge.invoke( 'getBrandWCPayRequest', { appId: pp.appId, timeStamp: pp.timeStamp, nonceStr: pp.nonceStr, package: pp.package, signType: pp.signType || 'RSA', paySign: pp.paySign, }, (res: any) => { if (res.err_msg === 'get_brand_wcpay_request:ok') { uni.showToast({ title: '支付成功', icon: 'success' }); resolve({ ok: true, outTradeNo: orderRes.out_trade_no }); } else { uni.showToast({ title: res.err_msg === 'get_brand_wcpay_request:cancel' ? '已取消' : '支付失败', icon: 'none', }); resolve({ ok: false, msg: res.err_msg }); } } ); }; if (typeof (window as any).WeixinJSBridge === 'undefined') { document.addEventListener('WeixinJSBridgeReady', invoke, false); } else { invoke(); } }); } catch (error: any) { uni.hideLoading(); uni.showToast({ title: error.msg || '创建订单失败', icon: 'none' }); return { ok: false, msg: error?.msg || 'create_order_failed' }; } } /** * OAuth 回跳后由入口页调用一次:有 code + pending 时自动下单并调起支付 */ export async function tryResumePartnerPaymentAfterOAuth(): Promise { if (typeof window === 'undefined') return { ok: false }; if (!isWechatBrowser()) return { ok: false }; const pending = getPartnerPending(); if (!pending?.businessId) return { ok: false }; if (!getUrlCode()) return { ok: false }; return payWithWechatJsapiH5({ description: pending.description, totalAmountYuan: pending.totalAmountYuan, businessType: pending.businessType, businessId: pending.businessId, }); }