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