// In-memory regression checks; no real payment or network requests. const assert = require('node:assert/strict'); const { fixture } = require('./check-api.cjs'); (async () => { const priced = fixture(); priced.info.status = 'purchasable'; priced.info.priceFen = 600; priced.info.productId = 'configured-product'; await priced.client.refresh(); priced.info.priceFen = 1200; // Purchase must refresh instead of using the previous view. assert.equal((await priced.client.request('purchase', { periodId: priced.periodId })).code, 1); const pricedOrder = priced.calls.find(c => c.path.includes('orderPaySig')); assert.equal(pricedOrder.body.itemPrice, 1200); assert.equal(pricedOrder.body.itemid, 'configured-product'); assert.equal(pricedOrder.body.itemCount, 1); for (const price of [undefined, null, 0, -1, 1.5, '300']) { const invalidPrice = fixture(); invalidPrice.info.status = 'purchasable'; invalidPrice.info.priceFen = price; assert.equal((await invalidPrice.client.request('purchase', { periodId: invalidPrice.periodId })).code, 0); assert(!invalidPrice.calls.some(c => c.path.includes('orderPaySig'))); } console.log('PASS: order uses freshly fetched product and price; invalid prices cannot create orders.'); for (const ios of [false, true]) { const f = fixture(new Map(), { ios, cancelPay: true }); f.info.status = 'purchasable'; await f.client.request('purchase', { periodId: f.periodId }); const original = { ...f.client.ledger.orders[f.periodId] }; f.server.cancelPay = false; f.server.intercept = (path, body, ok, fail, reply) => { if (path.includes('orderPaySig')) { ok({ outTradeNo: 'new-order', signData: 'untouched-signature', signature: 's', paySig: 'p' }); return true; } }; const before = f.calls.length; const result = await f.client.request('purchase', { periodId: f.periodId }); assert.equal(result.code, 1); assert.equal(f.counts().payCalls, 2); assert.equal(f.counts().saves, 0); const creates = f.calls.filter(c => c.path.includes('orderPaySig')); assert.notEqual(creates[1].body.createRequestId, original.createRequestId); assert.equal(creates[1].body.itemCount, 1); assert.equal(creates[1].body.itemPrice, 300); assert.equal(f.client.ledger.orders[f.periodId].outTradeNo, 'new-order'); assert(!f.calls.slice(before).some(c => c.body.outTradeNo === original.outTradeNo)); } console.log('PASS: Android and iOS cancellation retries create a fresh request and pay the new order.'); for (const paymentState of ['cancelled', 'failed', 'pending', undefined]) { const f = fixture(); f.info.status = 'purchasable'; f.client.ledger.orders[f.periodId] = { outTradeNo: 'old-order', createRequestId: 'original', channel: 'native', ios: false, paymentState }; assert.equal((await f.client.request('purchase', { periodId: f.periodId })).code, 1); assert.equal(f.counts().payCalls, 1); assert(f.calls.some(c => c.path.includes('orderPaySig') && c.body.createRequestId !== 'original')); assert(!f.calls.some(c => c.body.outTradeNo === 'old-order')); assert(f.calls.some(c => c.path.includes('getPayInfo') && c.body.outTradeNo === 'order-1')); } console.log('PASS: every purchase creates a new request regardless of previous state; only the new order is queried.'); const f = fixture(); f.info.status = 'purchasable'; f.client.ledger.orders[f.periodId] = { outTradeNo: 'original-order', createRequestId: 'original', channel: 'native', ios: false, paymentState: 'cancelled' }; await f.client.refresh(); assert(!f.calls.some(c => c.path.includes('getPayInfo'))); const result = await f.client.request('purchase', { periodId: f.periodId }); assert.equal(result.code, 1); assert.equal(f.counts().payCalls, 1); assert.equal(f.client.ledger.orders[f.periodId].outTradeNo, 'order-1'); assert.notEqual(f.client.ledger.orders[f.periodId].createRequestId, 'original'); assert(!f.calls.some(c => c.body.outTradeNo === 'original-order')); console.log('PASS: stored cancellation no longer queries or requires reuse of the cancelled order.'); const invalid = fixture(); invalid.info.status = 'purchasable'; invalid.server.intercept = (path, body, ok) => { if (path.includes('orderPaySig')) { ok({ outTradeNo: 'invalid-1', signData: '', signature: 's', paySig: 'p' }); return true; } }; const rejected = await invalid.client.request('purchase', { periodId: invalid.periodId }); assert.equal(rejected.code, 0); assert.match(rejected.msg, /signData/); assert.equal(invalid.counts().payCalls, 0); assert.equal(invalid.client.ledger.orders[invalid.periodId].outTradeNo, 'invalid-1'); console.log('PASS: incomplete payment parameters are rejected while preserving the order.'); })().catch(error => { console.error(error); process.exitCode = 1; });