/** 微信公众号 H5 网页授权(注册 / 绑定页无 code 时跳转) */ import { getWeChatOAuthConfig } from '@/api/wechat'; import { clearStashedInviteRef, readStashedInviteRef, stashInviteRef, } from '@/utils/inviteRefStorage'; export type WeChatH5Page = 'register' | 'bind' | 'login'; type WeChatOAuthRuntimeConfig = { appId: string; /** 规范化后的 origin,如 https://ideship.com;空则用当前页 */ redirectOrigin: string; }; let oauthConfigPromise: Promise | null = null; function normalizeRedirectOrigin(base: string | null | undefined): string { const raw = String(base ?? '').trim(); if (!raw) return ''; try { const url = new URL(raw.includes('://') ? raw : `https://${raw}`); return url.origin; } catch { return ''; } } /** 从其他配置 wechat_mp 取 AppId / OAuth 回调根(进程内缓存) */ export async function resolveWeChatMpOAuthConfig(): Promise { if (!oauthConfigPromise) { oauthConfigPromise = getWeChatOAuthConfig() .then((cfg) => { const appId = String(cfg.appId ?? '').trim(); if (!appId) { throw new Error('未配置微信公众号 AppId(其他配置 wechat_mp.AppKey)'); } return { appId, redirectOrigin: normalizeRedirectOrigin(cfg.oauthRedirectBase), }; }) .catch((err) => { oauthConfigPromise = null; throw err; }); } return oauthConfigPromise; } /** @deprecated 使用 stashInviteRef */ export function stashWeChatInviteRef(inviteCode?: string | null): void { stashInviteRef(inviteCode); } /** @deprecated 使用 readStashedInviteRef */ export function readStashedWeChatInviteRef(): string { return readStashedInviteRef(); } /** @deprecated 使用 clearStashedInviteRef */ export function clearStashedWeChatInviteRef(): void { clearStashedInviteRef(); } /** 从 OAuth state 解析邀请码(格式 register:CODE) */ export function parseInviteRefFromOAuthState(state: unknown): string { const raw = String(state ?? '').trim(); const match = raw.match(/^(?:register|bind):(.+)$/i); if (!match?.[1]) return ''; try { return decodeURIComponent(match[1]).trim(); } catch { return match[1].trim(); } } /** * 解析当前页邀请码:query.ref / query.invite → OAuth state → 7 天暂存 */ export function resolveWeChatInviteRef(query: Record): string { const fromQuery = String(query.ref ?? query.invite ?? '').trim(); if (fromQuery) { stashInviteRef(fromQuery); return fromQuery; } const fromState = parseInviteRefFromOAuthState(query.state); if (fromState) { stashInviteRef(fromState); return fromState; } return readStashedInviteRef(); } function resolveOAuthRedirectPath(page: WeChatH5Page, redirectOrigin: string): string { const origin = redirectOrigin || window.location.origin; if (page === 'login') { return `${origin}/h5/login`; } return `${origin}/wechat/${page}`; } /** * 跳转微信 OAuth。 * - AppId / 回调根来自其他配置 wechat_mp(AppKey、BaseUrl) * - register/bind:邀请码写入 state + 7 天暂存 * - login:可选把登录后回跳路径写入 state(login:/h5/orders) */ export async function redirectToWeChatOAuth( page: WeChatH5Page, inviteCode?: string | null, options?: { afterLoginPath?: string | null }, ): Promise { const { appId, redirectOrigin } = await resolveWeChatMpOAuthConfig(); const redirectUri = encodeURIComponent(resolveOAuthRedirectPath(page, redirectOrigin)); let state = page; if (page === 'login') { const after = String(options?.afterLoginPath ?? '').trim(); if (after.startsWith('/h5')) { state = `login:${encodeURIComponent(after)}`; } } else { const code = String(inviteCode ?? '').trim() || readStashedInviteRef(); if (code) stashInviteRef(code); state = code ? `${page}:${encodeURIComponent(code)}` : page; } window.location.replace( `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}` + `&redirect_uri=${redirectUri}` + `&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`, ); } /** 从 OAuth state 解析登录后回跳路径 */ export function parseAfterLoginPathFromOAuthState(state: unknown): string { const raw = String(state ?? '').trim(); const match = raw.match(/^login:(.+)$/i); if (!match?.[1]) return ''; try { const path = decodeURIComponent(match[1]).trim(); return path.startsWith('/h5') ? path : ''; } catch { const path = match[1].trim(); return path.startsWith('/h5') ? path : ''; } }