← back to CelebritySignatures
payments(TEST): Cody gate fixes — atomic USED_SIDS guard (closes concurrent double-credit race) + gate on status===complete (Stripe fulfillment best-practice)
f2fc2abe7679afe46a4d07ed76e8130e6fff5b05 · 2026-08-04 10:17:23 -0700 · Steve Abrams
Files touched
Diff
commit f2fc2abe7679afe46a4d07ed76e8130e6fff5b05
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 10:17:23 2026 -0700
payments(TEST): Cody gate fixes — atomic USED_SIDS guard (closes concurrent double-credit race) + gate on status===complete (Stripe fulfillment best-practice)
---
server.js | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/server.js b/server.js
index 6e59650..49401c8 100644
--- a/server.js
+++ b/server.js
@@ -58,6 +58,9 @@ function stripeTestKey() {
return null;
}
const STRIPE_TEST_KEY = (() => { const k = stripeTestKey(); return k && k.startsWith('sk_test_') ? k : null; })();
+// Atomic in-process guard against concurrent double-credit for the same paid session
+// (claimed synchronously before any await; the download-ledger is the restart-surviving backstop).
+const USED_SIDS = new Set();
// Merged signatures feed (used by /api/signatures, the crawlable /a/:qid pages,
// and the sitemap). The server-side occupation gate keeps the Artists category
@@ -423,13 +426,20 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
let paid = false;
try {
const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}` } })).json();
- paid = s.payment_status === 'paid' && String(s.metadata?.upload_id) === x.id;
+ // gate fulfillment on the settled session (status===complete), not just payment_status
+ paid = s.status === 'complete' && s.payment_status === 'paid' && String(s.metadata?.upload_id) === x.id;
} catch {}
if (!paid) return sendJSON(res, 402, { ok: false, error: 'payment not verified' });
- // Idempotent credit: append to the ledger + increment downloads ONCE per session id
- // (re-hitting the success URL re-downloads the file but never double-credits).
- let firstUse = true;
- try { firstUse = !(await readFile(join(DATA, 'download-ledger.jsonl'), 'utf8')).includes(`"sid":"${sid}"`); } catch {}
+ // Idempotent credit: claim the sid ATOMICALLY in-process (closes the concurrent
+ // double-credit race — has+add are synchronous, no await between them), then confirm
+ // against the persistent ledger so a restart can't re-credit an already-paid sid.
+ let firstUse = false;
+ if (!USED_SIDS.has(sid)) {
+ USED_SIDS.add(sid);
+ let inLedger = false;
+ try { inLedger = (await readFile(join(DATA, 'download-ledger.jsonl'), 'utf8')).includes(`"sid":"${sid}"`); } catch {}
+ firstUse = !inLedger;
+ }
if (firstUse) {
const dl = await currentUser(req); // downloader identity when signed in
const commission = +(x.priceUsd * x.commissionPct / 100).toFixed(2);
← 0881033 payments(TEST): gate signature downloads behind Stripe check
·
back to CelebritySignatures
·
SECURITY (HOLE 5, blocks live money): paid download link no d8cfb50 →