import type { ClientRuntimeConfig, WechatLoginResult } from '@dukang/shared-types'; import { isWxAuthorizeEnabled } from '@dukang/shared-types'; import { WECHAT_INAPP_REQUIRED_MSG } from '@dukang/weixin-sdk'; import { isWechatEnv, weixinSdk } from './weixin'; import { markScanWarmupAfterAuth } from './shop-scan-auth'; import { request, saveWechatSession, type ShopSessionPayload } from './api'; export type ShopAccountProfile = { id: string; storeId: string; name: string; phone: string; /** 是否已绑定微信(来自 /shop/auth/me account.hasWechat) */ hasWechat?: boolean; wxOpenId?: string | null; store?: { name: string }; }; /** 微信未绑定门店账号时的登录提示 */ export const SHOP_WX_NEED_PHONE_LOGIN_MSG = '请先用手机号登录'; const SHOP_WX_LOGIN_HINT_KEY = 'shop_wx_login_hint'; export function isShopWechatUnboundError(message: string): boolean { return message.includes('首次登录') || message.includes('手机验证码') || message.includes('请先用手机号'); } export function formatShopWechatError(e: unknown): string { const text = e instanceof Error ? e.message : '微信登录失败'; if (isShopWechatUnboundError(text)) return SHOP_WX_NEED_PHONE_LOGIN_MSG; return text; } /** OAuth 在启动层失败时暂存提示,供登录页展示 */ export function stashShopWechatLoginHint(message: string): void { try { sessionStorage.setItem(SHOP_WX_LOGIN_HINT_KEY, message); } catch { /* ignore */ } } export function consumeShopWechatLoginHint(): string | null { try { const hint = sessionStorage.getItem(SHOP_WX_LOGIN_HINT_KEY); if (hint) sessionStorage.removeItem(SHOP_WX_LOGIN_HINT_KEY); return hint; } catch { return null; } } export async function fetchClientConfig(): Promise { return request('SHOP_H5', '/common/client-config'); } export async function fetchShopAccount(): Promise { const me = await request<{ id?: string; storeId?: string; name?: string; phone?: string; account?: { id?: string; name?: string; phone?: string; hasWechat?: boolean; }; store?: { name?: string } | null; }>('SHOP_H5', '/shop/auth/me'); return { id: String(me.account?.id ?? me.id ?? ''), storeId: String(me.storeId ?? ''), name: String(me.account?.name ?? me.name ?? ''), phone: String(me.account?.phone ?? me.phone ?? ''), hasWechat: !!me.account?.hasWechat, store: me.store?.name ? { name: String(me.store.name) } : undefined, }; } export function needsWechatAuth( profile: ShopAccountProfile | null, config?: Pick | null, ): boolean { if (config && !isWxAuthorizeEnabled(config)) return false; const unbound = !!profile && !(profile.hasWechat || profile.wxOpenId); return isWechatEnv() && unbound; } export async function checkNeedsWechatAuth(profile: ShopAccountProfile | null): Promise { const config = await fetchClientConfig(); return needsWechatAuth(profile, config); } export function sessionFromWechatLogin(result: WechatLoginResult): ShopSessionPayload | null { if (!result.accessToken || !result.refreshToken) return null; const store = result.store as Record | undefined; const stores = Array.isArray((result as { stores?: unknown }).stores) ? ((result as { stores: ShopSessionPayload['stores'] }).stores) : undefined; const account = (result as { account?: ShopSessionPayload['account'] }).account; return { accessToken: result.accessToken, refreshToken: result.refreshToken, stores, account, selectedStoreId: (result as { selectedStoreId?: string }).selectedStoreId, store: store ? { id: String(store.id ?? account?.id ?? ''), storeId: String(store.storeId ?? ''), name: String(store.name ?? account?.name ?? ''), phone: String(store.phone ?? account?.phone ?? ''), storeName: String(store.storeName ?? store.name ?? ''), isPrimary: account?.isPrimary, stores, } : undefined, }; } /** 处理微信登录/绑定结果,写入 7 天免登录 session */ export function handleShopWechatLoginResult(result: WechatLoginResult): ShopSessionPayload | null { const session = sessionFromWechatLogin(result); if (!session) return null; saveWechatSession(session); return session; } /** @deprecated 使用 handleShopWechatLoginResult */ export function saveShopWechatAuth(result: WechatLoginResult): boolean { return !!handleShopWechatLoginResult(result); } export async function handleShopWechatCallback(): Promise { if (!isWechatEnv()) return null; const config = await fetchClientConfig(); if (!isWxAuthorizeEnabled(config)) return null; return weixinSdk.handleOAuthCallback(); } let oauthCallbackInflight: Promise | null = null; /** 全局单例:避免 Context 与页面 effect 重复消费 OAuth code */ export async function handleShopWechatCallbackOnce(): Promise { if (!isWechatEnv()) return null; const params = new URLSearchParams(window.location.search); if (!params.get('code')) return null; if (oauthCallbackInflight) return oauthCallbackInflight; oauthCallbackInflight = handleShopWechatCallback().finally(() => { oauthCallbackInflight = null; }); return oauthCallbackInflight; } /** * 微信一键登录(已绑定微信的门店账号免验证码)。 * 返回 session = 已登录;void = 已跳转授权页等待回调。 */ export async function loginShopWithWechat(): Promise { const config = await fetchClientConfig(); if (!isWxAuthorizeEnabled(config)) return null; if (!isWechatEnv()) { throw new Error(WECHAT_INAPP_REQUIRED_MSG); } markScanWarmupAfterAuth(); const result = await weixinSdk.login(); if (result) return handleShopWechatLoginResult(result); } /** 短信登录成功后于微信内自动发起 OAuth,绑定 openId 便于后续免登 */ export async function bindShopWechatAfterSmsLogin(session?: ShopSessionPayload): Promise<'skipped' | void> { const config = await fetchClientConfig(); if (!isWxAuthorizeEnabled(config)) return 'skipped'; if (!isWechatEnv()) return 'skipped'; // 已绑定则勿再 OAuth:每次 OAuth 回跳都会重置 iOS JSSDK 入场 URL,易导致扫码失败 if (session?.account?.hasWechat) return 'skipped'; markScanWarmupAfterAuth(); await weixinSdk.login(); } export async function authorizeShopWechat(): Promise { const config = await fetchClientConfig(); if (!isWxAuthorizeEnabled(config)) return; if (!isWechatEnv()) { throw new Error(WECHAT_INAPP_REQUIRED_MSG); } markScanWarmupAfterAuth(); return weixinSdk.login(); }