@base44/app-plugin-commerce 0.2.6 → 0.3.1

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.
Files changed (36) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -2
  3. package/scripts/install.js +15 -0
  4. package/skills/commerce/SKILL.md +31 -13
  5. package/skills/commerce/docs/api-admin.md +1 -1
  6. package/skills/commerce/docs/api-storefront.md +12 -12
  7. package/skills/commerce/install/01-install.md +5 -2
  8. package/skills/commerce/install/02-storefront.md +179 -220
  9. package/skills/commerce/install/03-data.md +1 -1
  10. package/skills/commerce/references/catalog-rendering.md +37 -43
  11. package/skills/commerce/references/reviews.md +21 -14
  12. package/skills/commerce/references/store-settings.md +1 -1
  13. package/skills/commerce/references/storefront-verification.md +21 -15
  14. package/src/commerce/storefront/StorefrontProvider.jsx +65 -128
  15. package/src/commerce/storefront/cartUI.jsx +26 -117
  16. package/src/commerce/storefront/index.js +61 -98
  17. package/src/commerce/storefront/pickers.jsx +53 -81
  18. package/src/commerce/storefront/useCartLine.js +23 -130
  19. package/src/commerce/storefront/useCheckout.jsx +50 -43
  20. package/src/commerce/storefront/useOrderReturn.js +17 -7
  21. package/src/commerce/storefront/useProduct.js +54 -119
  22. package/src/commerce/storefront/useProductList.js +15 -28
  23. package/src/commerce/utils/address-spec.js +1 -1
  24. package/src/commerce/utils/images.js +1 -1
  25. package/src/commerce/utils/index.js +9 -9
  26. package/src/commerce/utils/price.js +2 -1
  27. package/src/commerce/utils/specs.js +41 -91
  28. package/src/commerce/utils/totals.js +7 -4
  29. package/src/commerce/storefront/useAddressForm.js +0 -175
  30. package/src/commerce/storefront/usePlaceOrder.js +0 -55
  31. package/src/commerce/storefront/useProductGallery.js +0 -78
  32. package/src/commerce/storefront/useProductPrice.js +0 -58
  33. package/src/commerce/storefront/useProductReviews.js +0 -242
  34. package/src/commerce/storefront/useStorefrontSeo.js +0 -204
  35. package/src/commerce/storefront/useTotalsLines.js +0 -109
  36. package/src/commerce/storefront/useUpsell.js +0 -90
