@blamejs/blamejs-shop 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.1.x
10
10
 
11
+ - v0.1.2 (2026-05-25) — **Apple Pay and Google Pay express checkout.** The pay page now offers one-tap wallet checkout. Stripe's Express Checkout Element renders Apple Pay and Google Pay buttons above the card form on eligible devices, confirming the same PaymentIntent as the card path — so the webhook and order flow are unchanged. To turn the wallets on, the operator registers the shop's web domain with Stripe once via the admin API; Stripe performs Apple merchant validation and hosts the domain-association file, so no Apple Developer account is needed. **Added:** *Wallet buttons on the pay page* — The Stripe Express Checkout Element mounts above the card form and auto-renders Apple Pay / Google Pay (and Link) when the device and the shop's registered domain make them available. It stays hidden until Stripe reports an available wallet, and confirms the existing per-order PaymentIntent — the payment-completion path (webhook → order FSM) is identical to the card flow. · *Payment-method domain registration* — `POST /admin/payment-method-domains` (with `{ "domain_name": "shop.example.com" }`) registers a domain with Stripe to enable the wallets; `GET /admin/payment-method-domains` lists registered domains and their per-method status. The payment adapter gains `registerPaymentMethodDomain` + `listPaymentMethodDomains`. Apex, www, and each subdomain register separately; a live-mode registration also covers sandbox.
12
+
11
13
  - v0.1.1 (2026-05-25) — **Admin setup wizard + a browser-accessible admin console.** The admin is now reachable from a browser, not just the bearer-token JSON API. Sign in once at /admin by pasting the ADMIN_API_KEY and a sealed, /admin-scoped session cookie carries you through the guided setup wizard (shop name, contact email, default currency, support URL — saved to shop config) and the analytics dashboard. The shop name set in the wizard drives the storefront header, page titles, and the admin header. **Added:** *Browser admin sign-in* — `/admin` renders a sign-in form; pasting the ADMIN_API_KEY sets a sealed `shop_admin` session cookie (SameSite=Strict, scoped to /admin) so the rendered admin pages are reachable from a browser. The JSON API stays bearer-only; the dashboard accepts either the cookie or the bearer token. · *Setup wizard* — `/admin/setup` is a guided form for the shop's core identity — name, contact email, default currency, support URL — validated (ISO-4217 currency, RFC-shaped email, http(s) support URL) and saved to shop config. The landing nags until setup is complete; the shop name then drives the storefront and admin headers.
12
14
 
13
15
  - v0.1.0 (2026-05-25) — **Responsive cart + storefront cookie handling moved onto the framework primitive.** A storefront polish pass. The cart, order-confirmation, and account-history tables now reflow into stacked, labelled cards on phones and fit their column on wider screens — no more inner horizontal scroll. The quantity field reads clearly and accepts up to 99,999. The line actions (Update / Save for later / Remove) are compact with a clear hierarchy. Under the hood, all storefront cookie handling now composes the framework's cookie primitive (RFC 6265 parse/serialize plus vault-sealed read/write) instead of hand-built headers, and a malformed session cookie can no longer turn the cart — or any page that shows the cart count — into a 500. **Changed:** *Quantity field is readable and accepts up to 99,999* — The cart quantity input is wide enough to read a five-digit quantity, and the per-line maximum is raised to 99,999 (enforced on the server). · *Compact, clearly-ranked line actions* — Update is a small accent button, Save for later a quiet secondary, Remove a danger-tinted secondary — so the primary checkout call stays dominant. · *Cookie handling composes the framework primitive* — Session, authentication, WebAuthn-challenge, and payment cookies are all parsed and written through the framework cookie primitive (sealed read/write for the authenticated-session cookie), replacing hand-built Set-Cookie strings and manual header parsing. Cookie lifetimes are expressed through the framework's duration constants. **Fixed:** *Cart tables no longer scroll sideways* — The cart, order-confirmation, and account order-history tables reflow into one labelled card per row below 48rem and are sized to fit their column on wider layouts, so content is never trapped behind an inner horizontal scrollbar. · *Malformed session cookie no longer 500s the storefront* — A `shop_sid` cookie carrying a value that isn't a well-formed session id now reads as "no session" instead of reaching the cart lookup (which rejected it and surfaced a 500 on every page that renders the cart count). · *Account dashboard controls wrap on narrow screens* — The row of account actions (Wishlist / Saved for later / Recently viewed / Addresses / Returns / Sign out) now wraps instead of overflowing the viewport on a phone.
package/README.md CHANGED
@@ -57,7 +57,7 @@ Every primitive is composed on the vendored blamejs surface — no npm runtime d
57
57
  | **`lib/pricing.js`** | Pure-function money math — `lineTotal`, `subtotal`, `totals`, `format`. Multi-currency refused, banker's-style rounding, locale-aware via `Intl.NumberFormat`. |
