@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 +8 -8
- package/package.json +1 -1
- package/scripts/install.js +16 -11
- package/skills/commerce/SKILL.md +20 -21
- package/skills/commerce/docs/api-admin.md +3 -3
- package/skills/commerce/docs/api-storefront.md +2 -2
- package/skills/commerce/docs/entities.md +2 -2
- package/skills/commerce/{install/02-storefront.md → installation/install.md} +248 -6
- package/skills/commerce/references/catalog-rendering.md +2 -2
- package/skills/commerce/references/guest-access-security.md +1 -1
- package/skills/commerce/references/online-payments.md +2 -2
- package/skills/commerce/references/shipping-and-tax.md +3 -3
- package/skills/commerce/references/store-settings.md +1 -1
- package/src/commerce/admin/README.md +54 -6
- package/src/commerce/admin/context/ThemeContext.jsx +62 -0
- package/src/commerce/admin/i18n/locales/de.js +5 -0
- package/src/commerce/admin/i18n/locales/en.js +5 -0
- package/src/commerce/admin/i18n/locales/es.js +5 -0
- package/src/commerce/admin/i18n/locales/fr.js +5 -0
- package/src/commerce/admin/i18n/locales/ja.js +5 -0
- package/src/commerce/admin/i18n/locales/pt.js +5 -0
- package/src/commerce/admin/index.jsx +15 -8
- package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
- package/src/commerce/admin/layout/ThemeToggle.jsx +35 -0
- package/src/commerce/admin/layout/Topbar.jsx +3 -1
- package/src/commerce/admin/lib/theme.js +212 -0
- package/skills/commerce/install/01-install.md +0 -96
- package/skills/commerce/install/03-data.md +0 -144
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
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.
|
|
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",
|
package/scripts/install.js
CHANGED
|
@@ -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/
|
|
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
|
|
135
|
-
//
|
|
136
|
-
// whatever is in .agents/skills/ — so every retired doc goes, by name.
|
|
137
|
-
[".agents", "skills", "commerce", "post-installation.md"], // → install
|
|
138
|
-
|
|
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
|
|
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
|
|
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/
|
|
215
|
-
"It is the whole install
|
|
216
|
-
"
|
|
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" +
|
package/skills/commerce/SKILL.md
CHANGED
|
@@ -22,12 +22,13 @@ the file that mentioned them.
|
|
|
22
22
|
|
|
23
23
|
## Installing right now?
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
so
|
|
30
|
-
a model round trip per file. Read nothing
|
|
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
|
|
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
|
-
([`
|
|
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
|
|
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
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
| [`
|
|
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 |
|
|
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
|
|
151
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3
|
-
read_when: "
|
|
4
|
-
skip_when: "
|
|
5
|
-
forget_when: "
|
|
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 —
|
|
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
|
-
|
|
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.
|