upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
/**
* 微信 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,
});
}