200 lines
6.5 KiB
TypeScript
200 lines
6.5 KiB
TypeScript
/**
|
||
* 微信 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<WechatJsapiPayResult> {
|
||
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<WechatJsapiPayResult>((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<WechatJsapiPayResult> {
|
||
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,
|
||
});
|
||
}
|