[object Object]

← back to Fineartamerica Price Sync

Free HTTP FAA cost fetcher (stateful configurator) — pilot: 36/36 configs priced, all below cost

54dfec429a7d7edb2cf6c5fabf842e235a96a0fe · 2026-07-16 08:56:25 -0700 · Steve Abrams

Files touched

Diff

commit 54dfec429a7d7edb2cf6c5fabf842e235a96a0fe
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 16 08:56:25 2026 -0700

    Free HTTP FAA cost fetcher (stateful configurator) — pilot: 36/36 configs priced, all below cost
---
 bin/faa-fetch-http.js | 46 ++++++++++++++++++++++++++++++++++
 lib/faa-http.js       | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 115 insertions(+)

diff --git a/bin/faa-fetch-http.js b/bin/faa-fetch-http.js
new file mode 100644
index 0000000..1cf9cc4
--- /dev/null
+++ b/bin/faa-fetch-http.js
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+// Populate data/cost-cache.json with real FAA costs via the free HTTP
+// configurator endpoint (lib/faa-http.js). No Browserbase, no login.
+//
+//   node bin/faa-fetch-http.js --url "<faa product page>" --from-shopify <productId>
+//
+// For each distinct config on the Shopify product, replays FAA's setter sequence
+// and caches the returned cost keyed by configKey. Falls back to the page's
+// artworkId when the SKU's artworkId prices null (handles the SKU-vs-listing id
+// drift, e.g. Washington Top Hat SKU 65269183 vs live listing 65119339).
+
+const { openSession, priceFramedConfig } = require('../lib/faa-http');
+const { parseSku, configKey, configLabel } = require('../lib/parse-sku');
+const { loadCache, saveCache } = require('../lib/faa-cost');
+const { getProduct } = require('../lib/shopify');
+
+const args = process.argv.slice(2);
+const val = f => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : null; };
+const URL0 = val('--url');
+const PID = val('--from-shopify');
+
+(async () => {
+  if (!URL0 || !PID) { console.error('need --url and --from-shopify <productId>'); process.exit(1); }
+  const p = await getProduct(PID);
+  const seen = new Set(), configs = [];
+  for (const v of p.variants) {
+    const cfg = parseSku(v.sku); if (!cfg) continue;
+    const k = configKey(cfg); if (seen.has(k)) continue; seen.add(k); configs.push(cfg);
+  }
+  console.log(`${configs.length} distinct FAA configs on "${p.title}"`);
+
+  const session = await openSession(URL0);
+  console.log(`FAA session sid=${session.sessionId.slice(0,10)}… pageArtwork=${session.pageArtworkId}`);
+  const cache = loadCache();
+  let ok = 0, fail = 0;
+  for (const cfg of configs) {
+    let cost = await priceFramedConfig(session, cfg);
+    if (cost == null && cfg.artworkid !== session.pageArtworkId) {
+      cost = await priceFramedConfig(session, { ...cfg, artworkid: session.pageArtworkId }); // fallback
+    }
+    if (cost != null) { cache[configKey(cfg)] = cost; ok++; console.log(`  ✓ ${configLabel(cfg)} → $${cost.toFixed(2)}`); }
+    else { fail++; console.log(`  ✗ ${configLabel(cfg)} → null`); }
+  }
+  saveCache(cache);
+  console.log(`\ncosted ${ok}, failed ${fail}. cost-cache.json updated. ($0 — free HTTP)`);
+})().catch(e => { console.error(e); process.exit(1); });
diff --git a/lib/faa-http.js b/lib/faa-http.js
new file mode 100644
index 0000000..4ae6071
--- /dev/null
+++ b/lib/faa-http.js
@@ -0,0 +1,69 @@
+// FREE, login-free FAA per-config cost fetcher (cracked 2026-07-16).
+//
+// FAA's framed-print configurator is a STATEFUL session: each option selection
+// POSTs an `action=set*/select*` to
+//   /queries/queryUpdateProductConfiguratorProductPrint.php
+// which mutates the session's config and returns {status, price}. So to price
+// one variant we replay its setter sequence in a single cookie session and read
+// the price off the final (setPrintSize) response. No browser, no login.
+//
+// Proven: 20x24 CRQ13/PM918 = $195.00, 7x8 = $74.00, 34x40 = $350.00.
+
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36';
+const PRICE_URL = 'https://fineartamerica.com/queries/queryUpdateProductConfiguratorProductPrint.php';
+
+function parseCookies(setCookies) {
+  // node fetch exposes combined Set-Cookie via getSetCookie()
+  return (setCookies || []).map(c => c.split(';')[0]).join('; ');
+}
+
+// Open a configurator session for one FAA product page; capture cookies + ids.
+async function openSession(pageUrl) {
+  const res = await fetch(pageUrl + (pageUrl.includes('?') ? '&' : '?') + 'product=framed-print',
+    { headers: { 'User-Agent': UA } });
+  const cookie = parseCookies(res.headers.getSetCookie ? res.headers.getSetCookie() : []);
+  const html = await res.text();
+  const g = n => {
+    const m = html.match(new RegExp(`id='${n}'\\s+value='([^']*)'`)) ||
+              html.match(new RegExp(`name='${n}'\\s+value='([^']*)'`));
+    return m ? m[1] : '';
+  };
+  return { pageUrl, cookie, sessionId: g('sessionId'), uniqueId: g('uniqueId'), pageArtworkId: g('artworkId') };
+}
+
+async function post(session, params) {
+  const res = await fetch(PRICE_URL, {
+    method: 'POST',
+    headers: {
+      'User-Agent': UA,
+      'X-Requested-With': 'XMLHttpRequest',
+      'Content-Type': 'application/x-www-form-urlencoded',
+      'Referer': session.pageUrl + '?product=framed-print',
+      ...(session.cookie ? { Cookie: session.cookie } : {}),
+    },
+    body: `sessionId=${session.sessionId}&uniqueId=${session.uniqueId}&${params}`,
+  });
+  const txt = await res.text();
+  try { return JSON.parse(txt); } catch { return { status: 'error', raw: txt.slice(0, 120) }; }
+}
+
+// Price one parsed framed-print config. Replays the setter sequence; returns the
+// FAA fulfillment cost (number) or null on failure.
+async function priceFramedConfig(session, cfg) {
+  const artworkId = cfg.artworkid || session.pageArtworkId;
+  const base = `productId=printframed&artworkId=${artworkId}`;
+  const steps = [
+    `${base}&action=selectPaper&paperId=${cfg.paperid}`,
+    `${base}&action=selectFrame&frameId=${cfg.frameid}`,
+    `${base}&action=selectMat1&mat1Id=${cfg.mat1id}`,
+    `${base}&action=selectMatWidth&matWidth=${cfg.mat1width || 2}`,
+    `${base}&action=selectFinish&finishId=${cfg.finishid}`,
+    `${base}&action=setPrintSize&width=${cfg.imagewidth}&height=${cfg.imageheight}`,
+  ];
+  let last = null;
+  for (const s of steps) last = await post(session, s);
+  if (!last || last.status !== 'success' || last.price == null) return null;
+  return parseFloat(last.price);
+}
+
+module.exports = { openSession, priceFramedConfig, PRICE_URL };

← ac5c24e FAA price-sync: SKU->FAA cost->cost/0.65/0.85 Shopify repric  ·  back to Fineartamerica Price Sync  ·  auto-save: 2026-07-16T09:12:20 (3 files) — bin/faa-fetch-all cc49883 →