package/README.md CHANGED
@@ -12,7 +12,7 @@ It provides a full-featured **commerce data model and behavior** (variant-driven
12
12
  - **Shared commerce engine** (`base44/shared/commerce/`) — totals, tax, shipping, coupons, stock, order lifecycle, webhook dispatch (HMAC-signed), emails, card-payment plumbing, plus static country/currency/continent data.
13
13
  - **Admin UI** (`src/commerce/admin/`) — a React/Tailwind/shadcn admin with a familiar store back-office information architecture: dashboard, orders, products, coupons, customers, reports, and full settings including webhooks. Admin-role gated.
14
14
  - **Storefront helpers** (`src/commerce/utils/`) — framework-free, dependency-free modules for the shopfront you build: `storefront.js` is the API client (`createStorefront(base44)` — cart-token lifecycle, cached store-info, catalog/cart/checkout/reviews/return-page calls); `variants.js` maps an attribute selection (Size, Color) onto a `ProductVariation` and back, plus per-option availability and price ranges; `price.js` encodes the from-price and price-range rules; `totals.js` projects a cart *or* an order into one summary shape; `address-spec.js` is the checkout address form as data; `images.js` normalizes catalog images; `shipping-promos.js` reads the store's real free-shipping configuration so "Free shipping over €150" states a configured rule rather than an invented number.
15
- - **Storefront React layer** (`src/commerce/storefront/`) — **headless: the logic is premade, the UI never is.** Nothing in the layer renders markup or carries CSS; every element, class and word of copy in the storefront you build is yours, so a brief like "make it feel like <site>" applies to the whole store, checkout included. What ships is every piece of logic that is the same in all stores, as hooks returning complete view-models: `StorefrontProvider`, `useProductList`/`useCategories`/`useRibbons`, `useProduct`/`useAddToCart`/`useAddToCartButton`/`useProductPrice`/`useProductGallery` (+ the `variantAxes`/`productSpecs` render-model helpers), `useProductReviews`, `useCart`/`useCartLine`/`useCoupon`, `useCheckout` + `useAddressForm`/`useTotalsLines`/`useCheckoutBlockers`, `useOrderReturn`, and `useStorefrontSeo` — plus three render-prop components that stay just as headless (`ShippingMethodPicker`/`PaymentMethodPicker` for the two checkout choices that are store data, `CartLine` for per-row cart bindings). Each hook's doc comment states the render rules that keep a store correct (an unbuyable variant option renders disabled, not hidden; a receipt page must render `paymentInstructions`; …). Needs React and nothing else.
15
+ - **Storefront React layer** (`src/commerce/storefront/`) — **headless: the logic is premade, the UI never is.** Nothing in the layer renders markup or carries CSS; every element, class and word of copy in the storefront you build is yours, so a brief like "make it feel like <site>" applies to the whole store, checkout included. **It ships no customer-facing copy either**: where a state needs words you get the *state* — `buy.state`, a picker's `hint.code`, the checkout's `blockers`, a review's `status` — and write the sentence. What ships is every piece of logic that is the same in all stores: `StorefrontProvider` (+ `useStorefront`/`useStoreInfo`/`useFormatMoney`/`useCountries`), `useProductList`/`useCategories`/`useRibbons`, `useProduct`/`useAddToCart`, `useCart`/`useCartLine` (+ `useCartUI`/`CartUIProvider` for a drawer), `useCheckout`/`CheckoutProvider`/`useCheckoutContext`, `useOrderReturn`/`orderReceivedUrl` — plus three render-prop components that stay just as headless (`ShippingMethodPicker`/`PaymentMethodPicker` for the two checkout choices that are store data, `CartLine` for per-row cart bindings), and the framework-free view-model helpers re-exported so one import line covers a page (`variantAxes`, `productPrice`, `productImages`, `productSpecs`, `attributesLabel`, `cartTotalsLines`/`orderTotalsLines`, `addressFieldSpec`). Each hook's doc comment states the render rules that keep a store correct (an unbuyable variant option renders disabled, not hidden; a receipt page must render `paymentInstructions`; …). Needs React and nothing else.
16
16
  - **StoreAdmin agent + bot** — an AI copilot (`base44/agents/commerce/StoreAdmin.jsonc`, registered as `commerce/StoreAdmin`) with the `commerce/*` functions attached directly as tools (calls run as the chatting user → `requireAdmin()` still applies), variant-aware order editing, plus a chat panel in the admin sidebar with GFM markdown-table rendering.
17
17
  - **Docs** — this README plus the commerce skill folder [`skills/commerce/`](./skills/commerce/): [`SKILL.md`](./skills/commerce/SKILL.md) is the map every agent starts from (and the only path the platform needs to know); [`install/`](./skills/commerce/install/) holds the three stage files that are the whole install (`01-install` → `02-storefront` → `03-data`, each read at the moment its work starts and dropped when its checklist passes); [`references/`](./skills/commerce/references/) holds per-topic guides opened only on demand; [`docs/`](./skills/commerce/docs/) holds the data-model map ([`entities.md`](./skills/commerce/docs/entities.md)) and the two API references. The whole folder is installed into the app at `.agents/skills/commerce/` so agents pick it up natively.
18
18
 
@@ -32,8 +32,8 @@ base44-commerce-template/
32
32
  │ └── commerce/
33
33
  │ ├── admin/ React admin UI (copy into your app's src/commerce/)
34
34
  │ ├── utils/ storefront helpers — API client, variants, price/totals rules
35
- │ └── storefront/ storefront React layer — headless hooks (cart, checkout,
36
- catalog, reviews, SEO); no markup or CSS ships
35
+ │ └── storefront/ storefront React layer — headless hooks (catalog, cart,
36
+ checkout, receipt); no markup, CSS or copy ships
37
37
  ├── scripts/
38
38
  │ └── install.js static installer (run from <app>/examples/commerce/scripts/)
39
39
  ├── skills/
@@ -104,7 +104,7 @@ If you build on Base44's hosted platform, use the Base44 agent/MCP to write the
104
104
 
105
105
  ## What's NOT included
106
106
 
107
- - **No storefront *design*.** The parts of a shopfront that carry a brand — the home page, the collection grid, the product card, the product page's layout, the theme — ship as nothing at all, on purpose: that is the work a build should spend its effort on. Everything under those surfaces does ship: the storefront API, the hooks, and default markup for the commodity UI (checkout, cart, totals, coupon field, reviews, order-received, and the product page's internals), all restylable and replaceable — see [`skills/commerce/install/02-storefront.md`](./skills/commerce/install/02-storefront.md) for how the two tiers fit together, [`skills/commerce/references/catalog-rendering.md`](./skills/commerce/references/catalog-rendering.md) for what each catalog call returns, and [`skills/commerce/docs/api-storefront.md`](./skills/commerce/docs/api-storefront.md) for the raw API.
107
+ - **No storefront *design*.** The parts of a shopfront that carry a brand — the home page, the collection grid, the product card, the product page's layout, the theme — ship as nothing at all, on purpose: that is the work a build should spend its effort on. Everything under those surfaces does ship: the storefront API and the hooks that own the logic of every surface (catalog, product, cart, checkout, order-received, reviews) the markup, styling and copy on top of them stay yours, including the commodity screens — see [`skills/commerce/install/02-storefront.md`](./skills/commerce/install/02-storefront.md) for how the two tiers fit together, [`skills/commerce/references/catalog-rendering.md`](./skills/commerce/references/catalog-rendering.md) for what each catalog call returns, and [`skills/commerce/docs/api-storefront.md`](./skills/commerce/docs/api-storefront.md) for the raw API.
108
108
  - **No payment provider — and cards are off by default.** The order side of card payments is premade (see above), but charging a card needs a provider, so `commerce/seed-store` enables the manual **`offline`** method (bank transfer, cash on delivery, pickup — no code, no credentials) and leaves the **`card`** gateway **switched off**. **Enable cards only if a provider is wired, or is about to be** — implement the four functions in `base44/shared/commerce/card-payment.ts`, or for Stripe copy the shipped `card-payment.stripe.ts` over it and use it as-is (`skills/commerce/references/online-payments.md`), then enable the gateway via the seed's `payment_methods: ["offline", "card"]`; enabled with nothing behind it, checkout answers `503 no_card_payment_provider`. The rule and why it belongs at the end of a build rather than its start: `skills/commerce/install/03-data.md`.
109
109
  - **No scheduled workflows shipped.** Base44 *does* have a scheduler, but this template ships no workflow files — time-based jobs (stock-hold release, cart expiry, webhook-log pruning) run **opportunistically** where possible, and for the rest you (or the Base44 agent) create scheduled workflows that call `commerce/admin-tools`/`commerce/admin-orders` actions — see [`skills/commerce/references/operations.md`](./skills/commerce/references/operations.md).
110
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/app-plugin-commerce",
3
- "version": "0.2.6",
3
+ "version": "0.3.1",
4
4
  "description": "Base44 Commerce plugin — entities, backend functions, shared commerce engine, admin UI and the commerce skill, shipped as copyable source",
5
5
  "keywords": [
6
6
  "base44",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "base44",
24
- "scripts",
24
+ "scripts/install.js",
25
25
  "skills",
26
26
  "src",
27
27
  "README.md",
@@ -142,6 +142,21 @@
142
142
  [".agents", "skills", "commerce", "references", "limits-and-performance.md"], // → references/operations.md
143
143
  [".agents", "skills", "commerce", "references", "webhooks.md"], // → references/operations.md
144
144
  [".agents", "skills", "commerce", "references", "media-and-downloads.md"], // → docs/api-storefront.md + install/03-data.md
145
+ // The storefront layer went back to a small surface: these hooks are gone,
146
+ // their guards live in @/commerce/utils, and the rest is a doc recipe in
147
+ // install/02-storefront.md. They must not linger — each one carries
148
+ // kit-authored customer copy ("Add to bag", blocker messages) in its JSDoc,
149
+ // which is exactly what stores must write themselves. `index.js` stops
150
+ // exporting them either way, so a page that imported one from the barrel
151
+ // needs updating whether or not the file is here.
152
+ ["src", "commerce", "storefront", "useAddressForm.js"], // → addressFieldSpec + a recipe
153
+ ["src", "commerce", "storefront", "usePlaceOrder.js"], // → useCheckout's own gate
154
+ ["src", "commerce", "storefront", "useProductPrice.js"], // → productPrice
155
+ ["src", "commerce", "storefront", "useProductGallery.js"], // → productImages + your index state
156
+ ["src", "commerce", "storefront", "useProductReviews.js"], // → client methods + references/reviews.md
157
+ ["src", "commerce", "storefront", "useStorefrontSeo.js"], // → your own head handling
158
+ ["src", "commerce", "storefront", "useTotalsLines.js"], // → cartTotalsLines / orderTotalsLines
159
+ ["src", "commerce", "storefront", "useUpsell.js"], // → p.upsells + addItem
145
160
  ];
146
161
  let retiredRemoved = 0;
147
162
  for (const parts of RETIRED) {
@@ -47,19 +47,26 @@ moments. Read nothing else up front.
47
47
 
48
48
  **The UI is yours, all of it** — every page, element, class and word of copy.
49
49
  The kit deliberately ships **no markup and no CSS anywhere**; a brief like
50
- "make it feel like <site>" is your work, done as you would with no kit.
50
+ "make it feel like <site>" is your work, done as you would with no kit. Encode
51
+ that identity **once** — design classes in `index.css`, spent across every
52
+ page — not as decoration re-typed on every element;
53
+ [`install/02-storefront.md`](./install/02-storefront.md) opens with the
54
+ method.
51
55
 
52
56
  **The logic ships as headless hooks** (`@/commerce/storefront`) — checkout
53
- repricing, variant resolution, cart state, coupons, reviews, order-return
54
- verification, cart-drawer mechanics. Each returns a complete view-model
55
- (`status` to branch on, ready-to-map arrays, handlers) **plus ready-to-spread
56
- prop sets** (`inputProps`, `radioProps`, `buttonProps`, `panelProps`) that
57
- carry the wiring, so your markup is elements and classes. **Never re-implement
58
- what a hook does** — that is where storefront bugs cluster.
57
+ repricing, variant resolution, cart state, coupon redemption, order-return
58
+ verification, cart-drawer state. Each returns **plain states and handlers**
59
+ (`status` to branch on, arrays to map, callbacks) you write every element and
60
+ attribute from them. **Never re-implement what a hook does** — that is where
61
+ storefront bugs cluster.
62
+
63
+ It ships **no copy**, either: where a state needs words you get the state
64
+ (`buy.state`, `hint.code`, `blockers`) and write the words in the store's voice.
65
+ A kit sentence rendered into every store is how storefronts end up alike.
59
66
 
60
67
  ```jsx
61
68
  import { useCart, CartLine } from "@/commerce/storefront";
62
- const { status, lines } = useCart(); // branch on status, map lines into YOUR rows
69
+ const { status, cart } = useCart(); // branch on status, map cart.items into YOUR rows
63
70
  ```
64
71
 
65
72
  The admin UI (`src/commerce/admin/`) is finished and **also yours to change** —
@@ -80,6 +87,10 @@ hand back (API-level statements for non-React clients:
80
87
  4. **Never advertise what isn't configured** — no free-shipping banner without
81
88
  a real rate, no coupon codes without a field to redeem them in.
82
89
 
90
+ A fifth that breaks nothing but flattens the catalog, and is skipped most often:
91
+ **ribbons belong in both the grid and the product page**, each linking to its
92
+ filtered listing. They are the merchant's own merchandising.
93
+
83
94
  All backend functions return the envelope `{ success, data }`; with the SDK the
84
95
  payload is `res.data.data`:
85
96
 
@@ -95,26 +106,33 @@ Open a file when its work starts — not while planning.
95
106
  | Topic | Open when | Size |
96
107
  |---|---|---|
97
108
  | [`install/01-install.md`](./install/01-install.md) | installing — routes you to 02 and 03 | 6K |
98
- | [`install/02-storefront.md`](./install/02-storefront.md) | building storefront pages | 27K |
109
+ | [`install/02-storefront.md`](./install/02-storefront.md) | building storefront pages | 33K |
99
110
  | [`install/03-data.md`](./install/03-data.md) | seeding catalog/shipping, payments decision | 11K |
100
111
  | [`docs/entities.md`](./docs/entities.md) | any direct entity read/write ("which entity holds X") | 11K |
101
112
  | [`references/catalog-rendering.md`](./references/catalog-rendering.md) | which fields each catalog call returns, variant edge cases | 13K |
102
113
  | [`references/shipping-and-tax.md`](./references/shipping-and-tax.md) | zones beyond 03's recipe, taxes, day-2 edits | 8K |
103
114
  | [`references/online-payments.md`](./references/online-payments.md) | the store opted into cards and you are wiring the provider **now** | 8K |
104
115
  | [`references/storefront-verification.md`](./references/storefront-verification.md) | driving the storefront from a browser script | 3K |
105
- | [`references/reviews.md`](./references/reviews.md) | moderation, or a policy beyond the `policy` prop | 4K |
116
+ | [`references/reviews.md`](./references/reviews.md) | review policies (login-gated, verified buyers), moderation | 5K |
106
117
  | [`references/store-settings.md`](./references/store-settings.md) | changing store behavior through settings keys | 5K |
107
118
  | [`references/emails.md`](./references/emails.md) | order-email recipients, subjects, the log | 5K |
108
119
  | [`references/admin-product-form.md`](./references/admin-product-form.md) | editing the shipped product editor | 6K |
109
120
  | [`references/store-admin-agent.md`](./references/store-admin-agent.md) | changing the StoreAdmin copilot | 3K |
110
121
  | [`references/guest-access-security.md`](./references/guest-access-security.md) | **adding your own function or entity**; RLS/identity questions | 5K |
111
122
  | [`references/operations.md`](./references/operations.md) | scheduled maintenance, scaling limits, outbound webhooks | 7K |
112
- | [`docs/api-storefront.md`](./docs/api-storefront.md) | filters, customer accounts, refunds, a non-React client | 32K |
123
+ | [`docs/api-storefront.md`](./docs/api-storefront.md) | filters, customer accounts, refunds, a non-React client | 31K |
113
124
  | [`docs/api-admin.md`](./docs/api-admin.md) | changing admin pages, automating admin functions, the full seed contract | 24K |
114
125
 
115
126
  **The read budget.** Content you pull into context is re-read on every later
116
127
  call, so a file opened while planning costs many times what it costs opened at
117
128
  the moment it is used. Open one reference when its task starts, take what you
118
129
  need, and when a stage's checklist passes, record its carry-forward lines and
119
- treat the file as gone. If a *rule* appears in code (a hook's JSDoc), the code
120
- wins over any doc.
130
+ treat the file as gone.
131
+
132
+ **The install stages are self-contained.** Every request and response shape they
133
+ build against is written out in them — `install/02-storefront.md` carries a table
134
+ of exactly what each storefront hook resolves to. You should not need to open a
135
+ file under `src/commerce/` while building, and doing it mid-build is the most
136
+ expensive way to answer a question the stage already answers. (The source is
137
+ still the final authority if a doc and the code ever disagree — report that
138
+ rather than working around it.)
@@ -155,7 +155,7 @@ Not action-routed — the body **is** the payload. Requires admin. This is the *
155
155
 
156
156
  | Key | Type | Effect |
157
157
  |---|---|---|
158
- | `store_name` | string | **Required on a first seed** (the `emails` group doesn't exist yet) — otherwise **`400 store_name_required`**. Lands in `emails.store_name`: both the name in email subjects and the sender name, so a nameless store renders `[]: New order #1002`. On an already-seeded store it **fills a blank** name and never overwrites one the merchant chose. |
158
+ | `store_name` | string | **Required on a first seed** (the `emails` group doesn't exist yet) — otherwise **`400 store_name_required`**. Lands in `emails.store_name`: both the name in email subjects and the sender name, so a nameless store sends `New order #1002` — substitution drops the empty brackets and collapses the gap, never `[]: New order #1002` ([`../references/emails.md`](../references/emails.md)) — it just never names the shop. On an already-seeded store it **fills a blank** name and never overwrites one the merchant chose. |
159
159
  | `currency` | ISO code | Sets `general.currency`, validated against the shared currencies table. Prices are formatted with `Intl.NumberFormat` — there are no format settings. |
160
160
  | `weight_unit` | `kg`\|`g`\|`lbs`\|`oz` | Sets `general.weight_unit`. |
161
161
  | `dimension_unit` | `cm`\|`m`\|`mm`\|`in`\|`yd` | Sets `general.dimension_unit`. |
@@ -2,7 +2,7 @@
2
2
 
3
3
  The raw HTTP/SDK surface behind a customer-facing shopfront. **A React storefront should not call most of it directly** — `src/commerce/storefront/` ships the logic layer, and this file is the reference for what lies beyond it, for non-React and headless clients, and for the exact payloads and error codes.
4
4
 
5
- **Logic is premade, UI never is.** In a React app, every surface here has a headless hook in `@/commerce/storefront` `useProductList`, `useProduct` (+ `variantAxes`, `useAddToCartButton`, `useProductSpecs`, `useUpsell`), `useCart`/`useCartLine`/`useCoupon`/`useCartUI` (+ `CartUIProvider`), `useCheckout`/`usePlaceOrder` (+ `useAddressForm`, the pickers and prop-getters), `useOrderReturn`, `useProductReviews`, `useStorefrontSeo`. The hooks own the API calls and the branching below and hand you a view-model; **all markup and styling are yours** — nothing in the kit renders UI. Never hand-roll a hook's logic. Framework-free helpers (API client, variant resolution, price and totals rules, free-shipping rules) live in `src/commerce/utils/`.
5
+ **Logic is premade, UI never is.** In a React app every surface here — listing, product page, cart, checkout, the receipt, reviews — has a headless hook in `@/commerce/storefront` ([`../install/02-storefront.md`](../install/02-storefront.md) is the surface, one line per export). The hooks own the API calls and the branching below and hand you a view-model; **all markup, styling and copy are yours** — nothing in the kit renders UI or ships a word of customer-facing text. Never hand-roll a hook's logic. Framework-free helpers (API client, variant resolution, price and totals rules, free-shipping rules) live in `src/commerce/utils/`.
6
6
 
7
7
  Entities and direct CRUD → [`entities.md`](./entities.md) · admin surface → [`api-admin.md`](./api-admin.md)
8
8
 
@@ -50,7 +50,7 @@ Bootstrap data for a storefront. No payload.
50
50
  ```
51
51
  `settings` is a safe projection — display/behavior keys only, never admin config.
52
52
 
53
- `payment_gateways` is **every gateway the admin has enabled** (sorted by `order`) and this call is its **only** source — `cart.payment_gateways` does not exist, and a hardcoded list is a broken checkout. A **default-seeded store reports `offline` alone** (cards are off by default — [`../install/03-data.md`](../install/03-data.md)), so expect the one-method case. `online: true` marks the card option (`place-order` answers with a payment page; `503 no_card_payment_provider` if it was enabled with no provider behind it); everything else settles manually. In React: `useStoreInfo()` + `PaymentMethodPicker`.
53
+ `payment_gateways` is **every gateway the admin has enabled** (sorted by `order`) and this call is its **only** source — `cart.payment_gateways` does not exist, and a hardcoded list is a broken checkout. A **default-seeded store reports `offline` alone** (cards are off by default — [`../install/03-data.md`](../install/03-data.md)), so expect the one-method case. `online: true` marks the card option (`place-order` answers with a payment page; `503 no_card_payment_provider` if it was enabled with no provider behind it); everything else settles manually. In React the list arrives with the cached store info from `@/commerce/storefront`, which also ships the picker for it.
54
54
 
55
55
  ### `list-products`
56
56
  **Payload** (all optional): `search`, `category_id` (includes descendants), `ribbon_id`, `attribute_id` + `attribute_term`, `min_price`, `max_price`, `featured` (bool), `on_sale` (bool), `in_stock_only` (bool), `sort` (`-created_date`|`name`|`price`|`-price`|`popularity`|`rating`, default `-created_date`), `page` (default 1), `per_page` (default 12, max 100).
@@ -73,7 +73,7 @@ await cat({ on_sale: true, in_stock_only: true, per_page: 4 }); // deals
73
73
  await cat({ category_id, sort: "popularity", per_page: 4 }); // top in category
74
74
  ```
75
75
 
76
- `featured` is the admin's per-product flag (`commerce/seed-store` accepts `featured: true`), so a featured strip is store data, not slugs pinned in the UI. **A filter may legitimately match nothing** (nothing starred, nothing discounted) — drive the block off the returned array's length, hiding it or falling back to another slice, rather than assuming rows came back; `has_next` says whether a "see all" has more behind it. In React this is `useProductList`.
76
+ `featured` is the admin's per-product flag (`commerce/seed-store` accepts `featured: true`), so a featured strip is store data, not slugs pinned in the UI. **A filter may legitimately match nothing** (nothing starred, nothing discounted) — drive the block off the returned array's length, hiding it or falling back to another slice, rather than assuming rows came back; `has_next` says whether a "see all" has more behind it. In React this call is one hook in `@/commerce/storefront`, paging and filters included.
77
77
 
78
78
  ### `get-product`
79
79
  **Payload:** `{ id }` **or** `{ slug }`; optional `reviews_page` (1), `reviews_per_page` (10, max 50).
@@ -94,7 +94,7 @@ await cat({ category_id, sort: "popularity", per_page: 4 }); // top in catego
94
94
  ```
95
95
  **Errors:** `404 not_found` (missing / not published / hidden).
96
96
 
97
- > **`variations[]` is not a list of choices to show** — variant rule 1 applies, and variant prices come from `variations[]`, never `product.price` (a rolled-up from-price). React: `useProduct` + `useAddToCartButton`. The non-React resolver sample (`resolveSelection` from `@/commerce/utils`) and the variant deep-dive: [`../references/catalog-rendering.md`](../references/catalog-rendering.md).
97
+ > **`variations[]` is not a list of choices to show** — variant rule 1 applies, and variant prices come from `variations[]`, never `product.price` (a rolled-up from-price). In React the product page's hooks in `@/commerce/storefront` resolve this for you. The non-React resolver sample (`resolveSelection` from `@/commerce/utils`) and the variant deep-dive: [`../references/catalog-rendering.md`](../references/catalog-rendering.md).
98
98
 
99
99
  ### `list-categories`
100
100
  No payload. Returns a nested tree: `{ "categories": [ { ...category, "children": [...] } ] }` sorted by `menu_order` then name.
@@ -106,7 +106,7 @@ Returns `{ "ribbons": [ { id, name, count } ] }` sorted by name. By default it o
106
106
 
107
107
  `count` is tallied from the products this API would actually list (published, honoring `inventory.hide_out_of_stock`) — **not** from `ProductRibbon.count`, which also counts drafts and drifts until `admin-tools` `recount-terms` runs. So `Gift (12)` and the ribbon's listing agree.
108
108
 
109
- This is the **only** way a storefront can enumerate ribbons (the entity is admin-only) and what makes `list-products` `ribbon_id` usable, since that filter needs an id. Ribbons are flat, cross-cutting labels; categories are the hierarchical spine — show both ([`../references/catalog-rendering.md`](../references/catalog-rendering.md)). In React: `useRibbons` / `useCategories`.
109
+ This is the **only** way a storefront can enumerate ribbons (the entity is admin-only) and what makes `list-products` `ribbon_id` usable, since that filter needs an id. Ribbons are flat, cross-cutting labels; categories are the hierarchical spine — show both ([`../references/catalog-rendering.md`](../references/catalog-rendering.md)). In React both taxonomies are cached hooks in `@/commerce/storefront`.
110
110
 
111
111
  ### `list-attributes`
112
112
  No payload. Returns `{ "attributes": [ { ...attribute, "terms": [ ...values ] } ] }` — each attribute (`id, name, code, order`) with its values (`id, attribute_id, name, order, count`), both sorted by `order`; for filter UIs. Filter with `list-products` `attribute_id` (id or attribute **name**) + `attribute_term` (the value name); `code` is the stable key for a URL.
@@ -114,9 +114,9 @@ No payload. Returns `{ "attributes": [ { ...attribute, "terms": [ ...values ] }
114
114
  ### `submit-review`
115
115
  **Payload:** `{ product_id, email?, reviewer?, review, rating? }` — `email` is required for guests (`400 email_required`); a signed-in caller's session email always wins.
116
116
 
117
- > A React storefront gets the whole reviews surface this call, the paginated list from `get-product`, refresh, and the store's moderation policy from **`useProductReviews`**; don't hand-roll it. Read on for the raw contract.
117
+ > A React storefront reaches this call as `submitReview` (and the paginated list as `getProductReviews`) on the client from `@/commerce/storefront`'s `useStorefront()`, with the list itself already riding along on the product; policies and moderation are [`../references/reviews.md`](../references/reviews.md). Read on for the raw contract.
118
118
 
119
- **Public by default: anyone can review with an email address — no login.** A guest passes `email`; for a signed-in caller the session email always wins (the payload cannot impersonate). `reviewer` is the display name only, defaulting to the account's `full_name` then the email's local part. `rating` is optional (0–5). `verified` is derived from the email's order history. Status is `hold` unless `products.auto_approve_reviews` — the one server-side switch, so a hardcoded "awaiting approval" message is wrong when it is on. Stricter policies (login-gated, verified buyers only, rating required) are a `policy` option on the hook, not code you write: [`../references/reviews.md`](../references/reviews.md).
119
+ **Public by default: anyone can review with an email address — no login.** A guest passes `email`; for a signed-in caller the session email always wins (the payload cannot impersonate). `reviewer` is the display name only, defaulting to the account's `full_name` then the email's local part. `rating` is optional (0–5). `verified` is derived from the email's order history. Status is `hold` unless `products.auto_approve_reviews` — the one server-side switch, so a hardcoded "awaiting approval" message is wrong when it is on. Stricter policies (login-gated, verified buyers only, rating required) are the storefront's own gate around this call — the server accepts any valid email: [`../references/reviews.md`](../references/reviews.md).
120
120
 
121
121
  **Response:** `{ "review_id", "status": "hold"|"approved", "verified": true }`
122
122
  **Errors:** `404 not_found`, `400 email_required|review_incomplete|invalid_rating`.
@@ -153,7 +153,7 @@ Token-scoped cart (guest + member). Every action **except `create`** takes `cart
153
153
  "expires_at": "2025-..."
154
154
  }
155
155
  ```
156
- Coupons that stop validating are **auto-removed** and reported in `coupon_notices`; items whose product vanished or was unpublished appear in `removed_items`. In React `useCart` exposes this view as `lines` + `notices`.
156
+ Coupons that stop validating are **auto-removed** and reported in `coupon_notices`; items whose product vanished or was unpublished appear in `removed_items`. In React the cart hook in `@/commerce/storefront` publishes this exact view you render `items` and both notice arrays yourself.
157
157
 
158
158
  ### Actions
159
159
  | Action | Payload | Notes / errors |
@@ -176,7 +176,7 @@ Common: `400 cart_token_required`, `404 cart_not_found`, `404 cart_expired` —
176
176
 
177
177
  The token is the cart's bearer credential (treat it as a secret) and the **only** cart state a client keeps. Tokens expire (48h rolling TTL) and carts are consumed at checkout, so one cached in `localStorage` can go stale. `add-item` heals that by starting a fresh cart — but only if the client **reads `cart_token` from every cart response and re-persists it** instead of assuming the stored one survived. Do that after every cart call, and treat `404 cart_not_found|cart_expired` from the other actions as "clear the cached token and start over", not as an error to show.
178
178
 
179
- **In React none of this is yours:** `StorefrontProvider` owns the token and `useCart` exposes the priced view — never read, store or send the token yourself.
179
+ **In React none of this is yours:** the provider in `@/commerce/storefront` owns the token and publishes the priced view — never read, store or send the token yourself.
180
180
 
181
181
  ### Shipping is not optional — read this before building checkout
182
182
  <a id="shipping-is-not-optional--read-this-before-building-checkout"></a>
@@ -198,7 +198,7 @@ Rates re-resolve on every cart change (a stale choice is dropped, re-auto-select
198
198
 
199
199
  `place-order` errors: none chosen of several → `400 shipping_method_required`; `chosen_shipping_method` no longer offered → `400 invalid_shipping_method` (never silently swapped); nothing offered → `400 no_shipping_available`; an unsupported address already failed `set-shipping-address` with `400 shipping_not_available`. Each error body carries `available_shipping_methods`, no order is created, and a created order's `shipping_total` matches what was shown.
200
200
 
201
- **In React this is `useCheckout`**: address state debounces into `set-shipping-address`, `ShippingMethodPicker` renders the options, and `canPlaceOrder` blocks with a named blocker until a method resolves — the whole table above is the hook's behavior, not code you write. Location/zone/tax configuration: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md).
201
+ **In React this is the checkout hook in `@/commerce/storefront`**: address state debounces into `set-shipping-address`, a render-prop picker renders the options, and a `canPlaceOrder` gate blocks with a named blocker until a method resolves — the whole table above is the hook's behavior, not code you write. Location/zone/tax configuration: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md).
202
202
 
203
203
  ### Never advertise what the store isn't configured to do
204
204
 
@@ -255,7 +255,7 @@ Separate function, same guest-bearer rule: `order_id` + `order_key` (an admin ma
255
255
  | `complete-return` | `{ order_id, order_key, payment?, return_url? }` | `{ state: "paid"\|"cancelled"\|"unpaid", paid, already_confirmed, status, order, payment_link, payment_instructions }` — the whole return flow in one call: confirms with the provider, progresses the order, adds a fresh `payment_link: { url, reference }` while a card order is unpaid, and re-supplies `payment_instructions: { type, description, account_details }` for unpaid **manual** orders so bank details render on every visit. `payment` is only a hint — a hand-edited `?payment=success` can never yield `paid`. **`order` carries flat totals** (`order.total`, `order.shipping_total`); there is no `order.totals` — that shape belongs to the cart view |
256
256
  | `verify` | `{ order_id, order_key }` | `{ paid, already_confirmed, status, order }` — the same confirmation without the render-ready extras. **Idempotent**; money is asked of the provider about the reference stored on the order |
257
257
 
258
- > **The `/order-received` route is mandatory** (requirement 4): without it a paying customer lands on a 404 *and* the order is never marked paid. It only has to call `complete-return` and render its three states — `useOrderReturn` does exactly that (your markup renders the states, including `paymentInstructions`).
258
+ > **The `/order-received` route is mandatory** (requirement 4): without it a paying customer lands on a 404 *and* the order is never marked paid. It only has to call `complete-return` and render its three states — the receipt hook in `@/commerce/storefront` does exactly that (your markup renders the states, including `paymentInstructions`).
259
259
 
260
260
  Provider callbacks land on `commerce/payment-webhook` (server-to-server) — the second confirmation path, for buyers who pay and close the tab; whichever path runs second is a no-op. That function is premade (it verifies through the provider's API, so no signing secret). Wiring a provider: [`../references/online-payments.md`](../references/online-payments.md).
261
261
 
@@ -291,7 +291,7 @@ Two access modes: **auth** (Base44 session) or **`order_key` bearer** (guest tra
291
291
 
292
292
  ## Walkthrough — guest checkout
293
293
 
294
- Raw-call spine for a non-React client (a React app gets this from `useCart` + `useCheckout`).
294
+ Raw-call spine for a non-React client (a React app gets all of it from `@/commerce/storefront`).
295
295
 
296
296
  ```js
297
297
  const inv = (fn, payload) => base44.functions.invoke(fn, payload).then(r => r.data.data);
@@ -23,8 +23,11 @@ carry_forward:
23
23
  Image generation is the slowest step and nothing depends on it until seed time; the storefront doesn't wait on live data either.
24
24
 
25
25
  1. **Start image generation first** — every product image, before anything else.
26
- 2. **Mount the admin (below) and build the storefront** while images render.
27
- 3. **Seed the moment the image URLs are back** one `commerce/seed-store` call ([`./03-data.md`](./03-data.md)).
26
+ 2. **Mount the admin (below) and build the storefront** while images render. Every
27
+ request and response shape the pages build against is written out in
28
+ [`./02-storefront.md`](./02-storefront.md), so they are written from the docs,
29
+ not from live data.
30
+ 3. **Seed the moment the image URLs are back** — one `commerce/seed-store` call ([`./03-data.md`](./03-data.md)). Its writes run in parallel, so the call usually takes a few seconds — but don't idle on it either.
28
31
  4. **Converge**: open the finished pages against the live catalog.
29
32
  5. **Payments last, if at all** — cards are off by default; [`./03-data.md`](./03-data.md) decides it.
30
33