95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import type {
|
|
ClientRuntimeConfig,
|
|
WechatJsapiPrepayParams,
|
|
WechatLoginResult,
|
|
WechatPayOrderResult,
|
|
} from '@dukang/shared-types';
|
|
import { WECHAT_AUTH_REQUIRED, isWxAuthorizeEnabled } from '@dukang/shared-types';
|
|
import { invokeWechatPay } from '@dukang/weixin-sdk';
|
|
import { request, saveAuth, type UserProfile } from './api';
|
|
import { isWechatEnv, weixinSdk } from './weixin';
|
|
|
|
export function isMiniWechatEnv(): boolean {
|
|
return process.env.TARO_ENV === 'weapp';
|
|
}
|
|
|
|
export function isWechatAuthRequiredError(err: unknown): boolean {
|
|
return err instanceof Error && err.message === WECHAT_AUTH_REQUIRED;
|
|
}
|
|
|
|
export async function fetchClientConfig(): Promise<ClientRuntimeConfig> {
|
|
return request<ClientRuntimeConfig>('/common/client-config');
|
|
}
|
|
|
|
export async function fetchUserProfile(): Promise<UserProfile> {
|
|
return request<UserProfile>('/auth/me');
|
|
}
|
|
|
|
/** 真实微信支付且未绑定微信时需要授权(小程序 / H5 微信内) */
|
|
export function needsWechatAuthForPay(
|
|
config: ClientRuntimeConfig,
|
|
profile: UserProfile | null,
|
|
): boolean {
|
|
if (!isWxAuthorizeEnabled(config)) return false;
|
|
return !config.mockPay && config.wechatPayEnabled && isWechatEnv() && !profile?.hasWechat;
|
|
}
|
|
|
|
export function saveWechatLoginResult(result: WechatLoginResult): boolean {
|
|
if (!result.accessToken) return false;
|
|
saveAuth({
|
|
accessToken: result.accessToken,
|
|
refreshToken: result.refreshToken,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
/** H5:发起公众号 OAuth(可能直接跳转);小程序请用 bindWechatForUser */
|
|
export async function authorizeWechatForPay(): Promise<WechatLoginResult | void> {
|
|
const config = await fetchClientConfig();
|
|
if (!isWxAuthorizeEnabled(config)) return;
|
|
if (!isWechatEnv()) {
|
|
throw new Error('请在微信内打开以完成授权');
|
|
}
|
|
if (process.env.TARO_ENV === 'h5') {
|
|
return weixinSdk.login();
|
|
}
|
|
throw new Error('请使用小程序微信授权');
|
|
}
|
|
|
|
function sleep(ms: number) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
export async function waitOrderPaid(orderId: string, maxAttempts = 15): Promise<boolean> {
|
|
for (let i = 0; i < maxAttempts; i += 1) {
|
|
const order = await request<{ payStatus?: string }>(`/trade/orders/${orderId}`);
|
|
if (order.payStatus === 'PAID') return true;
|
|
await sleep(2000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export async function payOrder(orderId: string): Promise<'paid' | 'pending'> {
|
|
const result = await request<WechatPayOrderResult>(`/trade/orders/${orderId}/pay`, {
|
|
method: 'POST',
|
|
});
|
|
|
|
if (result.mode === 'jsapi' && result.prepay) {
|
|
const prepay = result.prepay as WechatJsapiPrepayParams;
|
|
if (process.env.TARO_ENV === 'h5') {
|
|
await weixinSdk.pay(prepay);
|
|
} else {
|
|
await invokeWechatPay(prepay, { platform: 'mini' });
|
|
}
|
|
const paid = await waitOrderPaid(orderId);
|
|
return paid ? 'paid' : 'pending';
|
|
}
|
|
|
|
return 'paid';
|
|
}
|
|
|
|
export type WechatBindResult =
|
|
| { ok: true; profile?: UserProfile }
|
|
| { ok: false; needBindPhone: true; wxSessionKey: string }
|
|
| { ok: false; redirecting: true };
|