openvibers/openvibe.live Commits
PowerChat reconcile: treat source=manual_test rows as test money
Changes
@@@ -133,14 +133,19 @@ async function reconcileOnce() {const order = orders.get(ref);if (!order) continue; // already credited, or not on this sweep's listif (row.source === 'developer_app') continue; // our own forwarded tip echo, never money in-- if (row.isTest && !allowTest) { summary.skippedTest++; continue; }++ // /paid-messages rows carry NO isTest field, yet include source='manual_test'++ // rows (the no-payment-provider test tip path). PowerChat's own webhook++ // rule is isTest = event.isTest || source === 'manual_test' — mirror it, or++ // a test tip would be reconciled as real money on a production PowerChat.++ const isTest = !!row.isTest || row.source === 'manual_test';++ if (isTest && !allowTest) { summary.skippedTest++; continue; }// Same shape the webhook hands to the checkout module (money in integer cents).const data = {eventId: row.eventId, occurredAt: row.occurredAt, source: row.source,donorName: row.donorName, isAnonymous: !!row.isAnonymous, message: row.message,amountCents: row.amountCents, currency: row.currency,amountUsdCents: row.amountUsdCents != null ? row.amountUsdCents : row.amountCents,-- appExternalRef: ref, isTest: !!row.isTest, reconciled: true,++ appExternalRef: ref, isTest, reconciled: true,};let consumed = false;try { consumed = checkout.handleAttributedDonation(host.receivingUserId, data); }@@@ -62,6 +62,8 @@ const reconcile = require('../server/integrations/powerchat-reconcile');{ eventId: 'e-other', occurredAt: '2026-08-27T09:30:00Z', source: 'powerchat', amountCents: 300, amountUsdCents: 300, appExternalRef: null },{ eventId: 'e-echo', occurredAt: '2026-08-27T09:20:00Z', source: 'developer_app', amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },{ eventId: 'e-test', occurredAt: '2026-08-27T09:19:00Z', source: 'powerchat', isTest: true, amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },++ // Real feed rows have NO isTest field; manual_test is PowerChat's no-provider test tip → must be treated as test.++ { eventId: 'e-manual', occurredAt: '2026-08-27T09:18:00Z', source: 'manual_test', amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },{ eventId: 'e-41', occurredAt: '2026-08-27T09:10:00Z', source: 'powerchat', donorName: 'Pat', amountCents: 500, currency: 'USD', amountUsdCents: 500, appExternalRef: 'pcorder:41' },] }, nextCursor: 'cur-2' });api.pages.push({ data: { rows: [@@@ -72,7 +74,7 @@ const reconcile = require('../server/integrations/powerchat-reconcile');assert.strictEqual(sum.hosts, 1, 'alex hosts both 41 (site) and 42 (direct, streamer 1 = alex)');assert.strictEqual(sum.skippedHosts, 1, "'other' has no paid_messages:read → skipped, no call");assert.strictEqual(sum.pages, 2, 'stopped after page 2: no orders left for that host despite a cursor');-- assert.strictEqual(sum.skippedTest, 1);++ assert.strictEqual(sum.skippedTest, 2, 'explicit isTest AND source=manual_test are both skipped');assert.deepStrictEqual(sum.credited.map((c) => c.ref).sort(), ['pcorder:41', 'pcsub:42']);assert.deepStrictEqual(sum.underpaid, []);assert.strictEqual(orders.get(41).status, 'credited');
@@ -133,14 +133,19 @@ async function reconcileOnce() {
@@ -133,14 +133,19 @@ async function reconcileOnce() {
const order = orders.get(ref);
const order = orders.get(ref);
if (!order) continue; // already credited, or not on this sweep's list
if (!order) continue; // already credited, or not on this sweep's list
if (row.source === 'developer_app') continue; // our own forwarded tip echo, never money in
if (row.source === 'developer_app') continue; // our own forwarded tip echo, never money in
- if (row.isTest && !allowTest) { summary.skippedTest++; continue; }
+ // /paid-messages rows carry NO isTest field, yet include source='manual_test'
+ // rows (the no-payment-provider test tip path). PowerChat's own webhook
+ // rule is isTest = event.isTest || source === 'manual_test' — mirror it, or
+ // a test tip would be reconciled as real money on a production PowerChat.
+ const isTest = !!row.isTest || row.source === 'manual_test';
+ if (isTest && !allowTest) { summary.skippedTest++; continue; }
// Same shape the webhook hands to the checkout module (money in integer cents).
// Same shape the webhook hands to the checkout module (money in integer cents).
const data = {
const data = {
eventId: row.eventId, occurredAt: row.occurredAt, source: row.source,
eventId: row.eventId, occurredAt: row.occurredAt, source: row.source,
donorName: row.donorName, isAnonymous: !!row.isAnonymous, message: row.message,
donorName: row.donorName, isAnonymous: !!row.isAnonymous, message: row.message,
amountCents: row.amountCents, currency: row.currency,
amountCents: row.amountCents, currency: row.currency,
amountUsdCents: row.amountUsdCents != null ? row.amountUsdCents : row.amountCents,
amountUsdCents: row.amountUsdCents != null ? row.amountUsdCents : row.amountCents,
- appExternalRef: ref, isTest: !!row.isTest, reconciled: true,
+ appExternalRef: ref, isTest, reconciled: true,
};
};
let consumed = false;
let consumed = false;
try { consumed = checkout.handleAttributedDonation(host.receivingUserId, data); }
try { consumed = checkout.handleAttributedDonation(host.receivingUserId, data); }
@@ -62,6 +62,8 @@ const reconcile = require('../server/integrations/powerchat-reconcile');
@@ -62,6 +62,8 @@ const reconcile = require('../server/integrations/powerchat-reconcile');
{ eventId: 'e-other', occurredAt: '2026-08-27T09:30:00Z', source: 'powerchat', amountCents: 300, amountUsdCents: 300, appExternalRef: null },
{ eventId: 'e-other', occurredAt: '2026-08-27T09:30:00Z', source: 'powerchat', amountCents: 300, amountUsdCents: 300, appExternalRef: null },
{ eventId: 'e-echo', occurredAt: '2026-08-27T09:20:00Z', source: 'developer_app', amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },
{ eventId: 'e-echo', occurredAt: '2026-08-27T09:20:00Z', source: 'developer_app', amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },
{ eventId: 'e-test', occurredAt: '2026-08-27T09:19:00Z', source: 'powerchat', isTest: true, amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },
{ eventId: 'e-test', occurredAt: '2026-08-27T09:19:00Z', source: 'powerchat', isTest: true, amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },
+ // Real feed rows have NO isTest field; manual_test is PowerChat's no-provider test tip → must be treated as test.
+ { eventId: 'e-manual', occurredAt: '2026-08-27T09:18:00Z', source: 'manual_test', amountCents: 500, amountUsdCents: 500, appExternalRef: 'pcorder:41' },
{ eventId: 'e-41', occurredAt: '2026-08-27T09:10:00Z', source: 'powerchat', donorName: 'Pat', amountCents: 500, currency: 'USD', amountUsdCents: 500, appExternalRef: 'pcorder:41' },
{ eventId: 'e-41', occurredAt: '2026-08-27T09:10:00Z', source: 'powerchat', donorName: 'Pat', amountCents: 500, currency: 'USD', amountUsdCents: 500, appExternalRef: 'pcorder:41' },
] }, nextCursor: 'cur-2' });
] }, nextCursor: 'cur-2' });
api.pages.push({ data: { rows: [
api.pages.push({ data: { rows: [
@@ -72,7 +74,7 @@ const reconcile = require('../server/integrations/powerchat-reconcile');
@@ -72,7 +74,7 @@ const reconcile = require('../server/integrations/powerchat-reconcile');
assert.strictEqual(sum.hosts, 1, 'alex hosts both 41 (site) and 42 (direct, streamer 1 = alex)');
assert.strictEqual(sum.hosts, 1, 'alex hosts both 41 (site) and 42 (direct, streamer 1 = alex)');
assert.strictEqual(sum.skippedHosts, 1, "'other' has no paid_messages:read → skipped, no call");
assert.strictEqual(sum.skippedHosts, 1, "'other' has no paid_messages:read → skipped, no call");
assert.strictEqual(sum.pages, 2, 'stopped after page 2: no orders left for that host despite a cursor');
assert.strictEqual(sum.pages, 2, 'stopped after page 2: no orders left for that host despite a cursor');
- assert.strictEqual(sum.skippedTest, 1);
+ assert.strictEqual(sum.skippedTest, 2, 'explicit isTest AND source=manual_test are both skipped');
assert.deepStrictEqual(sum.credited.map((c) => c.ref).sort(), ['pcorder:41', 'pcsub:42']);
assert.deepStrictEqual(sum.credited.map((c) => c.ref).sort(), ['pcorder:41', 'pcsub:42']);
assert.deepStrictEqual(sum.underpaid, []);
assert.deepStrictEqual(sum.underpaid, []);
assert.strictEqual(orders.get(41).status, 'credited');
assert.strictEqual(orders.get(41).status, 'credited');