import assert from 'node:assert/strict'; import test from 'node:test'; import { buildV2JsapiParams, buildXml, createWechatPayClient, loadWechatPayConfig, parseXmlFields, signV2Params, } from './wechat-pay.mjs'; test('signV2Params is deterministic and uppercase MD5', () => { const params = { appid: 'wx8888888888888888', mch_id: '1900000109', body: 'test', nonce_str: 'ibuaiVcKdpRxkhJA', }; const apiKey = '8934e7d15453e97507ef794cf7b0519d'; const sign = signV2Params(params, apiKey); assert.match(sign, /^[0-9A-F]{32}$/); assert.equal(sign, signV2Params(params, apiKey)); assert.notEqual(sign, signV2Params({ ...params, body: 'other' }, apiKey)); }); test('parseXmlFields and buildXml round-trip', () => { const xml = buildXml({ return_code: 'SUCCESS', out_trade_no: 'TK123', total_fee: '500', }); const fields = parseXmlFields(xml); assert.equal(fields.return_code, 'SUCCESS'); assert.equal(fields.out_trade_no, 'TK123'); assert.equal(fields.total_fee, '500'); }); test('loadWechatPayConfig selects v2 when APIv2 key is configured', () => { const previous = { enabled: process.env.H5_WECHAT_PAY_ENABLED, appId: process.env.H5_WECHAT_APP_ID, mchId: process.env.H5_WECHAT_MCH_ID, apiV2Key: process.env.H5_WECHAT_API_V2_KEY, apiVersion: process.env.H5_WECHAT_API_VERSION, notifyUrl: process.env.H5_WECHAT_NOTIFY_URL, v3Key: process.env.H5_WECHAT_API_V3_KEY, serialNo: process.env.H5_WECHAT_SERIAL_NO, privateKey: process.env.H5_WECHAT_PRIVATE_KEY, }; process.env.H5_WECHAT_PAY_ENABLED = '1'; process.env.H5_WECHAT_APP_ID = 'wxtestappid'; process.env.H5_WECHAT_MCH_ID = '1234567890'; process.env.H5_WECHAT_API_V2_KEY = 'test-api-v2-key-32chars-long!!!!'; process.env.H5_WECHAT_NOTIFY_URL = 'https://example.com/webhooks/wechat-pay/notify'; delete process.env.H5_WECHAT_API_VERSION; delete process.env.H5_WECHAT_API_V3_KEY; delete process.env.H5_WECHAT_SERIAL_NO; delete process.env.H5_WECHAT_PRIVATE_KEY; try { const config = loadWechatPayConfig(); assert.equal(config.apiVersion, 'v2'); assert.equal(config.enabled, true); const client = createWechatPayClient(config); assert.equal(client.apiVersion, 'v2'); assert.equal(client.enabled, true); } finally { for (const [key, value] of Object.entries(previous)) { const envKey = { enabled: 'H5_WECHAT_PAY_ENABLED', appId: 'H5_WECHAT_APP_ID', mchId: 'H5_WECHAT_MCH_ID', apiV2Key: 'H5_WECHAT_API_V2_KEY', apiVersion: 'H5_WECHAT_API_VERSION', notifyUrl: 'H5_WECHAT_NOTIFY_URL', v3Key: 'H5_WECHAT_API_V3_KEY', serialNo: 'H5_WECHAT_SERIAL_NO', privateKey: 'H5_WECHAT_PRIVATE_KEY', }[key]; if (value === undefined) delete process.env[envKey]; else process.env[envKey] = value; } } }); test('buildV2JsapiParams signs prepay package for WeixinJSBridge', () => { const config = { appId: 'wxtestappid', apiKey: 'test-api-v2-key-32chars-long!!!!', }; const params = buildV2JsapiParams(config, 'wx201410272009395522657a690389285100'); assert.equal(params.appId, 'wxtestappid'); assert.equal(params.package, 'prepay_id=wx201410272009395522657a690389285100'); assert.equal(params.signType, 'MD5'); assert.match(params.paySign, /^[0-9A-F]{32}$/); }); test('verifyNotify validates v2 callback signature', () => { const apiKey = 'test-api-v2-key-32chars-long!!!!'; const fields = { return_code: 'SUCCESS', result_code: 'SUCCESS', out_trade_no: 'TKORDER001', transaction_id: '4200001234', total_fee: '1000', nonce_str: 'abc123', }; fields.sign = signV2Params(fields, apiKey); const body = buildXml(fields); const client = createWechatPayClient({ enabled: true, apiVersion: 'v2', appId: 'wxtest', mchId: '123', apiKey, notifyUrl: 'https://example.com/notify', skipNotifyVerify: false, }); const { transaction } = client.verifyNotify({ headers: {}, body }); assert.equal(transaction.out_trade_no, 'TKORDER001'); assert.equal(transaction.trade_state, 'SUCCESS'); assert.equal(transaction.amount.total, 1000); });