[object Object]

← back to Dw Signup Fulfillment

retail: FINAL = gift card (Steve, memo §2) — re-wire webhook+admin+selftest to giftcard, shared-code demoted to alternate; preserves trade.js customer-id hardening

f385a171ec7c944eff746c25277722b5ccac0ead · 2026-07-28 10:34:18 -0700 · Steve

Files touched

Diff

commit f385a171ec7c944eff746c25277722b5ccac0ead
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jul 28 10:34:18 2026 -0700

    retail: FINAL = gift card (Steve, memo §2) — re-wire webhook+admin+selftest to giftcard, shared-code demoted to alternate; preserves trade.js customer-id hardening
---
 scripts/selftest.js | 51 +++++++++++++++++++++------------------------------
 server.js           | 27 ++++++++++++++-------------
 2 files changed, 35 insertions(+), 43 deletions(-)

diff --git a/scripts/selftest.js b/scripts/selftest.js
index 78eea4e..d4a714b 100644
--- a/scripts/selftest.js
+++ b/scripts/selftest.js
@@ -53,8 +53,8 @@ function restore() {
 
 const config = require('../lib/config');
 const webhook = require('../lib/webhook');
-const retailCode = require('../lib/retail-code'); // WIRED retail path (DTD path B)
-const giftcard = require('../lib/giftcard');      // alternate (not wired)
+const giftcard = require('../lib/giftcard');      // WIRED retail path (FINAL — gift card, memo §2)
+const retailCode = require('../lib/retail-code'); // alternate (shared function code — needs admin discount)
 const trade = require('../lib/trade');
 const reps = require('../lib/reps');
 
@@ -66,49 +66,40 @@ let failures = 0;
 async function main() {
   console.log('DW signup fulfillment — SELFTEST (DRY_RUN=' + config.DRY_RUN + ')');
   console.log('Store: ' + config.SHOP_DOMAIN + '  API ' + config.SHOPIFY_API_VERSION);
-  console.log('Retail path (WIRED): sample-locked SHARED code (first ' + config.FREE_SAMPLE_COUNT + ' sample units free, function-enforced) · RETAIL_SHARED_CODE=' + (config.RETAIL_SHARED_CODE || '(unset — will WARN+skip send)'));
+  console.log('Retail path (WIRED): unique GIFT CARD (' + config.FREE_SAMPLE_COUNT + ' × $' + config.SAMPLE_PRICE + ' = $' + config.SAMPLE_GIFT_VALUE + ') emailed per signup');
   if (!config.DRY_RUN) { fail('DRY_RUN is OFF — refusing to run selftest that would make live writes'); return; }
 
   // ---------------------------------------------------------------------------
-  hr('(a) customers/create webhook — VALID HMAC → WIRED sample-locked shared-code path');
+  hr('(a) customers/create webhook — VALID HMAC → WIRED gift-card path');
   const fakeCustomer = { id: 8675309, email: 'newshopper@example.com', first_name: 'Dana', created_at: new Date().toISOString() };
   const raw = Buffer.from(JSON.stringify(fakeCustomer), 'utf8');
   const goodHmac = webhook.sign(raw, TEST_SECRET);
   console.log('  computed X-Shopify-Hmac-Sha256 = ' + goodHmac);
   if (webhook.verify(raw, goodHmac)) ok('HMAC verify ACCEPTED the valid signature'); else fail('valid HMAC was rejected');
 
-  console.log('  --- WIRED retail issuance (shared sample-locked code, what it WOULD do) ---');
-  const rcResult = await retailCode.issueRetailCode(fakeCustomer);
-  console.log('  result: ' + JSON.stringify(rcResult, null, 2));
-  if (rcResult.path === 'shared_code') ok('retail path is shared_code (sample-locked function discount — never touches a roll)'); else fail('retail path is not shared_code: ' + rcResult.path);
-  if (rcResult.code === TEST_SHARED_CODE) ok('emits the configured RETAIL_SHARED_CODE (' + rcResult.code + ')'); else fail('unexpected code: ' + rcResult.code);
-  if (rcResult.warn === null) ok('no WARN when RETAIL_SHARED_CODE is set'); else fail('unexpected warn: ' + rcResult.warn);
-  if (rcResult.email && rcResult.email.dryRun && !rcResult.email.skipped) ok('WOULD email the code to the customer (dry-run, not skipped)'); else fail('code email not dry-run / was skipped');
-
-  console.log('  --- fail-loud guard: unset RETAIL_SHARED_CODE must WARN + skip send ---');
-  const savedCode = process.env.RETAIL_SHARED_CODE; delete process.env.RETAIL_SHARED_CODE;
-  delete require.cache[require.resolve('../lib/config')]; delete require.cache[require.resolve('../lib/retail-code')];
-  const retailCodeUnset = require('../lib/retail-code');
-  const rcUnset = await retailCodeUnset.issueRetailCode(fakeCustomer);
-  if (rcUnset.warn && rcUnset.email.skipped) ok('unset code → WARN + email SKIPPED (never emails a wrong/blank code)'); else fail('unset code did not warn+skip');
-  if (savedCode != null) process.env.RETAIL_SHARED_CODE = savedCode;
-  delete require.cache[require.resolve('../lib/config')]; delete require.cache[require.resolve('../lib/retail-code')];
-
-  console.log('  --- alternate (NOT wired) gift-card path still functions for reference ---');
+  console.log('  --- WIRED retail issuance (gift card, what it WOULD do) ---');
   const gcResult = await giftcard.issueRetailGiftCode(fakeCustomer);
-  if (gcResult.path === 'gift_card' && gcResult.value === 12.75) ok('alternate gift_card path OK ($12.75) — kept for reference only'); else fail('alternate gift-card path broken');
+  console.log('  result: ' + JSON.stringify(gcResult, null, 2));
+  if (gcResult.path === 'gift_card') ok('retail path is gift_card (unique code emailed per signup)'); else fail('retail path is not gift_card: ' + gcResult.path);
+  if (gcResult.value === 12.75) ok('gift value = 3 × $4.25 = $12.75'); else fail('unexpected gift value: ' + gcResult.value);
+  const would = gcResult.shopifyCall && gcResult.shopifyCall.WOULD;
+  if (would && /gift_cards/.test(would)) ok('WOULD POST gift_cards (' + would + ')'); else fail('did not record a WOULD gift_cards call');
+  if (gcResult.email && gcResult.email.dryRun) ok('WOULD email the gift code to the customer (dry-run, no real send)'); else fail('gift email not dry-run');
+
+  console.log('  --- gift email clearly offers the 3 free samples ---');
+  const gtpl = require('../lib/email').retailGiftEmail({ firstName: 'Dana', code: 'DEMO-CODE-1234', value: 12.75, count: 3 });
+  const blob = (gtpl.subject || '') + ' ' + (gtpl.html || '');
+  if (/\b3\b/.test(blob) && /sample/i.test(blob)) ok('email references "3" and "sample"'); else fail('email does not clearly offer 3 free samples');
+  if (/DEMO-CODE-1234/.test(blob)) ok('email includes the gift code'); else fail('email missing the code');
+
+  console.log('  --- alternate (NOT wired) shared-code path still shape-checks ---');
+  const rcResult = await retailCode.issueRetailCode(fakeCustomer);
+  if (rcResult.path === 'shared_code') ok('alternate shared_code path present — kept for reference only'); else fail('alternate shared-code path broken: ' + rcResult.path);
 
   // ---------------------------------------------------------------------------
   hr('(b) customers/create webhook — INVALID HMAC → rejected');
   if (!webhook.verify(raw, 'this-is-not-the-right-signature')) ok('HMAC verify REJECTED a bad signature'); else fail('bad HMAC was accepted');
 
-  // ---------------------------------------------------------------------------
-  hr('(a2) WIRED code email clearly offers the 3 free samples + includes the code');
-  const gtpl = require('../lib/email').retailCodeEmail({ firstName: 'Dana', code: 'DEMO-CODE-1234', count: 3 });
-  const blob = (gtpl.subject || '') + ' ' + (gtpl.html || '');
-  if (/\b3\b/.test(blob) && /sample/i.test(blob)) ok('email references "3" and "sample"'); else fail('email does not clearly offer 3 free samples');
-  if (/DEMO-CODE-1234/.test(blob)) ok('email includes the sample code'); else fail('email missing the code');
-
   // ---------------------------------------------------------------------------
   hr('(c) trade application → moderated approve');
   const created = trade.apply({ email: 'Studio@BigDesignCo.com', business_name: 'Big Design Co', resale_cert: 'CA-RESALE-99887', phone: '310-555-0142', shopify_customer_id: 5551234 });
diff --git a/server.js b/server.js
index 16fd759..e141bc5 100644
--- a/server.js
+++ b/server.js
@@ -11,8 +11,8 @@
 const express = require('express');
 const config = require('./lib/config');
 const webhook = require('./lib/webhook');
-const retailCode = require('./lib/retail-code');       // WIRED default: sample-locked shared function code (memo §2, DTD path B)
-const giftcard = require('./lib/giftcard');            // alternate (not wired — spendable on rolls)
+const giftcard = require('./lib/giftcard');            // WIRED default (FINAL — Steve, memo §2): unique gift-card code per signup
+const retailCode = require('./lib/retail-code');       // alternate (shared function code — needs a DW Free Samples admin discount)
 const giftcodeDiscount = require('./lib/giftcode-discount'); // alternate (not wired)
 const trade = require('./lib/trade');
 const reps = require('./lib/reps');
@@ -60,11 +60,13 @@ app.post('/webhooks/customers/create',
     res.status(200).json({ ok: true, received: true });
     try {
       console.log(`[webhook] customers/create id=${customer.id} email=${customer.email}`);
-      // WIRED retail path (DTD 2026-07-28, 5/5 → path B): email the new customer the
-      // SAMPLE-LOCKED shared "DW Free Samples" code (function-backed, never discounts a
-      // roll). Matches approved memo §2. Gift-card path retained as alternate only.
-      const result = await retailCode.issueRetailCode(customer);
-      console.log('[webhook] retail shared-code result:', JSON.stringify(result));
+      // WIRED retail path (FINAL — Steve 2026-07-28, memo §2): email the new customer a
+      // unique ~$12.75 gift-card code for their 3 free samples. Works with our token, no
+      // admin discount needed, risk bounded to the balance. The sample-locked shared-code
+      // path is retained as a labeled alternate (it needs a DW Free Samples admin discount
+      // that was never created — only a stray "3FREE" basic discount existed).
+      const result = await giftcard.issueRetailGiftCode(customer);
+      console.log('[webhook] retail gift-card result:', JSON.stringify(result));
     } catch (e) {
       console.error('[webhook] fulfillment error:', e.message);
     }
@@ -131,17 +133,16 @@ app.post('/admin/trade/:id/reject', adminAuth, async (req, res) => {
 app.get('/reps/next', adminAuth, (_req, res) => res.json({ assigned: reps.houseAccount() }));
 
 // Manual retail trigger (admin) — handy for go-live smoke test without a real
-// webhook. Default mirrors the WIRED sample-locked shared-code path (DTD path B).
-// ALTERNATES for comparison only: ?mode=giftcard (stored value) and ?mode=discount
-// (collection code).
+// webhook. Default mirrors the WIRED gift-card path (FINAL, memo §2). ALTERNATES for
+// comparison only: ?mode=sharedcode (function code) and ?mode=discount (collection code).
 app.post('/admin/retail/issue', adminAuth, async (req, res) => {
   const customer = req.body || {};
   if (!customer.email) return res.status(400).json({ ok: false, error: 'email required' });
-  let mode = req.query.mode || 'sharedcode';
+  let mode = req.query.mode || 'giftcard';
   let result;
-  if (mode === 'giftcard') result = await giftcard.issueRetailGiftCode(customer);            // alternate (spendable on rolls)
+  if (mode === 'sharedcode') result = await retailCode.issueRetailCode(customer);            // alternate (needs admin discount)
   else if (mode === 'discount') result = await giftcodeDiscount.issueRetailDiscountCode(customer); // alternate
-  else { mode = 'sharedcode'; result = await retailCode.issueRetailCode(customer); }          // WIRED default
+  else { mode = 'giftcard'; result = await giftcard.issueRetailGiftCode(customer); }          // WIRED default
   res.json({ ok: true, mode, result });
 });
 

← 379738e harden per contrarian gate: resolve trade customer-id by ema  ·  back to Dw Signup Fulfillment  ·  hosting: live on Kamatera dwsignup.agentabrams.com (pm2 :986 766b6b6 →