收口微信支付订阅诊断日志

微信支付和订阅失败日志改为固定阶段标签

原生壳门禁拒绝支付订阅日志输出原生错误对象

同步宿主壳方案和共享决策记录
This commit is contained in:
2026-06-21 15:32:54 +08:00
parent 0b848d38bb
commit c84c77616c
7 changed files with 35 additions and 12 deletions
@@ -105,6 +105,7 @@
- 2026-06-18 微信壳桥接层纳入统一验收:`npm run check:native-shells` 还会运行 `miniprogram/host-bridge/``miniprogram/shell/``pages/web-view` 样式和 `scripts/miniprogram-web-view-auth.test.ts` 的微信壳测试,覆盖 WebView 入口、登录触发、分享目标、支付结果、订阅消息结果和九宫切图行为;三端桥接层文件结构检查只证明目录边界,行为回归必须由同一门禁中的微信壳测试证明。
- 2026-06-21 微信 WebView env 诊断日志收口:微信 `web-view` 壳读取小程序 envVersion 失败时只记录 `[web-view] read mini program env failed` 固定标签,不输出 `wx.getAccountInfoSync()` 原生异常对象;根级原生壳门禁拒绝恢复 `console.warn(..., error)`
- 2026-06-21 微信九宫切图诊断日志收口:微信 `share-grid` 壳下载封面、读取图片、导出切图、保存相册或最终保存流程失败时,只记录 `[share-grid] <stage>` 固定标签,不输出 `wx.downloadFile``wx.getImageInfo``wx.canvasToTempFilePath``wx.saveImageToPhotosAlbum` 或其它原生错误对象;页面仍只展示 `九宫切图保存失败。` 稳定文案。
- 2026-06-21 微信支付与订阅诊断日志收口:微信支付参数解析、虚拟支付失败和订阅消息请求失败只记录 `[wechat-pay] <stage>` / `[subscribe-message] <stage>` 固定标签,不输出微信原生错误对象;H5 回灌继续只使用稳定 `wechat payment unavailable` / `wechat subscribe unavailable` 语义。
- 2026-06-19 微信壳路由一致性门禁:`npm run check:native-shells` 必须反查 `miniprogram/app.json.pages``miniprogram/host-bridge/protocol.js`、H5 `src/services/host-bridge/hostBridge.ts` 小程序页面常量、H5 `src/services/wechatMiniProgramSubscribe.ts` 订阅授权页面常量、`miniprogram/host-bridge/webView.js` 分享入口 / 分享消息类型、`miniprogram/config.js` source query / 域名格式、`miniprogram/shell/webView.js` 请求头来源标记、H5 runtime parser 和 H5 路由保留字段。新增或调整小程序页面、登录派生 URL、支付页、九宫切图页、订阅页、WebView 来源标记、H5 入口域名、API base URL 或宿主上下文 query 字段时,必须同步这几处常量并保持生产 / 开发域名都显式配置为纯 HTTPS domain;运行时开发域名回退生产域名只作为异常兜底。
- 2026-06-18 登录 / 支付能力禁伪声明:`auth.requestLogin``payment.request` 保留在共享 HostBridge 契约中供未来真实接入,但 Expo / Tauri 壳在真实 SDK、渠道流程和后端契约落地前不得声明这些 capability,也不得把它们写入入口 URL `hostCapabilities`;两端检查脚本会拒绝伪声明,请求实际到达壳层时必须返回明确 `unsupported_method` 并让 H5 fallback,两端壳测试直接覆盖这两个 method。
- 2026-06-20 桌面壳未声明 method 禁伪成功:Tauri 桌面壳只声明真实可用 capability;共享 HostBridge method 白名单中未进入桌面 capability profile 的 method,例如 `auth.requestLogin``payment.request``file.captureImage``scanner.scanQrCode``haptics.impact`,请求实际到达桌面壳时必须统一返回明确 `unsupported_method`,不得伪造成功或半接入。桌面 Rust 测试必须从 `HOST_BRIDGE_METHODS``capabilities()` 差集派生 unsupported 覆盖清单,桌面单端配置检查会反查该派生路径,后续共享契约新增 method 时必须同步声明真实桌面能力或补进 unsupported 语义。
@@ -540,6 +540,8 @@ GameBridge 禁止:
2026-06-21 追加:微信九宫切图壳的下载、图片读取、切图导出、保存相册和最终保存失败诊断只允许记录 `[share-grid] <stage>` 固定标签,不把微信原生错误对象或后端细节写入生产日志;页面仍只展示 `九宫切图保存失败。` 稳定文案。
2026-06-21 追加:微信支付参数解析、虚拟支付失败和订阅消息请求失败诊断只允许记录 `[wechat-pay] <stage>` / `[subscribe-message] <stage>` 固定标签,不把微信原生错误对象写入生产日志;H5 回灌仍只使用稳定支付 / 订阅失败语义。
2026-06-19 追加:微信小程序壳路由一致性进入原生壳统一验收。`npm run check:native-shells` 会反查 `miniprogram/app.json.pages``miniprogram/host-bridge/protocol.js` 页面 URL 常量一致,H5 `src/services/host-bridge/hostBridge.ts` 的小程序登录、支付、九宫切图页面常量、H5 `src/services/wechatMiniProgramSubscribe.ts` 的订阅授权页面常量与微信协议常量一致,`miniprogram/host-bridge/webView.js` 的 WebView 分享入口和分享目标消息类型不漂移;同时会校验 `miniprogram/config.js` 的生产 / 开发 H5 入口与 API base URL 都是显式配置的纯 HTTPS 域名,`WEB_VIEW_SOURCE_QUERY` 与共享 `HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY` 一致,`miniprogram/shell/webView.js` 请求头从 `WEB_VIEW_SOURCE_QUERY` 读取 `clientType` / `clientRuntime`H5 runtime parser 读取共享 `HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY`H5 路由保留字段读取共享 `HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS`。运行时对开发域名的生产域名回退只作为异常兜底,不作为配置口径;新增小程序页面、改页面路径、调整来源 query 或切换域名格式时,必须同步协议常量、H5 HostBridge / 订阅服务常量、`app.json`、共享 HostBridge query 契约和这条门禁。
2026-06-18 追加:原生壳本地生成物不作为生产源码门禁输入。Expo `.expo/`、Expo export smoke 临时目录、Tauri `target/`、Tauri schema `gen/` 和 Tauri 自动生成权限目录都必须保持 gitignored;根级生产壳敏感词扫描只检查可提交的壳源码和配置,避免本机工具输出影响生产门禁。手写 capability / 权限配置仍需保留在扫描范围内。
+6 -2
View File
@@ -8,11 +8,15 @@ function parsePayParams(rawValue) {
}
return params;
} catch (error) {
console.error('[wechat-pay] parse params failed', error);
logWechatPayFailure('parse params failed', error);
return null;
}
}
function logWechatPayFailure(label, _error) {
console.error(`[wechat-pay] ${label}`);
}
function isVirtualPaymentParams(payParams) {
return (
typeof payParams.mode === 'string' &&
@@ -111,7 +115,7 @@ function requestVirtualPayment(payParams) {
resolve({ status: 'success', errorMessage: '' });
},
fail(error) {
console.error('[wechat-pay] requestVirtualPayment failed', error);
logWechatPayFailure('requestVirtualPayment failed', error);
resolve({
status: resolvePayStatus(error),
errorMessage: normalizePayError(error),
+1 -1
View File
@@ -129,8 +129,8 @@ describe('wechat-pay mini program payment bridge', () => {
});
expect(console.error).toHaveBeenCalledWith(
'[wechat-pay] requestVirtualPayment failed',
payError,
);
expect(console.error.mock.calls.flat()).not.toContain(payError);
});
test('hides ordinary payment native failure details from H5 result', async () => {
+5 -1
View File
@@ -3,6 +3,10 @@
const SUBSCRIBE_RESULT_STORAGE_KEY = 'genarrative:wechat-subscribe-result';
const WECHAT_SUBSCRIBE_UNAVAILABLE_REASON = 'wechat subscribe unavailable';
function logWechatSubscribeFailure(label, _error) {
console.error(`[subscribe-message] ${label}`);
}
function appendSubscribeResult(url, result) {
const hashIndex = String(url || '').indexOf('#');
const baseUrl =
@@ -99,7 +103,7 @@ function createSubscribeMessagePageController(pageContext, options = {}) {
wx.navigateBack();
},
fail(error) {
console.error('[subscribe-message] request failed', error);
logWechatSubscribeFailure('request failed', error);
notifyPageResult(
page,
'skip',
@@ -88,10 +88,8 @@ describe('subscribe-message mini program bridge', () => {
SUBSCRIBE_RESULT_STORAGE_KEY,
'request-fail:skip:wechat%20subscribe%20unavailable',
);
expect(console.error).toHaveBeenCalledWith(
'[subscribe-message] request failed',
subscribeError,
);
expect(console.error).toHaveBeenCalledWith('[subscribe-message] request failed');
expect(console.error.mock.calls.flat()).not.toContain(subscribeError);
expect(globalThis.wx.navigateBack).toHaveBeenCalled();
});
+18 -4
View File
@@ -2653,6 +2653,10 @@ function assertWechatPaymentResultBoundaries() {
for (const snippet of [
"function normalizePayError()",
"return 'wechat payment unavailable'",
'function logWechatPayFailure(label, _error)',
'console.error(`[wechat-pay] ${label}`)',
"logWechatPayFailure('parse params failed', error)",
"logWechatPayFailure('requestVirtualPayment failed', error)",
]) {
if (!paymentSource.includes(snippet)) {
throw new Error(`wechat payment bridge must include ${snippet}`);
@@ -2660,7 +2664,9 @@ function assertWechatPaymentResultBoundaries() {
}
if (
paymentSource.includes('JSON.stringify({\n errCode') ||
paymentSource.includes('String(error.errMsg || error)')
paymentSource.includes('String(error.errMsg || error)') ||
paymentSource.includes("console.error('[wechat-pay] parse params failed', error)") ||
paymentSource.includes("console.error('[wechat-pay] requestVirtualPayment failed', error)")
) {
throw new Error('wechat payment bridge must not expose native payment errors to H5');
}
@@ -2668,6 +2674,8 @@ function assertWechatPaymentResultBoundaries() {
'maps virtual payment cancel errCode to cancel result',
'hides ordinary payment native failure details from H5 result',
"errorMessage: 'wechat payment unavailable'",
"expect(console.error).toHaveBeenCalledWith(\n '[wechat-pay] requestVirtualPayment failed'",
'expect(console.error.mock.calls.flat()).not.toContain(payError)',
]) {
if (!paymentTestSource.includes(snippet)) {
throw new Error(`wechat payment bridge test must include ${snippet}`);
@@ -2687,20 +2695,26 @@ function assertWechatSubscribeResultBoundaries() {
for (const snippet of [
"WECHAT_SUBSCRIBE_UNAVAILABLE_REASON = 'wechat subscribe unavailable'",
"console.error('[subscribe-message] request failed', error)",
'function logWechatSubscribeFailure(label, _error)',
'console.error(`[subscribe-message] ${label}`)',
"logWechatSubscribeFailure('request failed', error)",
'WECHAT_SUBSCRIBE_UNAVAILABLE_REASON',
]) {
if (!subscribeSource.includes(snippet)) {
throw new Error(`wechat subscribe bridge must include ${snippet}`);
}
}
if (subscribeSource.includes("error && error.errMsg ? error.errMsg : 'failed'")) {
if (
subscribeSource.includes("error && error.errMsg ? error.errMsg : 'failed'") ||
subscribeSource.includes("console.error('[subscribe-message] request failed', error)")
) {
throw new Error('wechat subscribe bridge must not expose native subscribe errors to H5');
}
for (const snippet of [
'hides requestSubscribeMessage native failure details from H5 result',
'wechat%20subscribe%20unavailable',
"expect(console.error).toHaveBeenCalledWith(",
"expect(console.error).toHaveBeenCalledWith('[subscribe-message] request failed')",
'expect(console.error.mock.calls.flat()).not.toContain(subscribeError)',
]) {
if (!subscribeTestSource.includes(snippet)) {
throw new Error(`wechat subscribe bridge test must include ${snippet}`);