[object Object]

← 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 →