Files
Genarrative/miniprogram/host-bridge/payment.test.js
T
kdletters 433b65e154 收紧微信支付能力日志边界
微信虚拟支付不可用时只记录稳定标签

补充不可用路径日志脱敏测试

更新原生壳根门禁防止能力细节泄露
2026-06-21 19:24:09 +08:00

217 lines
7.1 KiB
JavaScript

import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { loadCommonJsModule } from '../test-utils/loadCommonJsModule.js';
const payBridgePath = path.resolve(
process.cwd(),
'miniprogram/host-bridge/payment.js',
);
describe('wechat-pay mini program payment bridge', () => {
let wechatPayBridge;
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation(() => {});
globalThis.wx = {
requestPayment: vi.fn(),
requestVirtualPayment: vi.fn(),
getSystemInfoSync: vi.fn(() => ({ SDKVersion: '2.32.0' })),
setStorageSync: vi.fn(),
navigateBack: vi.fn(),
};
globalThis.getCurrentPages = vi.fn(() => []);
wechatPayBridge = loadCommonJsModule(payBridgePath);
});
test('routes virtual payloads to wx.requestVirtualPayment', async () => {
const { requestWechatPayment } = wechatPayBridge;
globalThis.wx.requestVirtualPayment.mockImplementationOnce((options) => {
options.success?.({ errMsg: 'requestVirtualPayment:ok' });
});
const payParams = {
mode: 'short_series_coin',
signData:
'{"offerId":"offer-1","buyQuantity":1,"env":0,"currencyType":"CNY","outTradeNo":"order-virtual-1","attach":"mud_points_60"}',
paySig: 'pay-sig',
signature: 'user-sig',
};
const result = await requestWechatPayment(payParams);
expect(result).toEqual({ status: 'success', errorMessage: '' });
expect(globalThis.wx.requestVirtualPayment).toHaveBeenCalledWith({
mode: 'short_series_coin',
signData: payParams.signData,
paySig: 'pay-sig',
signature: 'user-sig',
success: expect.any(Function),
fail: expect.any(Function),
});
expect(globalThis.wx.requestPayment).not.toHaveBeenCalled();
});
test('routes goods virtual payloads to wx.requestVirtualPayment', async () => {
const { requestWechatPayment } = wechatPayBridge;
globalThis.wx.requestVirtualPayment.mockImplementationOnce((options) => {
options.success?.({ errMsg: 'requestVirtualPayment:ok' });
});
const payParams = {
mode: 'short_series_goods',
signData:
'{"offerId":"offer-1","buyQuantity":1,"env":0,"currencyType":"CNY","productId":"member_month","goodsPrice":2800,"outTradeNo":"order-goods-1","attach":"member_month"}',
paySig: 'pay-sig',
signature: 'user-sig',
};
const result = await requestWechatPayment(payParams);
expect(result).toEqual({ status: 'success', errorMessage: '' });
expect(globalThis.wx.requestVirtualPayment).toHaveBeenCalledWith({
mode: 'short_series_goods',
signData: payParams.signData,
paySig: 'pay-sig',
signature: 'user-sig',
success: expect.any(Function),
fail: expect.any(Function),
});
expect(globalThis.wx.requestPayment).not.toHaveBeenCalled();
});
test('keeps ordinary requestPayment payloads on wx.requestPayment', async () => {
const { requestWechatPayment } = wechatPayBridge;
globalThis.wx.requestPayment.mockImplementationOnce((options) => {
options.success?.();
});
const result = await requestWechatPayment({
timeStamp: '1777110165',
nonceStr: 'nonce',
package: 'prepay_id=wx-prepay',
signType: 'RSA',
paySign: 'signature',
});
expect(result).toEqual({ status: 'success', errorMessage: '' });
expect(globalThis.wx.requestPayment).toHaveBeenCalledWith({
timeStamp: '1777110165',
nonceStr: 'nonce',
package: 'prepay_id=wx-prepay',
signType: 'RSA',
paySign: 'signature',
success: expect.any(Function),
fail: expect.any(Function),
});
expect(globalThis.wx.requestVirtualPayment).not.toHaveBeenCalled();
});
test('maps virtual payment cancel errCode to cancel result', async () => {
const { requestWechatPayment } = wechatPayBridge;
const payError = {
errCode: -2,
errMsg: 'requestVirtualPayment:fail cancel',
};
globalThis.wx.requestVirtualPayment.mockImplementationOnce((options) => {
options.fail?.(payError);
});
await expect(
requestWechatPayment({
mode: 'short_series_coin',
signData: '{}',
paySig: 'pay-sig',
signature: 'user-sig',
}),
).resolves.toEqual({
status: 'cancel',
errorMessage: 'wechat payment unavailable',
});
expect(console.error).toHaveBeenCalledWith(
'[wechat-pay] requestVirtualPayment failed',
);
expect(console.error.mock.calls.flat()).not.toContain(payError);
});
test('logs virtual payment unavailable without exposing capability details', async () => {
const { requestWechatPayment } = wechatPayBridge;
globalThis.wx.canIUse = vi.fn(() => false);
globalThis.wx.getSystemInfoSync = vi.fn(() => ({ SDKVersion: '2.18.0' }));
delete globalThis.wx.requestVirtualPayment;
await expect(
requestWechatPayment({
mode: 'short_series_coin',
signData: '{}',
paySig: 'pay-sig',
signature: 'user-sig',
}),
).resolves.toEqual({
status: 'fail',
errorMessage: '当前微信基础库不支持 requestVirtualPayment',
});
expect(console.error).toHaveBeenCalledWith(
'[wechat-pay] requestVirtualPayment unavailable',
);
expect(console.error.mock.calls).toContainEqual([
'[wechat-pay] requestVirtualPayment unavailable',
]);
});
test('hides ordinary payment native failure details from H5 result', async () => {
const { requestWechatPayment } = wechatPayBridge;
globalThis.wx.requestPayment.mockImplementationOnce((options) => {
options.fail?.({
errCode: 1001,
errMsg: 'requestPayment:fail private native detail',
});
});
await expect(
requestWechatPayment({
timeStamp: '1777110165',
nonceStr: 'nonce',
package: 'prepay_id=wx-prepay',
signType: 'RSA',
paySign: 'signature',
}),
).resolves.toEqual({
status: 'fail',
errorMessage: 'wechat payment unavailable',
});
});
test('notifies previous web-view after virtual payment', () => {
const { notifyPreviousWebView } = wechatPayBridge;
const previousPage = {
data: { webViewUrl: 'https://web.test/#tab=profile' },
setData: vi.fn(),
};
globalThis.getCurrentPages = vi.fn(() => [previousPage, {}]);
notifyPreviousWebView('request-1', 'order-1', {
status: 'success',
errorMessage: '',
});
expect(globalThis.wx.setStorageSync).toHaveBeenCalledWith(
'genarrative:wechat-pay-result',
'request-1:success:order-1',
);
expect(previousPage.setData).toHaveBeenCalledWith({
webViewUrl: 'https://web.test/#tab=profile&wx_pay_result=request-1%3Asuccess%3Aorder-1',
});
});
test('parsePayParams and appendPayResult keep existing behavior', () => {
const { appendPayResult, parsePayParams } = wechatPayBridge;
expect(parsePayParams(encodeURIComponent('{"paySign":"sig"}'))).toEqual({
paySign: 'sig',
});
expect(appendPayResult('https://web.test/#old=1', 'req:fail:order-1')).toBe(
'https://web.test/#old=1&wx_pay_result=req%3Afail%3Aorder-1',
);
});
});