[object Object]

← back to Sample Box

step 2: server.js — add Shopify catalog endpoints, Shopify-hosted order capture

931b363d2a4e0e0779a73dd2bd7c1f81b07c5ce6 · 2026-07-16 07:37:18 -0700 · Steve Abrams

New endpoints (read-only, no token):
- GET /api/catalog-meta  → checkout_domain + sample_variant_id
- GET /api/shopify-products?sort=&q=  → 200 real products from shopify-catalog.json

POST /api/orders now records payment_method=SHOPIFY_HOSTED,
payment_status=pending_shopify, and shopify_permalink=
https://www.designerwallcoverings.com/cart/<sample_variant_id>:<qty>

Stripe TEST_STUB references removed from order flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files touched

Diff

commit 931b363d2a4e0e0779a73dd2bd7c1f81b07c5ce6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 16 07:37:18 2026 -0700

    step 2: server.js — add Shopify catalog endpoints, Shopify-hosted order capture
    
    New endpoints (read-only, no token):
    - GET /api/catalog-meta  → checkout_domain + sample_variant_id
    - GET /api/shopify-products?sort=&q=  → 200 real products from shopify-catalog.json
    
    POST /api/orders now records payment_method=SHOPIFY_HOSTED,
    payment_status=pending_shopify, and shopify_permalink=
    https://www.designerwallcoverings.com/cart/<sample_variant_id>:<qty>
    
    Stripe TEST_STUB references removed from order flow.
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 server.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 61 insertions(+), 5 deletions(-)

diff --git a/server.js b/server.js
index 5285ab5..a31e463 100644
--- a/server.js
+++ b/server.js
@@ -13,6 +13,16 @@ const DATA_DIR    = path.join(DIR, 'data');
 const ORDERS_FILE = path.join(DATA_DIR, 'orders.json');
 const BOX_PRICE   = 15.00; // $15 sample box, fully credited to first roll order
 
+// ── Shopify catalog (read-only static file — no token in server) ──────────────
+const SHOPIFY_CATALOG_FILE = path.join(DATA_DIR, 'shopify-catalog.json');
+let _shopifyCatalog = null;
+function loadShopifyCatalog() {
+  if (_shopifyCatalog) return _shopifyCatalog;
+  try { _shopifyCatalog = JSON.parse(fs.readFileSync(SHOPIFY_CATALOG_FILE, 'utf8')); }
+  catch { _shopifyCatalog = { products: [], checkout_domain: '', sample_variant_id: '', count: 0 }; }
+  return _shopifyCatalog;
+}
+
 // ── Data helpers ──────────────────────────────────────────────────────────────
 function loadProducts() {
   try { return JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'products.json'), 'utf8')); }
@@ -114,6 +124,44 @@ http.createServer(async (req, res) => {
     return res.end(JSON.stringify({ vibes: ALL_VIBES.filter(v => vibeSet.has(v.id)) }));
   }
 
+  // GET /api/catalog-meta — returns checkout_domain + sample_variant_id (public, no token)
+  if (u.pathname === '/api/catalog-meta' && mt === 'GET') {
+    const cat = loadShopifyCatalog();
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    return res.end(JSON.stringify({
+      checkout_domain:   cat.checkout_domain   || '',
+      sample_variant_id: cat.sample_variant_id || '',
+      pulled_at:         cat.pulled_at         || '',
+      shop_domain:       cat.shop_domain        || '',
+    }));
+  }
+
+  // GET /api/shopify-products?sort=newest&q= — real catalog from shopify-catalog.json
+  if (u.pathname === '/api/shopify-products' && mt === 'GET') {
+    const cat = loadShopifyCatalog();
+    let products = (cat.products || []).slice();
+    const q = (u.searchParams.get('q') || '').toLowerCase().trim();
+    if (q) {
+      products = products.filter(p =>
+        (p.title||'').toLowerCase().includes(q) ||
+        (p.vendor||'').toLowerCase().includes(q) ||
+        (p.sku||'').toLowerCase().includes(q) ||
+        (p.type||'').toLowerCase().includes(q)
+      );
+    }
+    const sortMode = u.searchParams.get('sort') || 'newest';
+    switch (sortMode) {
+      case 'title':      products.sort((a,b) => (a.title||'').localeCompare(b.title||'')); break;
+      case 'sku':        products.sort((a,b) => (a.sku||'').localeCompare(b.sku||'')); break;
+      case 'price-asc':  products.sort((a,b) => (a.price||0) - (b.price||0)); break;
+      case 'price-desc': products.sort((a,b) => (b.price||0) - (a.price||0)); break;
+      case 'vendor':     products.sort((a,b) => (a.vendor||'').localeCompare(b.vendor||'')); break;
+      default: break; // newest = natural catalog order
+    }
+    res.writeHead(200, { 'Content-Type': 'application/json' });
+    return res.end(JSON.stringify({ count: products.length, products }));
+  }
+
   // POST /api/orders — place a TEST order (no real charge)
   if (u.pathname === '/api/orders' && mt === 'POST') {
     const body = await readBody(req);
@@ -125,25 +173,33 @@ http.createServer(async (req, res) => {
     }
 
     const orderId = 'SB-' + Date.now().toString(36).toUpperCase();
+    // Build Shopify cart permalink using sample_variant_id × swatch count
+    const cat = loadShopifyCatalog();
+    const checkoutDomain   = cat.checkout_domain   || 'www.designerwallcoverings.com';
+    const sampleVariantId  = cat.sample_variant_id || '';
+    const swatchQty        = swatches.length;
+    const shopifyPermalink = sampleVariantId
+      ? `https://${checkoutDomain}/cart/${sampleVariantId}:${swatchQty}`
+      : null;
     const order   = {
       id:               orderId,
       vibe:             vibe || 'all',
-      swatches,                         // [{sku,title,image,price}]
+      swatches,                         // [{sku,title,image,price,variant_id}]
       swatch_count:     swatches.length,
       box_price:        BOX_PRICE,
       credit_note:      `$${BOX_PRICE.toFixed(2)} credited toward your first roll order (ref: ${orderId})`,
       email,
       shipping,                         // {name,address1,address2,city,state,zip,country}
-      payment_method:   payment_stub ? 'TEST_STUB' : 'STRIPE_TEST',
-      payment_status:   'paid_test',
-      stripe_session_id: payment_stub ? null : ('cs_test_' + crypto.randomBytes(12).toString('hex')),
+      payment_method:   'SHOPIFY_HOSTED',
+      payment_status:   'pending_shopify',
+      shopify_permalink: shopifyPermalink,
       status:           'pending_fulfillment',
       created_at:       new Date().toISOString(),
     };
 
     saveOrder(order);
     res.writeHead(201, { 'Content-Type': 'application/json' });
-    return res.end(JSON.stringify({ success: true, order_id: orderId, order }));
+    return res.end(JSON.stringify({ success: true, order_id: orderId, order, shopify_permalink: shopifyPermalink }));
   }
 
   // ── Admin (gated) ───────────────────────────────────────────────────────────

← 5e7cf3b step 1: copy real Shopify catalog (200 products, checkout_do  ·  back to Sample Box  ·  step 3: index.html — real Shopify catalog swatch grid + Shop 936c9a8 →