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', ); }); });