import { getRuntimePlatform, isIosDevice, isWechatDevTools } from './env'; import { ensureJssdkReady } from './jssdk'; import type { WeixinSdkConfig } from './types'; function delay(ms: number): Promise { return new Promise((resolve) => window.setTimeout(resolve, ms)); } /** 将微信 scanQRCode fail 的 errMsg 转为用户可读文案(避免误判为系统相机权限) */ export function formatScanFailMessage(errMsg: string): string { const msg = errMsg.trim() || '扫码失败'; if (/cancel/i.test(msg)) return ''; // iOS 常见:JSSDK 权限离线校验尚未完成,并非系统相机权限 if (/offline verifying|permission value is offline/i.test(msg)) { return '微信权限验证中,请稍候再试或刷新页面后重新扫码'; } if (/invalid signature|config:fail|signature/i.test(msg)) { return '微信 JSSDK 签名校验失败,请刷新页面后重试'; } // 系统级相机权限(iPhone 设置 → 隐私 → 相机 → 微信) if (/system.*camera|NotAllowedError|camera.*not.*allowed|无法访问相机|无相机权限/i.test(msg)) { return '相机权限未开启,请在 iPhone「设置 → 隐私与安全性 → 相机」中允许微信使用摄像头'; } // JSSDK 接口授权失败(含 permission,但不等于相机权限) if (/permission|auth|denied|授权|拒绝/i.test(msg)) { return `微信扫码接口未就绪(${msg}),请刷新页面后重试`; } return msg; } function invokeScanQrCode(): Promise { return new Promise((resolve, reject) => { window.wx!.scanQRCode!({ needResult: 1, scanType: ['qrCode', 'barCode'], success: (res) => resolve(res.resultStr || null), fail: (res) => { const msg = res.errMsg || '扫码失败'; if (/cancel/i.test(msg)) { resolve(null); return; } const formatted = formatScanFailMessage(msg); reject(new Error(formatted || msg)); }, }); }); } /** 调起扫码(返回二维码/条码内容,失败返回 null) */ export async function scanQrCode(config: WeixinSdkConfig): Promise { const platform = getRuntimePlatform(); if (platform === 'mini' && window.wx?.scanCode) { return new Promise((resolve) => { window.wx!.scanCode!({ onlyFromCamera: true, scanType: ['qrCode', 'barCode'], success: (res) => resolve(res.result || null), fail: () => resolve(null), }); }); } if (platform === 'wechat-h5') { await ensureJssdkReady({ apiBase: config.apiBase ?? '/api/v1', clientApp: config.clientApp, getAccessToken: config.getAccessToken, }); if (!window.wx?.scanQRCode) { throw new Error('当前微信版本不支持扫码,请升级微信后重试'); } // iOS 在 wx.ready 后立即调 scanQRCode 可能触发 offline verifying if (isIosDevice() && !isWechatDevTools()) { await delay(500); } const maxAttempts = 2; let lastError: Error | null = null; for (let attempt = 0; attempt < maxAttempts; attempt++) { if (attempt > 0) { await delay(800); } try { return await invokeScanQrCode(); } catch (e) { lastError = e instanceof Error ? e : new Error('扫码失败'); const raw = lastError.message; const retryable = /offline verifying|权限验证中|接口未就绪/i.test(raw); if (attempt < maxAttempts - 1 && retryable) continue; throw lastError; } } throw lastError ?? new Error('扫码失败'); } throw new Error('请在微信内打开以使用扫码核销'); }