商铺端核销,调用相机报无效签名修复

This commit is contained in:
2026-07-07 17:55:12 +08:00
parent 5fef6ae2b1
commit 67eed11276
4 changed files with 54 additions and 5 deletions
+12 -2
View File
@@ -12,6 +12,7 @@ import {
sessionFromWechatLogin,
} from '../lib/wechat-auth';
import { isWechatEnv, weixinSdk } from '../lib/weixin';
import { stripOAuthParamsFromLocation } from '@dukang/weixin-sdk';
import WechatScanAuthModal from '../components/WechatScanAuthModal';
const PENDING_SCAN_KEY = 'shop_pending_scan';
@@ -20,6 +21,14 @@ function formatMoney(n: number) {
return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
}
function formatScanError(e: unknown): string {
const msg = e instanceof Error ? e.message : '扫码失败,请重试';
if (/invalid signature/i.test(msg)) {
return '微信 JSSDK 签名校验失败:请确认公众号已配置 JS 接口安全域名为 shop.runxian.top,并刷新页面后重试';
}
return msg;
}
export default function HomePage() {
const navigate = useNavigate();
const { applySession } = useStoreSession();
@@ -51,11 +60,12 @@ export default function HomePage() {
}
setAuthModalOpen(false);
setAuthError('');
stripOAuthParamsFromLocation();
setSearchParams({}, { replace: true });
const shouldScan = sessionStorage.getItem(PENDING_SCAN_KEY) === '1';
sessionStorage.removeItem(PENDING_SCAN_KEY);
if (shouldScan) {
void runScan();
window.setTimeout(() => void runScan(), 0);
}
})
.catch((e) => {
@@ -81,7 +91,7 @@ export default function HomePage() {
}
navigate(`/redeem?token=${encodeURIComponent(token)}`);
} catch (e) {
setScanMsg(e instanceof Error ? e.message : '扫码失败,请重试');
setScanMsg(formatScanError(e));
} finally {
setScanning(false);
}
+1 -1
View File
@@ -1,5 +1,5 @@
export { isWechatBrowser, isMiniProgram, getRuntimePlatform } from './env';
export { initWechatJssdk, ensureJssdkReady, isJssdkReady } from './jssdk';
export { initWechatJssdk, ensureJssdkReady, isJssdkReady, normalizeJssdkPageUrl, stripOAuthParamsFromLocation } from './jssdk';
export {
getWechatLocation,
getWechatLocationDetailed,
+26 -1
View File
@@ -8,7 +8,32 @@ let configured = false;
let configuredUrl: string | null = null;
function currentPageUrl() {
return typeof window !== 'undefined' ? window.location.href.split('#')[0] : '';
return typeof window !== 'undefined' ? normalizeJssdkPageUrl(window.location.href) : '';
}
/** 参与 JSSDK 签名的页面 URL(去掉 hash、OAuth 回跳参数) */
export function normalizeJssdkPageUrl(rawUrl: string): string {
try {
const url = new URL(rawUrl);
url.hash = '';
url.searchParams.delete('code');
url.searchParams.delete('state');
const query = url.searchParams.toString();
return `${url.origin}${url.pathname}${query ? `?${query}` : ''}`;
} catch {
return rawUrl.split('#')[0];
}
}
export function stripOAuthParamsFromLocation(): void {
if (typeof window === 'undefined') return;
const url = new URL(window.location.href);
if (!url.searchParams.has('code') && !url.searchParams.has('state')) return;
url.searchParams.delete('code');
url.searchParams.delete('state');
const query = url.searchParams.toString();
const next = `${url.pathname}${query ? `?${query}` : ''}${url.hash}`;
window.history.replaceState({}, '', next);
}
function loadScript(): Promise<void> {
@@ -75,13 +75,27 @@ export class WechatController {
@Get('jssdk-config')
async jssdkConfig(@Query('url') url: string, @Req() req: Request) {
if (!url) throw new BadRequestException('url 参数必填');
const pageUrl = decodeURIComponent(url).split('#')[0];
const decoded = decodeURIComponent(url).split('#')[0];
const pageUrl = this.normalizeJssdkUrl(decoded);
const user = (req as Request & { user?: AuthUser }).user;
const actorRef =
user?.actorType === 'USER' ? { refType: 'USER', refId: user.actorId } : undefined;
return this.wechat.createJssdkConfig(pageUrl, actorRef);
}
private normalizeJssdkUrl(rawUrl: string): string {
try {
const parsed = new URL(rawUrl);
parsed.hash = '';
parsed.searchParams.delete('code');
parsed.searchParams.delete('state');
const query = parsed.searchParams.toString();
return `${parsed.origin}${parsed.pathname}${query ? `?${query}` : ''}`;
} catch {
return rawUrl;
}
}
@Get('oauth-url')
oauthUrl(
@Query('redirectUri') redirectUri: string,