MatchMaster/tools/coin-madness/check-payment-retry.cjs

77 lines
4.9 KiB
JavaScript

// 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; });