58
58
  | **`lib/tax.js`** | Operator-table adapter. Country / state / postal_prefix → rate_bps. Most-specific-first match, banker's rounding. Pluggable adapter shape for future Stripe Tax / TaxJar / Avalara. |
59
59
  | **`lib/shipping.js`** | Operator-table adapter. Services with zones (flat or per-gram + base + min/max), free-over-threshold, `digital_only` flag. |
60
- | **`lib/payment.js`** | Stripe adapter — verify webhook (HMAC-SHA256 via upstream `b.webhook.verify` alg `hmac-sha256-stripe`), create / retrieve / confirm / cancel PaymentIntent, refund. No `stripe` npm dep — outbound through `b.httpClient` (SSRF-gated, retried, circuit-broken). |
60
+ | **`lib/payment.js`** | Stripe adapter — verify webhook (HMAC-SHA256 via upstream `b.webhook.verify` alg `hmac-sha256-stripe`), create / retrieve / confirm / cancel PaymentIntent, refund, register / list payment-method domains (Apple Pay + Google Pay enablement for the Express Checkout Element). No `stripe` npm dep — outbound through `b.httpClient` (SSRF-gated, retried, circuit-broken). |
61
61
  | **`lib/order.js`** | FSM-driven post-checkout record via upstream `b.fsm`. States: pending → paid → fulfilling → shipped → delivered (+ refunded / cancelled). Every transition appends to `order_transitions`. |
62
62
  | **`lib/checkout.js`** | Orchestrator. `quote()` returns priced quote; `confirm()` creates PaymentIntent + persists order in pending; `handleStripeEvent()` verifies webhook + fires the FSM transition (idempotent on re-delivery). |
63
63
  | **`lib/email.js`** | Transactional templates — order receipt, ship notification, refund confirmation. Strict `{{var}}` renderer with HTML escape + refusal of unknown / unused placeholders. Composed on `b.mail` (DKIM/SPF/DMARC/BIMI upstream). |
package/lib/admin.js CHANGED
@@ -789,6 +789,31 @@ function mount(router, deps) {
789
789
  }));
790
790
  }
791
791
 
792
+ // ---- payment-method domains (Apple Pay / Google Pay enablement) -----
793
+ //
794
+ // Registering the shop's web domain with Stripe enables the wallet
795
+ // methods for the Express Checkout Element on the pay page. Stripe
796
+ // performs Apple merchant validation + hosts the association file, so
797
+ // there's no Apple Developer account to wire — this is the operator's
798
+ // one-shot action. Disabled when the payment dep is absent.
799
+ if (payment) {
800
+ router.post("/admin/payment-method-domains", W("payment_domain.register", async function (req, res) {
801
+ var body = req.body || {};
802
+ var domainName = body.domain_name;
803
+ var result = await payment.registerPaymentMethodDomain(domainName);
804
+ _json(res, 201, result);
805
+ return { id: (result && result.id) || domainName };
806
+ }));
807
+
808
+ router.get("/admin/payment-method-domains", R(async function (req, res) {
809
+ var url = req.url ? new URL(req.url, "http://localhost") : null;
810
+ var domainName = url && url.searchParams.get("domain_name");
811
+ var filter = domainName ? { domain_name: domainName } : {};
812
+ var result = await payment.listPaymentMethodDomains(filter);
813
+ _json(res, 200, result);
814
+ }));
815
+ }
816
+
792
817
  // ---- webhooks -------------------------------------------------------
793
818
 
794
819
  var webhooks = deps.webhooks || null;
package/lib/payment.js CHANGED
@@ -370,6 +370,33 @@ function stripe(opts) {
370
370
  {}, idempotencyKey);
371
371
  },
372
372
 
