← back to CelebritySignatures
Live switch is now MURAL-ONLY (Steve go-live 2026-08-04): per-flow keys — murals use live key when STRIPE_LIVE_ENABLED=1, downloads PINNED to test key until a celebrity-payout mechanism exists. Flipping the live switch can never accidentally charge a real card for a download whose owner we cannot pay. mode stamped from STRIPE_MODE (TK-10181)
ea2f10b0838cab07bc1689124316126d7b1881a0 · 2026-08-04 10:35:28 -0700 · Steve Abrams
Files touched
Diff
commit ea2f10b0838cab07bc1689124316126d7b1881a0
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Aug 4 10:35:28 2026 -0700
Live switch is now MURAL-ONLY (Steve go-live 2026-08-04): per-flow keys — murals use live key when STRIPE_LIVE_ENABLED=1, downloads PINNED to test key until a celebrity-payout mechanism exists. Flipping the live switch can never accidentally charge a real card for a download whose owner we cannot pay. mode stamped from STRIPE_MODE (TK-10181)
---
server.js | 28 +++++++++++++++++-----------
1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/server.js b/server.js
index b3855bd..cd7b0bd 100644
--- a/server.js
+++ b/server.js
@@ -63,9 +63,15 @@ const STRIPE_LIVE_ENABLED = envVal('STRIPE_LIVE_ENABLED') === '1';
const _testKey = (() => { const k = envVal('STRIPE_TEST_SECRET_KEY'); return k && k.startsWith('sk_test_') ? k : null; })();
const _liveKey = (() => { const k = envVal('STRIPE_LIVE_SECRET_KEY'); return k && k.startsWith('sk_live_') ? k : null; })();
const STRIPE_LIVE = STRIPE_LIVE_ENABLED && _liveKey; // real-money mode active?
-const STRIPE_TEST_KEY = STRIPE_LIVE ? _liveKey : _testKey; // the key the endpoints use
+// PER-FLOW keys (Steve 2026-08-04: "go live on murals only, keep downloads in
+// test until payouts exist"). MURALS charge real cards once the live switch is
+// on; DOWNLOADS stay pinned to the TEST key until a celebrity-payout mechanism
+// exists — so flipping the live switch can NEVER accidentally take real money
+// for a download whose owner we cannot yet pay their commission.
+const STRIPE_MURAL_KEY = STRIPE_LIVE ? _liveKey : _testKey;
+const STRIPE_DOWNLOAD_KEY = _testKey;
const STRIPE_MODE = STRIPE_LIVE ? 'live' : 'test';
-if (STRIPE_LIVE) console.log('⚠️ STRIPE LIVE MODE — real charges enabled'); else if (_testKey) console.log('Stripe test mode active');
+if (STRIPE_LIVE) console.log('⚠️ STRIPE LIVE MODE — murals charge REAL cards; downloads stay TEST'); else if (_testKey) console.log('Stripe test mode active (murals + downloads)');
// 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();
@@ -270,7 +276,7 @@ createServer(async (req, res) => {
// key is configured, the endpoint reports so gracefully (the lead-form flow
// still works as the fallback).
if (path === '/api/mural-checkout' && M === 'POST') {
- if (!STRIPE_TEST_KEY) return sendJSON(res, 503, { ok: false, error: 'payments not configured yet (awaiting Stripe test key)' });
+ if (!STRIPE_MURAL_KEY) return sendJSON(res, 503, { ok: false, error: 'payments not configured yet (awaiting Stripe key)' });
const b = await readBody(req);
const name = String(b.name || '').slice(0, 120);
const email = String(b.email || '').trim();
@@ -287,7 +293,7 @@ createServer(async (req, res) => {
const id = (list.at(-1)?.id || 1000) + 1;
const order = { id, at: new Date().toISOString(), account: u ? u.email : null, name, email,
mural: b.mural, mural_title: title, widthFt: w, heightFt: h, sqft: w * h,
- amountUsd: amountCents / 100, placement: b.placement || null, status: 'pending_payment', mode: 'test' };
+ amountUsd: amountCents / 100, placement: b.placement || null, status: 'pending_payment', mode: STRIPE_MODE };
list.push(order); await store('mural-orders.json', list);
// create the Stripe Checkout Session via REST (form-encoded)
const params = new URLSearchParams();
@@ -305,7 +311,7 @@ createServer(async (req, res) => {
try {
const sres = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
- headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
+ headers: { Authorization: `Bearer ${STRIPE_MURAL_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
const sj = await sres.json();
@@ -317,9 +323,9 @@ createServer(async (req, res) => {
if (path === '/order-success' && M === 'GET') {
const sid = url.searchParams.get('sid') || '';
let paid = false, order = null;
- if (STRIPE_TEST_KEY && /^cs_(test|live)_[A-Za-z0-9]+$/.test(sid)) {
+ if (STRIPE_MURAL_KEY && /^cs_(test|live)_[A-Za-z0-9]+$/.test(sid)) {
try {
- const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}` } })).json();
+ const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_MURAL_KEY}` } })).json();
if (s.payment_status === 'paid') {
paid = true;
const list = await load('mural-orders.json', []);
@@ -397,7 +403,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
// session before releasing the file. Inert (503) until a test key is set;
// the LIVE key is deliberately never read here — going live is a separate gate.
if (path === '/api/signature-checkout' && M === 'POST') {
- if (!STRIPE_TEST_KEY) return sendJSON(res, 503, { ok: false, error: 'downloads not purchasable yet (awaiting Stripe test key)' });
+ if (!STRIPE_DOWNLOAD_KEY) return sendJSON(res, 503, { ok: false, error: 'downloads not purchasable yet (awaiting Stripe test key)' });
const b = await readBody(req);
const ups = await load('celebrity-uploads.json', []);
const x = ups.find(e => e.id === String(b.id || ''));
@@ -414,7 +420,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
try {
const sres = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
- headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
+ headers: { Authorization: `Bearer ${STRIPE_DOWNLOAD_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
const sj = await sres.json();
@@ -429,11 +435,11 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
if (!x || x.status !== 'approved') return sendJSON(res, 404, { ok: false, error: 'not available' });
// PAYMENT GATE (TEST mode): require a PAID Stripe checkout session for this file.
const sid = url.searchParams.get('sid') || '';
- if (!STRIPE_TEST_KEY) return sendJSON(res, 503, { ok: false, error: 'downloads require payment — not configured yet' });
+ if (!STRIPE_DOWNLOAD_KEY) return sendJSON(res, 503, { ok: false, error: 'downloads require payment — not configured yet' });
if (!/^cs_(test|live)_[A-Za-z0-9]+$/.test(sid)) return sendJSON(res, 402, { ok: false, error: 'payment required — purchase this download first', purchase: '/api/signature-checkout' });
let paid = false, sessionCreated = 0;
try {
- const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_TEST_KEY}` } })).json();
+ const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${STRIPE_DOWNLOAD_KEY}` } })).json();
// 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;
sessionCreated = s.created || 0;
← cc656ee Live-mode switch (prep for go-live, defaults OFF): STRIPE_LI
·
back to CelebritySignatures
·
AdSense Auto-ads integration (Steve request 2026-08-04): loa 6db34b0 →