Files
----/前端源码/uni-app/utils/wechat-h5-jsapi-pay.ts

200 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 微信 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,
});
}