373
+ // Register a web domain so Stripe enables the wallet methods (Apple
374
+ // Pay / Google Pay / Link / PayPal) for the Express Checkout Element
375
+ // served from it. Stripe performs Apple merchant validation + hosts
376
+ // the domain-association file — the operator does not need an Apple
377
+ // Developer account. Registering in live mode also registers
378
+ // sandbox. One-shot operator action (admin endpoint). `domainName`
379
+ // is a bare hostname — apex, www, and subdomains register separately.
380
+ registerPaymentMethodDomain: function (domainName, idempotencyKey) {
381
+ if (typeof domainName !== "string" ||
382
+ !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/i.test(domainName)) {
383
+ throw new TypeError("payment.registerPaymentMethodDomain: domainName must be a bare hostname (no scheme / path / port)");
384
+ }
385
+ return _stripeCall(opts, "POST", "/payment_method_domains", { domain_name: domainName }, idempotencyKey);
386
+ },
387
+
388
+ // List registered payment-method domains (optionally filtered to one
389
+ // hostname) with each method's enablement status. Read-only.
390
+ listPaymentMethodDomains: function (filter) {
391
+ filter = filter || {};
392
+ var path = "/payment_method_domains";
393
+ if (filter.domain_name) {
394
+ if (typeof filter.domain_name !== "string") throw new TypeError("payment.listPaymentMethodDomains: domain_name must be a string");
395
+ path += "?domain_name=" + encodeURIComponent(filter.domain_name);
396
+ }
397
+ return _stripeCall(opts, "GET", path, null, null);
398
+ },
399
+
373
400
  refund: function (input, idempotencyKey) {
374
401
  if (!input || typeof input !== "object") throw new TypeError("payment.refund: input object required");
375
402
  _assertSecret(input.payment_intent, "refund.payment_intent");
package/lib/storefront.js CHANGED
@@ -1617,6 +1617,10 @@ var PAY_PAGE =
1617
1617
  " <p class=\"section-head__lede\">Order <code class=\"inline-code\">{{order_id}}</code> · the Stripe Payment Element is mounted below in a same-origin form.</p>\n" +
1618
1618
  " </header>\n" +
1619
1619
  " <div class=\"pay-card\">\n" +
1620
+ " <div id=\"express-checkout\" class=\"pay-card__express\" hidden>\n" +
1621
+ " <div id=\"express-checkout-element\"></div>\n" +
1622
+ " <div class=\"pay-card__divider\"><span>or pay with card</span></div>\n" +
1623
+ " </div>\n" +
1620
1624
  " <div id=\"payment-element\" class=\"pay-card__element\"></div>\n" +
1621
1625
  " <button id=\"submit\" type=\"button\" class=\"btn-primary pay-card__submit\">Pay {{grand_total}}</button>\n" +
1622
1626
  " <p id=\"payment-message\" class=\"pay-card__message\"></p>\n" +
@@ -1626,14 +1630,29 @@ var PAY_PAGE =
1626
1630
  " (function () {\n" +
1627
1631
  " var stripe = Stripe({{pk_json}});\n" +
1628
1632
  " var elements = stripe.elements({ clientSecret: {{client_secret_json}}, appearance: { theme: \"stripe\" } });\n" +
1629
- " var paymentElement = elements.create(\"payment\");\n" +
1630
- " paymentElement.mount(\"#payment-element\");\n" +
1631
- " document.getElementById(\"submit\").addEventListener(\"click\", function () {\n" +
1632
- " document.getElementById(\"payment-message\").textContent = \"Processing...\";\n" +
1633
- " stripe.confirmPayment({ elements: elements, confirmParams: { return_url: window.location.origin + \"/orders/{{order_id}}\" } }).then(function (result) {\n" +
1634
- " if (result.error) { document.getElementById(\"payment-message\").textContent = result.error.message || \"Payment failed.\"; }\n" +
1633
+ " var returnUrl = window.location.origin + \"/orders/{{order_id}}\";\n" +
1634
+ " var message = document.getElementById(\"payment-message\");\n" +
1635
+ " function confirm() {\n" +
1636
+ " message.textContent = \"Processing...\";\n" +
1637
+ " return stripe.confirmPayment({ elements: elements, confirmParams: { return_url: returnUrl } }).then(function (result) {\n" +
1638
+ " if (result.error) { message.textContent = result.error.message || \"Payment failed.\"; }\n" +
1635
1639
  " });\n" +
1640
+ " }\n" +
1641
+ " // Express Checkout Element — renders Apple Pay / Google Pay /\n" +
1642
+ " // Link wallet buttons when the device + the shop's registered\n" +
1643
+ " // payment-method domain make them available. It confirms the\n" +
1644
+ " // same PaymentIntent as the card form, so the webhook + order\n" +
1645
+ " // FSM are identical. Hidden until Stripe reports an available\n" +
1646
+ " // wallet so the divider never sits over an empty box.\n" +
1647
+ " var ece = elements.create(\"expressCheckout\");\n" +
1648
+ " ece.on(\"ready\", function (ev) {\n" +
1649
+ " if (ev && ev.availablePaymentMethods) { document.getElementById(\"express-checkout\").hidden = false; }\n" +
1636
1650
  " });\n" +
1651
+ " ece.on(\"confirm\", function () { confirm(); });\n" +
1652
+ " ece.mount(\"#express-checkout-element\");\n" +
1653
+ " var paymentElement = elements.create(\"payment\");\n" +
1654
+ " paymentElement.mount(\"#payment-element\");\n" +
1655
+ " document.getElementById(\"submit\").addEventListener(\"click\", function () { confirm(); });\n" +
1637
1656
  " })();\n" +
1638
1657
  " </script>\n" +
1639
1658
  "</section>\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {