@base44/app-plugin-commerce 0.8.4 → 0.9.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.
package/README.md CHANGED
@@ -8,9 +8,9 @@ It provides a full-featured **commerce data model and behavior** (variant-driven
8
8
 
9
9
  - **20 entities** — Products (a product sells variants when it carries attributes; no type field), variations, categories, ribbons, attributes + values, reviews, orders (embedded line/shipping/tax/fee/coupon lines), order notes, refunds, coupons, customers, Shipping & Tax Locations (shipping rates + tax groups per location), payment gateways, store settings, webhooks + deliveries, carts, download permissions, email log.
10
10
  - **16 backend functions** — 9 admin (`commerce/admin-products`, `commerce/admin-orders`, `commerce/admin-refunds`, `commerce/admin-coupons`, `commerce/admin-customers`, `commerce/admin-reviews`, `commerce/admin-webhooks`, `commerce/admin-reports`, `commerce/admin-tools`), 4 storefront (`commerce/storefront-catalog`, `commerce/storefront-cart`, `commerce/storefront-checkout`, `commerce/storefront-account`), 2 payment (`commerce/payments`, `commerce/payment-webhook`), and an idempotent `commerce/seed-store` — one call seeds the business defaults **and the whole catalog** (products with attributes in, variants/categories/taxonomy created internally).
11
- - **Payments: manual methods work out of the box; online cards are opt-in** — the seed enables the manual `offline` method (bank transfer, cash on delivery, pickup: on-hold + instructions, no code) and leaves the `card` gateway **disabled**. The order side of card payments *is* premade — checkout routing, payment links for unpaid orders, two idempotent confirmation paths (customer return + webhook) and refund records — so a store that opts in wires a provider by implementing **four functions in one file**, `base44/shared/commerce/card-payment.ts`. **For Stripe there is nothing to write**: `base44/shared/commerce/card-payment.stripe.ts` is a complete implementation used as-is — copy it over the stub and enable the gateway. Any other provider (PayPal, Adyen, a local PSP) follows the same shape. Enable the gateway only with a provider behind it, or checkout answers `503 no_card_payment_provider`. The rule and timing: [`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md); provider mechanics: [`skills/commerce/references/online-payments.md`](./skills/commerce/references/online-payments.md). The admin can add more manual methods in Settings → Payments.
11
+ - **Payments: manual methods work out of the box; online cards are opt-in** — the seed enables the manual `offline` method (bank transfer, cash on delivery, pickup: on-hold + instructions, no code) and leaves the `card` gateway **disabled**. The order side of card payments *is* premade — checkout routing, payment links for unpaid orders, two idempotent confirmation paths (customer return + webhook) and refund records — so a store that opts in wires a provider by implementing **four functions in one file**, `base44/shared/commerce/card-payment.ts`. **For Stripe there is nothing to write**: `base44/shared/commerce/card-payment.stripe.ts` is a complete implementation used as-is — copy it over the stub and enable the gateway. Any other provider (PayPal, Adyen, a local PSP) follows the same shape. Enable the gateway only with a provider behind it, or checkout answers `503 no_card_payment_provider`. The rule and timing: [`skills/commerce/installation/install.md`](./skills/commerce/installation/install.md); provider mechanics: [`skills/commerce/references/online-payments.md`](./skills/commerce/references/online-payments.md). The admin can add more manual methods in Settings → Payments.
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
- - **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. Localized — ships in English (default), German, Spanish, French, Japanese and Portuguese, switched by one line of code, with a documented recipe for adding any other language (no i18n dependency; see [`src/commerce/admin/README.md`](./src/commerce/admin/README.md) and the skill's `references/admin-localization.md`).
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. Renders in the store's own theme, with a topbar toggle to a neutral Base44-dashboard palette and font for stores whose site theme makes the back office hard to read (colors and fonts only; remembered per browser). Localized — ships in English (default), German, Spanish, French, Japanese and Portuguese, switched by one line of code, with a documented recipe for adding any other language (no i18n dependency; see [`src/commerce/admin/README.md`](./src/commerce/admin/README.md) and the skill's `references/admin-localization.md`).
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` and `ribbons.js` normalize the two catalog fields that are arrays of objects (`{src, name, alt}` images, `{id, name}` ribbons) rather than strings; `types.js` writes the catalog shapes down as JSDoc typedefs (`StorefrontProduct` and the rest), so what a field holds is answerable from the frontend; `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
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), one deliberately rendered-but-unstyled component (`AddressFields` — the checkout address form, whose state/province and `autoComplete` mechanics are where hand-rolled forms break; it ships no CSS and styles via `data-part` selectors or class props), and the framework-free view-model helpers re-exported so one import line covers a page (`variantAxes`, `productPrice`, `productImages`, `productRibbons`, `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`; …) and names its return type from `storefront/types.js`, so a page reads a field's shape off the hook instead of off a backend function. 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.
@@ -71,7 +71,7 @@ node examples/commerce/scripts/install.js
71
71
 
72
72
  From your existing Base44 app:
73
73
 
74
- 1. **Copy the files** — either copy this whole repo into your app at `examples/commerce/` and run `node examples/commerce/scripts/install.js`, or merge `entities/`, `functions/`, `shared/` into your app's `base44/` directory by hand (see [`skills/commerce/install/01-install.md`](./skills/commerce/install/01-install.md)). Confirm your `base44/config.jsonc` `entitiesDir`/`functionsDir` point at these folders.
74
+ 1. **Copy the files** — either copy this whole repo into your app at `examples/commerce/` and run `node examples/commerce/scripts/install.js`, or merge `entities/`, `functions/`, `shared/` into your app's `base44/` directory by hand (see [`skills/commerce/installation/install.md`](./skills/commerce/installation/install.md)). Confirm your `base44/config.jsonc` `entitiesDir`/`functionsDir` point at these folders.
75
75
  2. **Push the schema, functions and agent:**
76
76
  ```bash
77
77
  npx base44 entities push
@@ -98,7 +98,7 @@ From your existing Base44 app:
98
98
  ```
99
99
  Name the section they group under in `base44/ui.jsonc` (app-owned — edit in place): `{ "version": 1, "sections": [{ "path": "/store-admin/*", "name": "Store Management" }] }`
100
100
  6. **Grant yourself the `admin` role** (Base44 dashboard → users, or `users.inviteUser(email, "admin")`). The admin UI refuses non-admins.
101
- 7. **Seed the store.** Either open `/store-admin` and click **Initialize store defaults** on the first-run setup screen, or call `commerce/seed-store` directly — it creates the settings groups, the payment gateway rows (`offline` enabled, `card` off — enable it only with a provider wired) and — unless you pass your own `locations` — a fallback Shipping & Tax Location, plus the catalog: pass `products` (whole products with attributes — variants, categories, ribbons and taxonomy are created internally) or `with_sample_data: true` for the generic demo. Either way pass `store_name` (the app's name) — it is required on a first seed and becomes both the email subject prefix and the sender name. Once the `general` settings group exists the store counts as ready and the first-run screen stops appearing. Worked example: [`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md); the full payload contract: [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md). Shipping zones are part of the same call — `locations` takes `continents: ["EU"]` and `rest_of_world: true`, so "€20 in Europe, €100 worldwide" is six lines.
101
+ 7. **Seed the store.** Either open `/store-admin` and click **Initialize store defaults** on the first-run setup screen, or call `commerce/seed-store` directly — it creates the settings groups, the payment gateway rows (`offline` enabled, `card` off — enable it only with a provider wired) and — unless you pass your own `locations` — a fallback Shipping & Tax Location, plus the catalog: pass `products` (whole products with attributes — variants, categories, ribbons and taxonomy are created internally) or `with_sample_data: true` for the generic demo. Either way pass `store_name` (the app's name) — it is required on a first seed and becomes both the email subject prefix and the sender name. Once the `general` settings group exists the store counts as ready and the first-run screen stops appearing. Worked example: [`skills/commerce/installation/install.md`](./skills/commerce/installation/install.md); the full payload contract: [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md). Shipping zones are part of the same call — `locations` takes `continents: ["EU"]` and `rest_of_world: true`, so "€20 in Europe, €100 worldwide" is six lines.
102
102
 
103
103
  ## Quick start (Base44 MCP / hosted apps)
104
104
 
@@ -106,17 +106,17 @@ If you build on Base44's hosted platform, use the Base44 agent/MCP to write the
106
106
 
107
107
  1. Copy this whole repo into the target app at `examples/commerce/` (e.g. download + extract a tarball with `run_command`), then run `node examples/commerce/scripts/install.js` via `run_command` — or use `write_file` to copy every file under `base44/` and `src/commerce/` individually (use `list_directory`/`read_file` to adapt to the app's actual layout — e.g. the `@/api/base44Client` path and your router file).
108
108
  2. Wait for the app to build (`get_app_status`), then confirm entities exist (`list_entity_schemas`).
109
- 3. Grant your user the `admin` role, then seed the store's data — one `commerce/seed-store` call takes the whole catalog via `products`, or `with_sample_data: true` for the demo catalog; leave both out for defaults only, or skip the call for the admin's first-run **Initialize store defaults** screen ([`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md)).
109
+ 3. Grant your user the `admin` role, then seed the store's data — one `commerce/seed-store` call takes the whole catalog via `products`, or `with_sample_data: true` for the demo catalog; leave both out for defaults only, or skip the call for the admin's first-run **Initialize store defaults** screen ([`skills/commerce/installation/install.md`](./skills/commerce/installation/install.md)).
110
110
 
111
111
  ## What's NOT included
112
112
 
113
- - **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.
114
- - **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`.
113
+ - **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/installation/install.md`](./skills/commerce/installation/install.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.
114
+ - **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/installation/install.md`.
115
115
  - **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).
116
116
 
117
117
  ## Next steps
118
118
 
119
- - **Install into your app:** [`skills/commerce/install/01-install.md`](./skills/commerce/install/01-install.md)
119
+ - **Install into your app:** [`skills/commerce/installation/install.md`](./skills/commerce/installation/install.md)
120
120
  - **Operate & extend:** the commerce skill — [`skills/commerce/SKILL.md`](./skills/commerce/SKILL.md)
121
121
  - **Build a storefront:** [`skills/commerce/docs/api-storefront.md`](./skills/commerce/docs/api-storefront.md)
122
122
  - **Admin automation / alternative admin:** [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@base44/app-plugin-commerce",
3
- "version": "0.8.4",
3
+ "version": "0.9.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",
@@ -26,7 +26,7 @@
26
26
  * else in the app is left untouched.
27
27
  *
28
28
  * This is only the static part of the install. Everything after it lives in
29
- * ../skills/commerce/install/01-install.md (deps, the /store-admin mount, the
29
+ * ../skills/commerce/installation/install.md (deps, the /store-admin mount, the
30
30
  * admin-role gating), which routes on to 02-storefront.md and 03-data.md at the
31
31
  * right moments; ../skills/commerce/SKILL.md is the map for all later work. The
32
32
  * skill folder carries all of that documentation into the app.
@@ -131,20 +131,24 @@
131
131
  ["src", "commerce", "admin", "pages", "settings", "TaxRatesTable.jsx"],
132
132
  ["src", "commerce", "admin", "pages", "settings", "ShippingSettings.jsx"],
133
133
  ["src", "commerce", "admin", "pages", "settings", "ShippingZoneEditor.jsx"],
134
- // The skill's docs were restructured into stage files (install/01..03) plus a
135
- // smaller reference set. Stale guidance is worse than none — agents read
136
- // whatever is in .agents/skills/ — so every retired doc goes, by name.
137
- [".agents", "skills", "commerce", "post-installation.md"], // → install/01..03
138
- [".agents", "skills", "commerce", "installation-guidelines.md"], // install/01-install.md + references/store-admin-agent.md
134
+ // The skill's docs were restructured into stage files, then merged into a
135
+ // single installation/install.md. Stale guidance is worse than none — agents
136
+ // read whatever is in .agents/skills/ — so every retired doc goes, by name.
137
+ [".agents", "skills", "commerce", "post-installation.md"], // → installation/install.md
138
+ // The three stage files merged into installation/install.md, which the install
139
+ // program now returns outright. Left behind, install/ is a second and stale copy
140
+ // of the entire hot path — and the one an agent would read from disk.
141
+ [".agents", "skills", "commerce", "install"], // → installation/install.md
142
+ [".agents", "skills", "commerce", "installation-guidelines.md"], // → installation/install.md + references/store-admin-agent.md
139
143
  [".agents", "skills", "commerce", "references", "product-render.md"], // → references/catalog-rendering.md
140
144
  [".agents", "skills", "commerce", "references", "storefront-product-page.md"], // → references/catalog-rendering.md
141
145
  [".agents", "skills", "commerce", "references", "scheduled-work.md"], // → references/operations.md
142
146
  [".agents", "skills", "commerce", "references", "limits-and-performance.md"], // → references/operations.md
143
147
  [".agents", "skills", "commerce", "references", "webhooks.md"], // → references/operations.md
144
- [".agents", "skills", "commerce", "references", "media-and-downloads.md"], // → docs/api-storefront.md + install/03-data.md
148
+ [".agents", "skills", "commerce", "references", "media-and-downloads.md"], // → docs/api-storefront.md + installation/install.md
145
149
  // The storefront layer went back to a small surface: these hooks are gone,
146
150
  // 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
151
+ // installation/install.md. They must not linger — each one carries
148
152
  // kit-authored customer copy ("Add to bag", blocker messages) in its JSDoc,
149
153
  // which is exactly what stores must write themselves. `index.js` stops
150
154
  // exporting them either way, so a page that imported one from the barrel
@@ -211,9 +215,10 @@
211
215
  (retiredRemoved ? ` (${retiredRemoved} retired file${retiredRemoved === 1 ? "" : "s"} removed)` : ""),
212
216
  );
213
217
  console.log(
214
- "\nNext: read .agents/skills/commerce/install/01-install.md and follow it.\n" +
215
- "It is the whole install, and it says when to open 02-storefront.md and\n" +
216
- "03-data.md read nothing else up front.\n" +
218
+ "\nNext: read .agents/skills/commerce/installation/install.md and follow it.\n" +
219
+ "It is the whole install all three stages, in the order you work them:\n" +
220
+ "the admin mount, the storefront, then the seed payload. Read nothing else\n" +
221
+ "up front.\n" +
217
222
  "\n" +
218
223
  " 1. No deps to add: sonner, recharts and react-markdown ship with the default\n" +
219
224
  " Base44 template — check package.json and npm i only what is truly missing\n" +
@@ -22,12 +22,13 @@ the file that mentioned them.
22
22
 
23
23
  ## Installing right now?
24
24
 
25
- Read all three stages **in one turn** — [`install/01-install.md`](./install/01-install.md),
26
- [`install/02-storefront.md`](./install/02-storefront.md),
27
- [`install/03-data.md`](./install/03-data.md) then work them in that order:
28
- install, then the storefront UI, then the catalog. Every install runs all three,
29
- so fetching them one at a time defers nothing you will not read anyway and costs
30
- a model round trip per file. Read nothing *else* up front.
25
+ The whole install is one file — [`installation/install.md`](./installation/install.md).
26
+ It carries all three stages in the order you work them: install, then the
27
+ storefront UI, then the catalog. **If the install tool returned its content, it is
28
+ already in your context never read it from disk.** Every install runs all three
29
+ stages, so they ship as one file rather than three: splitting them defers nothing
30
+ you will not read anyway and costs a model round trip per file. Read nothing
31
+ *else* up front.
31
32
 
32
33
  **Admin language.** English: nothing to do. de/es/fr/ja/pt: in
33
34
  `src/commerce/admin/i18n/index.js` repoint `import active from "./locales/de.js"`
@@ -45,13 +46,13 @@ and stop — never edit admin screens. Other:
45
46
  records. It is idempotent and every key is independent, so it is the day-2
46
47
  tool as much as the install one: a later call carries only the slice it
47
48
  changes — enabling cards is `{ payment_methods: ["offline", "card"] }`, whole
48
- ([`install/03-data.md`](./install/03-data.md)).
49
+ ([`installation/install.md`](./installation/install.md)).
49
50
  - **Don't weaken the admin gating** — three layers (UI guard, entity RLS,
50
51
  `requireAdmin()`); keep all three when touching routes or schemas
51
- ([`install/01-install.md`](./install/01-install.md)).
52
+ ([`installation/install.md`](./installation/install.md)).
52
53
  - **Online card payments are optional and off by default.** A seeded store
53
54
  takes manual (`offline`) payment and works end to end; cards are a late,
54
- deliberate step ([`install/03-data.md`](./install/03-data.md)).
55
+ deliberate step ([`installation/install.md`](./installation/install.md)).
55
56
 
56
57
  ## The storefront: logic is premade, UI never is
57
58
 
@@ -60,7 +61,7 @@ The kit deliberately ships **no markup and no CSS anywhere**; a brief like
60
61
  "make it feel like <site>" is your work, done as you would with no kit. Encode
61
62
  that identity **once** — design classes in `index.css`, spent across every
62
63
  page — not as decoration re-typed on every element;
63
- [`install/02-storefront.md`](./install/02-storefront.md) opens with the
64
+ [`installation/install.md`](./installation/install.md) opens with the
64
65
  method.
65
66
 
66
67
  **The logic ships as headless hooks** (`@/commerce/storefront`) — checkout
@@ -116,19 +117,17 @@ const { products, has_next } = res.data.data;
116
117
 
117
118
  ## Where to look for what
118
119
 
119
- Open a file when its work starts — not while planning. The exception is the
120
- three `install/` stages: an install reads all of them, so they come as one
121
- batch (above).
120
+ Open a file when its work starts — not while planning. The exception is
121
+ `installation/install.md`: an install reads all three of its stages, so they ship
122
+ as one file and arrive with the installer (above).
122
123
 
123
124
  | Topic | Open when | Size |
124
125
  |---|---|---|
125
- | [`install/01-install.md`](./install/01-install.md) | installing — routes you to 02 and 03 | 8K |
126
- | [`install/02-storefront.md`](./install/02-storefront.md) | building storefront pages | 38K |
127
- | [`install/03-data.md`](./install/03-data.md) | seeding catalog, shipping rates/zones, payments; re-callable per slice | 11K |
126
+ | [`installation/install.md`](./installation/install.md) | installing — all three stages: admin mount, storefront pages, then the seed payload | 58K |
128
127
  | [`docs/entities.md`](./docs/entities.md) | any direct entity read/write ("which entity holds X") | 11K |
129
128
  | [`references/catalog-rendering.md`](./references/catalog-rendering.md) | field shapes each catalog call returns, variant edge cases | 16K |
130
- | [`references/shipping-and-tax.md`](./references/shipping-and-tax.md) | zones beyond 03's recipe, taxes, day-2 edits | 8K |
131
- | [`references/online-payments.md`](./references/online-payments.md) | enabling card payments, or wiring the provider — at install or any time later | 8K |
129
+ | [`references/shipping-and-tax.md`](./references/shipping-and-tax.md) | zones beyond stage 03's recipe, taxes, day-2 edits | 8K |
130
+ | [`references/online-payments.md`](./references/online-payments.md) | enabling card payments, or wiring the provider — at install or any time later | 10K |
132
131
  | [`references/storefront-verification.md`](./references/storefront-verification.md) | driving the storefront from a browser script | 3K |
133
132
  | [`references/reviews.md`](./references/reviews.md) | review policies (open by default; login-gated, verified buyers), moderation | 6K |
134
133
  | [`references/store-settings.md`](./references/store-settings.md) | changing store behavior through settings keys | 5K |
@@ -147,11 +146,11 @@ planning than at the moment it is used. That is the rule for `references/` and
147
146
  `docs/`: open one when its task starts, take what you need, and when a stage's
148
147
  checklist passes, record its carry-forward lines and treat the file as gone.
149
148
  Deferring is worth a round trip only when it might save the read entirely — so
150
- it is the wrong instinct for the three install stages, which every install
151
- reads. Batch those.
149
+ it is the wrong instinct for the install stages, which every install reads. That
150
+ is why they are one file, delivered by the installer rather than fetched.
152
151
 
153
152
  **The install stages are self-contained.** Every request and response shape they
154
- build against is written out in them — `install/02-storefront.md` carries a table
153
+ build against is written out in them — `installation/install.md` carries a table
155
154
  of exactly what each storefront hook resolves to. You should not need to open a
156
155
  file under `src/commerce/` while building, and doing it mid-build is the most
157
156
  expensive way to answer a question the stage already answers. (The source is
@@ -147,7 +147,7 @@ An admin authorizes with the role and passes `{ order_id }` alone where a custom
147
147
 
148
148
  ## commerce/seed-store
149
149
 
150
- Not action-routed — the body **is** the payload. Requires admin. This is the **canonical seed contract**; the worked example lives in [`../install/03-data.md`](../install/03-data.md).
150
+ Not action-routed — the body **is** the payload. Requires admin. This is the **canonical seed contract**; the worked example lives in [`../installation/install.md`](../installation/install.md).
151
151
 
152
152
  **Order of operations**, so a failure says how much was written: (1) validate — pure, so bad input is **`400 invalid_payload`** carrying every problem at once in `errors: [{ path, error }]`, before anything runs; (2) **canary schema probe** per entity the seeder writes — any incompatibility is **`422 schema_incompatible`** with `errors: [{ entity, error }]`, nothing written; (3) defaults, idempotently (settings groups, gateways, and the fallback location **only when the payload brings no `locations`**); (4) the catalog, rolling back everything this call created on a mid-write failure (**`500 catalog_seed_failed`**), reused taxonomy untouched.
153
153
 
@@ -165,7 +165,7 @@ Not action-routed — the body **is** the payload. Requires admin. This is the *
165
165
 
166
166
  `currency`, the units and `payment_methods` **always win**, first seed and re-runs alike: there is no blank state distinguishing a merchant's choice from the default, so passing one is an instruction. `store_name` is the exception (fill-blank only) — pass the app's name **as the platform shows it**: `base44/config.jsonc` → `name` is not authoritative (it can say `New App` for an app the platform calls `Canvas`) and a function's env is only `BASE44_APP_ID`, so ask the user or read the dashboard.
167
167
 
168
- **Omit `payment_methods` and the seed enables `offline`, leaving `card` disabled** — cards are off by default and only belong on with a provider wired, since an enabled card option with nothing behind it answers `503 no_card_payment_provider` at checkout. The decision: [`../install/03-data.md`](../install/03-data.md).
168
+ **Omit `payment_methods` and the seed enables `offline`, leaving `card` disabled** — cards are off by default and only belong on with a provider wired, since an enabled card option with nothing behind it answers `503 no_card_payment_provider` at checkout. The decision: [`../installation/install.md`](../installation/install.md).
169
169
 
170
170
  ### Payload — `products` / `coupons`
171
171
 
@@ -187,7 +187,7 @@ A variation with its own `stock_quantity` gets `manage_stock: "yes"`; without on
187
187
 
188
188
  Seeding `locations` is **the** way to set shipping up. Each entry becomes a `commerce.ShippingTaxLocation`, skip-if-exists **by name**: a scope (`countries: ["IL", "DE"]`, `continents: ["EU"]` — the **continent**, not the EU — raw `regions`, or `rest_of_world: true`, the catch-all, which contradicts the other scope keys), `order` (match priority ascending, defaults to payload position), `shipping_rates: [{ id?, name, cost, free_over? }]` (a minted `id` must stay stable — carts/orders reference it), `tax_groups` (a `Products` group prepended when missing) and `shipping_tax`. Unknown codes, negative `cost` or a tax `rate` outside 0–100 fail validation. Field-by-field semantics, matching and recipes: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md).
189
189
 
190
- A payload carrying `locations` suppresses the seeded catch-all fallback — without your own, unlisted addresses get `shipping_not_available`, flagged as `warnings: ["no_catchall_location: …"]` (same reference; [`../install/03-data.md`](../install/03-data.md) also owns this).
190
+ A payload carrying `locations` suppresses the seeded catch-all fallback — without your own, unlisted addresses get `shipping_not_available`, flagged as `warnings: ["no_catchall_location: …"]` (same reference; [`../installation/install.md`](../installation/install.md) also owns this).
191
191
 
192
192
  ### Response
193
193
 
@@ -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 — 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/`.
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` ([`../installation/install.md`](../installation/install.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 the list arrives with the cached store info from `@/commerce/storefront`, which also ships the picker for it.
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 — [`../installation/install.md`](../installation/install.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).
@@ -91,7 +91,7 @@ Webhook ──< WebhookDelivery EmailLog append-only
91
91
 
92
92
  ## Recipes
93
93
 
94
- **Enable/disable a payment gateway** — not an entity write. One call converges *every* gateway row to the set you name, validates the slugs, and is idempotent. Cards need a provider wired first ([`../install/03-data.md`](../install/03-data.md)):
94
+ **Enable/disable a payment gateway** — not an entity write. One call converges *every* gateway row to the set you name, validates the slugs, and is idempotent. Cards need a provider wired first ([`../installation/install.md`](../installation/install.md)):
95
95
  ```js
96
96
  await base44.functions.invoke("commerce/seed-store", { payment_methods: ["offline", "card"] });
97
97
  ```
@@ -134,4 +134,4 @@ await base44.entities["commerce.StoreSettings"].update(g.id, { values: { ...g.va
134
134
 
135
135
  - A function's actions, payloads and error codes → [`api-admin.md`](./api-admin.md) · [`api-storefront.md`](./api-storefront.md)
136
136
  - One entity's complete field list, types, enums, defaults → `base44/entities/commerce.<Name>.jsonc` (one read, never a directory scan)
137
- - Seeding catalog / locations / payment methods in one call → [`api-admin.md#commerceseed-store`](./api-admin.md#commerceseed-store); worked example in [`../install/03-data.md`](../install/03-data.md)
137
+ - Seeding catalog / locations / payment methods in one call → [`api-admin.md#commerceseed-store`](./api-admin.md#commerceseed-store); worked example in [`../installation/install.md`](../installation/install.md)
@@ -1,9 +1,15 @@
1
1
  ---
2
- stage: install/02
3
- read_when: "You are about to build storefront pages (product list/page, cart, checkout, order-received)."
4
- skip_when: "The storefront pages already render against live data and pass the checklist at the bottom of this file."
5
- forget_when: "The checklist at the bottom passes every page renders against the seeded catalog and an offline order completes."
2
+ stage: install
3
+ read_when: "The commerce kit's files were just copied into the app, or you are installing it now. This is the whole install: all three stages."
4
+ skip_when: "All three checklists below already pass the admin mounts and / routes somewhere real, the storefront renders against live data, and commerce/seed-store has returned success."
5
+ forget_when: "All three checklists below pass. Record each stage's carry-forward lines as you go."
6
6
  carry_forward:
7
+ - "The admin's six picker-visible routes are literal <Route> JSX in src/App.jsx (the platform discovers pages by reading that file — an array or a .map() discovers nothing); the rest run off its <Route path=\"*\" element={<AdminRoutes />} />."
8
+ - "Admin enforcement is three layers — AuthGuard (UI), admin-only entity RLS, requireAdmin() in every admin function. Never weaken any of them."
9
+ - "/order-received must exist as a route: every payment link returns there, and confirming is what marks an order paid."
10
+ - "The storefront header shows a visible \"Store manager\" link to /store-admin when the signed-in user's role is admin, and nothing for everyone else."
11
+ - "Interleave: start image generation first → mount admin + build the storefront → seed with the image_url the generate_image result already returned (never poll, never write a function to fetch URLs) → payments last."
12
+ - "Entities are dotted + bracket-syntax only (`base44.entities[\"commerce.X\"]`); the map is ../docs/entities.md — never scan base44/entities/."
7
13
  - "Payment gateways, currency and countries come from useStoreInfo() only — never off a cart (cart.payment_gateways is always undefined)."
8
14
  - "A store with any coupons must have a coupon field (useCart().applyCoupon) in the cart or the checkout, or its codes can never be redeemed."
9
15
  - "/order-received is mandatory and renders useOrderReturn's states, including paymentInstructions — how a normal (offline) customer learns how to pay."
@@ -13,8 +19,110 @@ carry_forward:
13
19
  - "The kit ships no copy: hooks hand you state codes (buy.state, hint.code, blockers) and you write every word — including the reason a disabled button is disabled."
14
20
  - "Identity is encoded once — design classes in index.css plus one or two signature moments per page; a utility run that appears twice becomes a class."
15
21
  - "The product page is the richest surface and budgeted for it (~8K chars): productSpecs rows carry a type — branch on it, never .map() the list into one grey label/value table."
22
+ - "Product slugs come from the seed response's catalog.products[] — link pages by slug, never by a client-side map."
23
+ - "Payments: report at handover where they landed (default = offline on, card off) — the owner must never learn it from a customer. Shipping the Stripe file is not a Stripe connection; only the user can authorise that."
24
+ - "Turning card payments on or off later is one more seed call: { payment_methods: [\"offline\", \"card\"] }."
25
+ - "Seed-time `locations` is THE shipping path; patching commerce.ShippingTaxLocation is the day-2 route."
16
26
  ---
17
27
 
28
+ # Install — the whole build
29
+
30
+ The install tool returns this file's content, so it is **already in your context**:
31
+ never read it from disk. It carries all three stages; work them in order.
32
+
33
+ | Stage | What it does |
34
+ |---|---|
35
+ | [01 — Install](#01--install) | dependencies, the `/store-admin` mount, routes, admin-role enforcement |
36
+ | [02 — Storefront](#02--storefront) | the design language, then every storefront page against the headless hooks |
37
+ | [03 — Store data](#03--store-data) | the `commerce/seed-store` payload: catalog, shipping, currency, payments |
38
+
39
+ Each stage ends with its own checklist. When one passes, record its carry-forward
40
+ lines and treat that stage as done — but keep reading this file, not re-fetching it.
41
+
42
+ # 01 — Install
43
+
44
+ **Inside the Base44 runtime, writing a resource file *is* the deploy** — the 20 `commerce.*` entities, the 16 `commerce/*` functions + shared engine, the StoreAdmin agent, the finished admin app (`src/commerce/admin/` — don't validate it, it ships tested) and the storefront hooks (`src/commerce/storefront/` + `utils/`) are live the moment the files exist. If `scripts/install.js` ran, all of that is already in place; there is no build step and nothing to push.
45
+
46
+ **Anonymous function calls must be allowed** in the app's settings — the storefront functions are public by design (they verify per action: auth session, `cart_token`, or `order_key`). If the app blocks unauthenticated invocation, every guest hits errors on the entire storefront.
47
+
48
+ **Dependencies.** `sonner`, `recharts`, `react-markdown` — all three ship with the default Base44 template, so check `package.json` and `npm i` only what is actually absent (the normal outcome is installing nothing).
49
+
50
+ ## Work order — interleave, don't queue
51
+
52
+ Image generation is the slowest step and nothing depends on it until seed time; the storefront doesn't wait on live data either.
53
+
54
+ 1. **Start image generation first** — every product image, before anything else.
55
+ ⚑ **The result already carries the real `image_url`** alongside a `placeholder_url`
56
+ — use `image_url` and move on. Nothing is pending, there is nothing to poll, and a
57
+ backend function calling `Core.GenerateImage` to "fetch the real URLs" is pure waste:
58
+ you already have them. If a `/__generating__/…` value does reach a file or a seed
59
+ payload, the platform swaps it for the real URL after the turn (in files *and* entity
60
+ records) — so a placeholder that renders broken in a mid-build preview is expected and
61
+ must not be "fixed".
62
+ 2. **Mount the admin (below) and build the storefront** while images render. Every
63
+ request and response shape the pages build against is written out in
64
+ [stage 02 below](#02--storefront), so they are written from the docs,
65
+ not from live data.
66
+ 3. **Seed the moment the image URLs are back** — one `commerce/seed-store` call ([stage 03 below](#03--store-data)). Its writes run in parallel, so the call usually takes a few seconds — but don't idle on it either.
67
+ 4. **Converge**: open the finished pages against the live catalog.
68
+ 5. **Payments last, if at all** — cards are off by default; [stage 03 below](#03--store-data) decides it.
69
+
70
+ The only dependency edges are *image URLs → seed payload* and *seed done → real products on the pages*.
71
+
72
+ ## Mount the admin
73
+
74
+ The admin mounts as a **layout route in the app's own `src/App.jsx`**: the six screens the store owner opens from the builder are declared there as literal `<Route>` JSX, and everything deeper goes to `<AdminRoutes />` on a splat. The platform discovers an app's pages by reading that file — a screen declared anywhere else is unreachable from the page picker.
75
+
76
+ ```jsx
77
+ import AdminApp, { AdminRoutes } from "@/commerce/admin";
78
+ import Dashboard from "@/commerce/admin/pages/Dashboard"; // …and orders/OrdersList,
79
+ // products/ProductsList, customers/CustomersList, coupons/CouponsList, reports/Reports
80
+
81
+ {/* Literal JSX — the platform reads this file, it never runs it. Do not refactor into a map. */}
82
+ <Route path="/store-admin" element={<AdminApp />}>
83
+ <Route index element={<Dashboard />} />
84
+ <Route path="orders" element={<OrdersList />} />
85
+ <Route path="products" element={<ProductsList />} />
86
+ <Route path="customers" element={<CustomersList />} />
87
+ <Route path="coupons" element={<CouponsList />} />
88
+ <Route path="reports" element={<Reports />} />
89
+ <Route path="*" element={<AdminRoutes />} /> {/* editors, settings, webhooks */}
90
+ </Route>
91
+ <Route path="/" element={<Navigate to="/store-admin" replace />} /> {/* until a storefront exists */}
92
+ <Route path="/order-received" element={<OrderReceived />} /> {/* mandatory — see below */}
93
+ ```
94
+
95
+ - **Those seven lines, as they are.** The splat is what keeps the app's listed pages to six instead of 26: `path="*"` is skipped, so the editors, settings tabs and webhook screens stay navigable without appearing there, and `<AdminRoutes />` still serves the admin's own 404.
96
+ - **Don't add `settings` to the list** — it is a tabbed layout around a nested route, so it only renders correctly from the splat.
97
+ - **Elsewhere than `/store-admin`**: change the layout route's path and pass the prefix — `<AdminApp basePath="/backoffice" />`; the children are unchanged.
98
+ - **Name the group** in `base44/ui.jsonc` — app-owned, so edit it in place, keep any other keys, never recreate a deleted one: `{ "version": 1, "sections": [{ "path": "/store-admin/*", "name": "Store Management" }] }`
99
+ - **Give `/` something** — a blank app has no `/` route, and "page not found" at the app's own URL reads like a broken install.
100
+ - **Link the admin from the storefront header** — otherwise the merchant has no way in but typing the URL. Resolve the signed-in user once (`base44.auth.me()`, rejection/no session = not an admin, never blocking the page) and render a plainly visible "Store manager" link to `/store-admin` in the header when `role === "admin"` — and nothing at all for everyone else.
101
+ - **`/order-received` is mandatory**, even offline-only: every payment link returns there, and confirming is what marks an order paid — without it a paying customer hits a 404 and the order stays unpaid. The page is one hook, `useOrderReturn()` ([stage 02 below](#02--storefront)). A different path must be set in Settings → General (`general.order_received_path`).
102
+
103
+ ## Admin-role enforcement — do not weaken
104
+
105
+ Three layers, all load-bearing; keep every one when touching routes or schemas:
106
+
107
+ 1. **UI guard** — the shipped `AuthGuard` (requires `role === "admin"`; grant via the dashboard or `base44.users.inviteUser(email, "admin")`).
108
+ 2. **Entity RLS** — every commerce entity is admin-only on all operations.
109
+ 3. **Function guard** — every `commerce/admin-*` function and `commerce/seed-store` calls `requireAdmin()`: **401** unauthenticated, **403** not admin.
110
+
111
+ Storefront functions are public on purpose (per-action verification, above). To check install state at any point: `commerce/admin-tools` `{ action: "status" }` ([`../docs/api-admin.md`](../docs/api-admin.md)).
112
+
113
+ ## Done — stage 01 complete
114
+
115
+ - [ ] The `/store-admin` layout route is in `src/App.jsx` with its six literal `<Route>` screens and the `path="*"` → `<AdminRoutes />` splat; `base44/ui.jsonc` names the section; the three enforcement layers untouched.
116
+ - [ ] `/` routes somewhere real; `/order-received` is a route.
117
+ - [ ] The storefront header shows a visible `/store-admin` link to signed-in admins, and to nobody else.
118
+ - [ ] Anonymous function invocation is allowed in the app's settings.
119
+ - [ ] Missing dependencies (if any) installed; image generation already running.
120
+
121
+ Then continue with [stage 02 below](#02--storefront) when you start building UI, and [stage 03 below](#03--store-data) when you start the seed payload — both are already in this file, so there is nothing to fetch. Open no `references/` or `docs/` file during an install: a file read early costs its size on every later call.
122
+
123
+ Record this file's `carry_forward` lines (front matter) in your working notes, then move on to stage 02 below.
124
+
125
+
18
126
  # 02 — Storefront
19
127
 
20
128
  One split decides everything here: **the logic is premade, the UI never is.** The hooks own checkout repricing, variant resolution, cart state, order-return verification — hand-writing any of it is where storefront bugs cluster, so **never re-implement what a hook does**. Every element, class, layout and **word** is yours; nothing in `@/commerce/storefront` renders markup or carries CSS. **Decide how the store looks as if this kit did not exist**, then encode it **once** as design classes ([below](#design-language--once-before-any-page)) — the snippets here are wiring reference, never design input.
@@ -339,7 +447,7 @@ A receipt is a convention surface: your classes, every branch present, no bespok
339
447
 
340
448
  The cart is optimistic and debounced, so a script that acts faster than it settles sees a working store as broken. Read [`../references/storefront-verification.md`](../references/storefront-verification.md) **before** writing the script — not after it fails.
341
449
 
342
- ## Done — forget this file
450
+ ## Done — stage 02 complete
343
451
 
344
452
  - [ ] Catalog UI in whatever form fits the store, plus a checkout, plus `/order-received` rendering `useOrderReturn`'s states **including `paymentInstructions`**.
345
453
  - [ ] `index.css` defines the store's design classes; pages carry class names, not repeated utility runs.
@@ -356,4 +464,138 @@ The cart is optimistic and debounced, so a script that acts faster than it settl
356
464
  - [ ] The storefront carries the design you settled on before reading this file — design classes plus one or two signature moments per page; convention surfaces carry the classes and nothing bespoke.
357
465
  - [ ] **Specs and axes render by what they are**: the product page branches on `productSpecs` `type`/`key` for the rows that carry this catalog's meaning, and the grid has a rhythm — no page ships one uniform grey label/value table or one identical chip row per axis.
358
466
 
359
- Then copy this file's `carry_forward` lines (in its front matter) into your working notes, and do not re-read this file.
467
+ Record this file's `carry_forward` lines (front matter) in your working notes, then move on to stage 03 below.
468
+
469
+
470
+ # 03 — Store data
471
+
472
+ A fresh install has **no settings and no catalog**. One admin-only, idempotent call to `commerce/seed-store` creates both: the six settings groups, the two gateway rows (`offline` enabled, `card` disabled), and — from the payload — the catalog, coupons and shipping locations. Nothing in [stage 02 below](#02--storefront) waits on it: fire it when the image URLs are back.
473
+
474
+ | Mode | Body |
475
+ |---|---|
476
+ | **Real catalog** | `{ store_name, products: [...] }` (+ optional `coupons`, `locations`) |
477
+ | **Demo data** | `{ store_name, with_sample_data: true }` — 10 generic products; cannot combine with `products` (**400**) |
478
+ | **No products** | `{ store_name }` — defaults only |
479
+
480
+ **`store_name` is required on a first seed** — the app's name as the platform shows it (`base44/config.jsonc` → `name` can be stale; ask if unsure). **`currency`** is an ISO code (`"EUR"`); formatting follows the viewer's locale, nothing else to set. Explicit values always win, first seed and re-runs alike.
481
+
482
+ The working call — `name` is the only required product key; give each product the keys its own catalog entry actually has and leave the rest out. The **full key list** (sale windows, downloads, tax, backorders, upsells…) lives in `api-admin.md` — open it only if the catalog needs one:
483
+
484
+ ```js
485
+ try {
486
+ const res = await base44.functions.invoke("commerce/seed-store", {
487
+ store_name: "Aurora Threads",
488
+ currency: "EUR",
489
+ products: [
490
+ { name: "Linen Scarf", regular_price: 45 }, // minimal is complete
491
+
492
+ { name: "Runner Sneaker",
493
+ sku: "SNK-RUN", // optional; makes re-runs idempotent
494
+ featured: true, // → useProductList({ featured: true }) rails
495
+ regular_price: 89,
496
+ sale_price: 79, // sets on_sale; storefront strikes regular_price
497
+ stock_quantity: 12, // implies manage_stock: true
498
+ sold_individually: false, // true → max 1 per order (no qty stepper)
499
+ short_description: "Cushioned everyday runner.",
500
+ description: "<p>Cut from recycled knit…</p>", // HTML, rendered as rich text
501
+ images: ["https://…/sneaker.jpg"], // URLs or { src, alt } — see Images below
502
+ categories: ["Shoes"], // get-or-created by display name
503
+ ribbons: ["Best Seller"], // flat labels, not a hierarchy
504
+
505
+ weight: 0.248, // real fields, in the store's units —
506
+ dimensions: { length: 31, width: 12, height: 11 }, // never a meta_data row
507
+
508
+ // Descriptive spec rows (productSpecs). NOT axes, NOT ribbons; `_` hides.
509
+ // Qualities only — a weight or a size here is a string nothing can read.
510
+ meta_data: [
511
+ { key: "Material", value: "Recycled knit upper" },
512
+ { key: "Care", value: "Machine wash cold" },
513
+ ],
514
+
515
+ attributes: [ // the axes → one selector each
516
+ { name: "Size", options: ["41", "42"] },
517
+ { name: "Color", options: ["Black", "White"] },
518
+ ],
519
+ default_options: { Size: "42", Color: "Black" },
520
+ variations: [ // omit entirely → all combos auto-generated
521
+ { options: { Size: "41", Color: "Black" }, stock_quantity: 4, weight: 0.242 },
522
+ { options: { Size: "42", Color: "White" }, regular_price: 94, stock_quantity: 3,
523
+ weight: 0.254, // per variant, never inherited from the parent
524
+ image: "https://…/sneaker-white.jpg" }, // per-variation image for a visual axis
525
+ ],
526
+ },
527
+ ],
528
+ coupons: [{ code: "WELCOME10", discount_type: "percent", amount: 10 }],
529
+ // ONLY with a coupon field in the cart or checkout (see stage 02 below)
530
+ // locations: [ … ], // shipping — next section; passing any makes them the store's ONLY ones
531
+ });
532
+ return res.data; // ← the { success, data } envelope: plain JSON
533
+ } catch (e) {
534
+ return { success: false, status: e.response?.status, ...(e.response?.data ?? { error: e.message }) };
535
+ }
536
+ ```
537
+
538
+ **Running this through a code-execution tool? Return `res.data`, never the raw response** — the raw response carries circular objects and fails `Converting circular structure to JSON` *even when the seed succeeded*; a thrown error needs `e.response?.data` for the same reason.
539
+
540
+ Reference taxonomy by **display name** — existing records are matched case-insensitively and reused. The seeder derives slugs, checks SKU uniqueness, prices variations, and **rolls the parent's price up from the cheapest publishable variant** — never set a variant parent's price yourself. Unknown keys are rejected, so typos surface. **Idempotency:** a product whose `sku` (or derived slug) exists is skipped and reported — safe to retry. Bad payloads fail **400** `invalid_payload` with `errors: [{ path, error }]` before anything is written; per-call limits (≤100 products, ≤500 variations, ≤50 locations) and the full key list are in [`../docs/api-admin.md`](../docs/api-admin.md#commerceseed-store).
541
+
542
+ The response reports everything; these matter downstream:
543
+
544
+ ```jsonc
545
+ { "catalog": { "products": [{ "name": "Runner Sneaker", "id": "…", "slug": "runner-sneaker", "variation_count": 2 }] },
546
+ "payment_methods": null, // null = the default (offline on, card off)
547
+ "warnings": [] } // always present; read it — see shipping
548
+ ```
549
+
550
+ Link pages by the **`slug`** from `catalog.products[]`; never mirror the seed in client-side constants (the database is the source of truth and a mirror diverges on the first merchant edit).
551
+
552
+ ## Shipping — declare it in the seed call
553
+
554
+ Each location is a scope plus its rates and taxes; locations match in payload order. "€20 in Europe, €100 everywhere else" is two locations:
555
+
556
+ ```js
557
+ locations: [
558
+ { name: "Europe", continents: ["EU"], shipping_rates: [{ name: "Standard", cost: 20 },
559
+ { name: "Express", cost: 35 }] },
560
+ { name: "Worldwide", rest_of_world: true, shipping_rates: [{ name: "International", cost: 100 }] },
561
+ ]
562
+ ```
563
+
564
+ ⚑ **Speeds go in one location's `shipping_rates`; zones are separate locations.** Exactly one location matches an address, so several rates in it become the customer's pick at checkout (`shipping_status: "choice_required"`), while a second location with the same scope is simply dead — never matched, its rates never offered.
565
+
566
+ - **`continents: ["EU"]`** spares you a 51-country list — and `EU` is the *continent* Europe, not the European Union.
567
+ - **`rest_of_world: true`** is the catch-all — there is **no country code meaning "everywhere"**, and improvised ones (`["*"]`, `["ALL"]`, alpha-3 `["USA"]`) fail **400**. The narrower scope is `countries: ["IL", "DE"]`; matching is country + state only.
568
+ - The matched location supplies the taxes too (`tax_groups`, `shipping_tax`) and `free_over` zeroes a rate above a subtotal — reference below.
569
+
570
+ **The catch-all trap.** Passing any `locations` suppresses the seeded worldwide fallback, so scoped locations with nothing behind them answer `shipping_not_available` to every other address. Plausibly intended, so it warns rather than fails: **read `warnings` on every seed** and either add a `rest_of_world` location or state the restriction to the user. If the brief named tiered rates, price a cart against one address per zone to check them (`set-shipping-address` → `available_shipping_methods`; `[]` means no location matched).
571
+
572
+ Continent codes in full, state regions, tax binding, VAT-on-shipping, day-2 edits: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md).
573
+
574
+ ## Images
575
+
576
+ **The URL you seed is the URL the store serves.** Resolve each to its final, permanent URL before seeding: the app's image generation, or `base44.integrations.Core.UploadFile({ file })` → public URL. A temporary/**signed** URL (expiry params in the query string are the tell) must be re-hosted via `UploadFile` first — the failure is silent at seed time and visible only in the store. Cheap insurance before seeding: fetch one or two of the URLs and check they answer 200 with an image content type. Seeding imageless and patching later is allowed but is open debt; close it before handover.
577
+
578
+ ## Payments — the decision
579
+
580
+ Online card payments are **off by default**: the seeded store takes the manual `offline` method and is complete and payable — the order goes on-hold and `/order-received` renders the gateway's payment instructions, no code, no credentials. **Enable `card` only if a provider is wired in the same stretch of work**: an enabled card option with nothing behind it answers **`503 no_card_payment_provider`** the moment a customer picks it.
581
+
582
+ `payment_methods` is the on/off switch — the listed slugs are enabled, every other row disabled. ⚑ **Never write `enabled` on the `commerce.PaymentGateway` row instead.** **Seeding need not happen all at once:** every key is independent, so re-call `commerce/seed-store` whenever one slice of configuration changes, carrying only that slice — `{ payment_methods: ["offline", "card"] }` is a complete standalone call, as valid on a live store weeks later as during the install (`store_name` is required only on a store's first seed).
583
+
584
+ | The request says | Decision |
585
+ |---|---|
586
+ | A provider is named ("use Stripe") | copy the provider file, enable `card` — whenever convenient |
587
+ | Selling online implied, no provider named | raise the provider **after** the store works |
588
+ | Paid another way (transfer, COD, invoice, pickup) | nothing to do — the default is exactly this |
589
+ | Payments not mentioned | leave the default, **state it at handover** |
590
+
591
+ *Cards only:* **if it is Stripe the code is already written** — copy `base44/shared/commerce/card-payment.stripe.ts` over `base44/shared/commerce/card-payment.ts` **whole** (`fs.copyFileSync`; a partial edit breaks every function's deploy), then enable via `payment_methods`. Nothing needs filling in. ⚑ **That is the code, not a connection** — the app is not connected to Stripe until the user authorises it and its secret key reaches the app's secrets, which only they can do. Say the card path is *ready* and ask them to connect Stripe; never report it as connected, and never ask for a key in chat. Any other provider: [`../references/online-payments.md`](../references/online-payments.md).
592
+
593
+ ## Done — stage 03 complete
594
+
595
+ - [ ] Seed returned success — real products, final permanent image URLs; slugs recorded and pages link by them.
596
+ - [ ] `warnings` is empty, or every warning is deliberate and stated to the user.
597
+ - [ ] Shipping expressed in `locations` (catch-all present if the store ships worldwide); named tiers asserted.
598
+ - [ ] `coupons` seeded only if a coupon field exists ([stage 02 below](#02--storefront)).
599
+ - [ ] Cards off, or on with the provider file copied whole.
600
+
601
+ Record this file's `carry_forward` lines (front matter) in your working notes. The install is finished — treat this file as spent and do not re-read it.