← back to CelebritySignatures
wear: independent Stripe live switch — allows one test order before real money (TK-10286)
c2a79ec7afb93b152ea6183aff7ce98ace667e84 · 2026-09-09 07:39:25 -0700 · Steve Abrams
Steve wants a real test order through /wear before it charges real cards.
Critical finding that made this necessary: Kamatera's .env already has
STRIPE_LIVE_ENABLED=1 set (murals/downloads use it) with a real live key
present. /wear's checkout previously shared STRIPE_MURAL_KEY, so the moment
WEAR_SALES_LIVE=1 landed, it would have charged REAL cards immediately —
zero chance to place a test order first.
Added WEAR_STRIPE_LIVE_ENABLED as wear's own independent live switch,
completely separate from STRIPE_LIVE_ENABLED. New WEAR_STRIPE_KEY /
WEAR_STRIPE_MODE resolvers (same shape as the existing STRIPE_MURAL_KEY /
STRIPE_DOWNLOAD_KEY per-flow pattern already in this file for exactly this
kind of reason — downloads stay pinned to test until a payout mechanism
exists). Swapped both /api/wear-checkout and /wear-success off
STRIPE_MURAL_KEY/STRIPE_MODE onto the new wear-specific ones. Applied the
same fix to reconcile-wear-orders.mjs's resolveStripeKey().
This means Steve can now set WEAR_SALES_LIVE=1 + WEAR_POD_PROVIDER=printify +
PRINTIFY_SHOP_ID + PRINTIFY_MANUAL_APPROVAL_CONFIRMED=1 — making checkout,
Printify, and the cart fully functional — while WEAR_STRIPE_LIVE_ENABLED
stays unset, so it still charges the TEST key. Place one real test order
end-to-end, then flip WEAR_STRIPE_LIVE_ENABLED=1 as the final, deliberate
step to real money.
Verified by faking the exact production condition locally (STRIPE_LIVE_
ENABLED=1 + WEAR_SALES_LIVE=1, WEAR_STRIPE_LIVE_ENABLED unset): checkout
correctly returned a cs_test_ session despite the shared flag being live,
with the startup log clearly showing the split ("STRIPE LIVE MODE — murals
charge REAL cards" vs "Wear apparel: Stripe test mode, independent of the
murals live flag"). Also verified WEAR_STRIPE_LIVE_ENABLED=1 correctly flips
wear to live mode on its own. Test order cleaned from the gitignored
wear-orders.json afterward. Real (unfaked) local environment re-confirmed
still fully gated off.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
Files touched
M scripts/reconcile-wear-orders.mjsM server.js
Diff
commit c2a79ec7afb93b152ea6183aff7ce98ace667e84
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 07:39:25 2026 -0700
wear: independent Stripe live switch — allows one test order before real money (TK-10286)
Steve wants a real test order through /wear before it charges real cards.
Critical finding that made this necessary: Kamatera's .env already has
STRIPE_LIVE_ENABLED=1 set (murals/downloads use it) with a real live key
present. /wear's checkout previously shared STRIPE_MURAL_KEY, so the moment
WEAR_SALES_LIVE=1 landed, it would have charged REAL cards immediately —
zero chance to place a test order first.
Added WEAR_STRIPE_LIVE_ENABLED as wear's own independent live switch,
completely separate from STRIPE_LIVE_ENABLED. New WEAR_STRIPE_KEY /
WEAR_STRIPE_MODE resolvers (same shape as the existing STRIPE_MURAL_KEY /
STRIPE_DOWNLOAD_KEY per-flow pattern already in this file for exactly this
kind of reason — downloads stay pinned to test until a payout mechanism
exists). Swapped both /api/wear-checkout and /wear-success off
STRIPE_MURAL_KEY/STRIPE_MODE onto the new wear-specific ones. Applied the
same fix to reconcile-wear-orders.mjs's resolveStripeKey().
This means Steve can now set WEAR_SALES_LIVE=1 + WEAR_POD_PROVIDER=printify +
PRINTIFY_SHOP_ID + PRINTIFY_MANUAL_APPROVAL_CONFIRMED=1 — making checkout,
Printify, and the cart fully functional — while WEAR_STRIPE_LIVE_ENABLED
stays unset, so it still charges the TEST key. Place one real test order
end-to-end, then flip WEAR_STRIPE_LIVE_ENABLED=1 as the final, deliberate
step to real money.
Verified by faking the exact production condition locally (STRIPE_LIVE_
ENABLED=1 + WEAR_SALES_LIVE=1, WEAR_STRIPE_LIVE_ENABLED unset): checkout
correctly returned a cs_test_ session despite the shared flag being live,
with the startup log clearly showing the split ("STRIPE LIVE MODE — murals
charge REAL cards" vs "Wear apparel: Stripe test mode, independent of the
murals live flag"). Also verified WEAR_STRIPE_LIVE_ENABLED=1 correctly flips
wear to live mode on its own. Test order cleaned from the gitignored
wear-orders.json afterward. Real (unfaked) local environment re-confirmed
still fully gated off.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
---
scripts/reconcile-wear-orders.mjs | 9 ++++++---
server.js | 35 +++++++++++++++++++++++++----------
2 files changed, 31 insertions(+), 13 deletions(-)
diff --git a/scripts/reconcile-wear-orders.mjs b/scripts/reconcile-wear-orders.mjs
index 75f1500..4ad7b4c 100644
--- a/scripts/reconcile-wear-orders.mjs
+++ b/scripts/reconcile-wear-orders.mjs
@@ -41,10 +41,13 @@ function envVal(name) {
}
return null;
}
-// Mirrors server.js's STRIPE_MURAL_KEY resolver exactly (live only when both
-// STRIPE_LIVE_ENABLED=1 and a real sk_live_/rk_live_ key are present).
+// Mirrors server.js's WEAR_STRIPE_KEY resolver exactly — wear has its OWN
+// live switch (WEAR_STRIPE_LIVE_ENABLED), deliberately separate from
+// STRIPE_LIVE_ENABLED (which murals/downloads already use), so wear can go
+// fully live everywhere else while Steve still places one test order through
+// wear specifically before flipping this switch too (TK-10286, 2026-09-09).
function resolveStripeKey() {
- const liveEnabled = envVal('STRIPE_LIVE_ENABLED') === '1';
+ const liveEnabled = envVal('WEAR_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 && /^(sk|rk)_live_/.test(k) ? k : null; })();
return liveEnabled && liveKey ? liveKey : testKey;
diff --git a/server.js b/server.js
index 2b22836..c9d9bfa 100644
--- a/server.js
+++ b/server.js
@@ -72,7 +72,20 @@ const STRIPE_LIVE = STRIPE_LIVE_ENABLED && _liveKey; // real-money mode act
const STRIPE_MURAL_KEY = STRIPE_LIVE ? _liveKey : _testKey;
const STRIPE_DOWNLOAD_KEY = _testKey;
const STRIPE_MODE = STRIPE_LIVE ? 'live' : 'test';
+// WEAR gets its OWN live switch, deliberately separate from STRIPE_LIVE_ENABLED
+// (Steve 2026-09-09, TK-10286): murals/downloads already flipped the shared
+// flag live for an unrelated feature, so if wear reused STRIPE_MURAL_KEY, the
+// moment WEAR_SALES_LIVE=1 landed it would charge REAL cards with zero chance
+// to place a test order first. WEAR_STRIPE_LIVE_ENABLED lets Steve turn
+// everything else about /wear on (Printify mapped, checkout functional, cart
+// working) while still charging the TEST key — then flip this ONE additional
+// switch, independently, once a real end-to-end test order looks right.
+const WEAR_STRIPE_LIVE_ENABLED = envVal('WEAR_STRIPE_LIVE_ENABLED') === '1';
+const WEAR_STRIPE_LIVE = WEAR_STRIPE_LIVE_ENABLED && _liveKey;
+const WEAR_STRIPE_KEY = WEAR_STRIPE_LIVE ? _liveKey : _testKey;
+const WEAR_STRIPE_MODE = WEAR_STRIPE_LIVE ? 'live' : 'test';
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)');
+if (WEAR_STRIPE_LIVE) console.log('⚠️ WEAR STRIPE LIVE MODE — apparel charges REAL cards'); else if (_testKey) console.log('Wear apparel: Stripe test mode (independent of the murals live flag)');
// ---- Google AdSense (Auto ads) ---------------------------------------------
// Driven by ONE env var, ADSENSE_PUB_ID (stored as `pub-XXXXXXXXXXXXXXXX`; a
// leading `ca-` is tolerated). When set, the loader is injected into every HTML
@@ -881,11 +894,13 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
const podProvider = wearPodProvider();
const podTemplates = await wearTemplates();
if (!wearPodReady(podTemplates, podProvider)) return sendJSON(res, 503, { ok: false, error: 'apparel fulfillment is not configured' });
- // Apparel is a physical POD sale — charge through the live-capable resolver
- // (STRIPE_MURAL_KEY: live only when STRIPE_LIVE_ENABLED=1 AND a sk_live_ key
- // is present, else the test key). NOT STRIPE_DOWNLOAD_KEY, which is test-only,
- // so wear would never have charged a real card. One consistent live switch.
- if (!STRIPE_MURAL_KEY) return sendJSON(res, 503, { ok: false, error: 'apparel not purchasable yet (awaiting Stripe key)' });
+ // Apparel is a physical POD sale — charge through wear's OWN live-capable
+ // resolver (WEAR_STRIPE_KEY: live only when WEAR_STRIPE_LIVE_ENABLED=1 AND
+ // a sk_live_ key is present, else the test key). Deliberately NOT
+ // STRIPE_MURAL_KEY — that flag is already live for an unrelated feature,
+ // so reusing it would let real charges start before Steve ever placed a
+ // test order through wear specifically.
+ if (!WEAR_STRIPE_KEY) return sendJSON(res, 503, { ok: false, error: 'apparel not purchasable yet (awaiting Stripe key)' });
const b = await readBody(req);
const email = String(b.email || '').trim();
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return sendJSON(res, 400, { ok: false, error: 'valid email required' });
@@ -921,7 +936,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
// WEAR_POD_PROVIDER (or its readiness) changes before the customer lands
// on /wear-success, the draft must still route to the provider that was
// actually configured — and eligible — when the charge happened.
- status: 'pending_payment', mode: STRIPE_MODE, provider: podProvider, pod_submitted: false };
+ status: 'pending_payment', mode: WEAR_STRIPE_MODE, provider: podProvider, pod_submitted: false };
list.push(order); await store('wear-orders.json', list);
const params = new URLSearchParams();
params.set('mode', 'payment');
@@ -936,13 +951,13 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
params.set(`line_items[${i}][price_data][currency]`, 'usd');
params.set(`line_items[${i}][price_data][unit_amount]`, String(it.amountCents));
params.set(`line_items[${i}][price_data][product_data][name]`, `${it.signature_name} signature — left-chest ${it.garment_label} (${it.color_label}, ${it.size})`);
- params.set(`line_items[${i}][price_data][product_data][description]`, `Signature printed at the left chest; made to order.${STRIPE_MODE === 'test' ? ' TEST checkout.' : ''}`);
+ params.set(`line_items[${i}][price_data][product_data][description]`, `Signature printed at the left chest; made to order.${WEAR_STRIPE_MODE === 'test' ? ' TEST checkout.' : ''}`);
});
params.set('metadata[order_id]', String(id));
try {
const sres = await fetch('https://api.stripe.com/v1/checkout/sessions', {
method: 'POST',
- headers: { Authorization: `Bearer ${STRIPE_MURAL_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
+ headers: { Authorization: `Bearer ${WEAR_STRIPE_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
const sj = await sres.json();
@@ -954,9 +969,9 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
if (path === '/wear-success' && M === 'GET') {
const sid = url.searchParams.get('sid') || '';
let paid = false, order = null;
- if (STRIPE_MURAL_KEY && /^cs_(test|live)_[A-Za-z0-9]+$/.test(sid)) {
+ if (WEAR_STRIPE_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_MURAL_KEY}` } })).json();
+ const s = await (await fetch(`https://api.stripe.com/v1/checkout/sessions/${sid}`, { headers: { Authorization: `Bearer ${WEAR_STRIPE_KEY}` } })).json();
if (s.payment_status === 'paid') {
paid = true;
const list = await load('wear-orders.json', []);
← f2976c0 wear: use 'convert' not 'magick' — prod box only has ImageMa
·
back to CelebritySignatures
·
wear: fix recolored signature URL being relative, not absolu c9d72f3 →