@base44/app-plugin-commerce 0.1.19 → 0.2.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 +25 -22
- package/base44/functions/commerce/admin-reports/entry.ts +1 -1
- package/base44/functions/commerce/seed-store/entry.ts +34 -0
- package/base44/functions/commerce/seed-store/seed-catalog.ts +39 -5
- package/base44/shared/commerce/card-payment.stripe.ts +178 -0
- package/base44/shared/commerce/scan.ts +1 -1
- package/base44/shared/commerce/sequence.ts +1 -1
- package/package.json +1 -1
- package/scripts/install.js +24 -14
- package/skills/commerce/SKILL.md +107 -51
- package/skills/commerce/docs/api-admin.md +89 -28
- package/skills/commerce/docs/api-storefront.md +113 -126
- package/skills/commerce/docs/entities.md +137 -0
- package/skills/commerce/install/01-install.md +101 -0
- package/skills/commerce/install/02-storefront.md +188 -0
- package/skills/commerce/install/03-data.md +162 -0
- package/skills/commerce/references/admin-product-form.md +10 -0
- package/skills/commerce/references/catalog-rendering.md +110 -0
- package/skills/commerce/references/emails.md +49 -12
- package/skills/commerce/references/guest-access-security.md +18 -5
- package/skills/commerce/references/online-payments.md +50 -149
- package/skills/commerce/references/operations.md +52 -0
- package/skills/commerce/references/reviews.md +31 -16
- package/skills/commerce/references/shipping-and-tax.md +110 -0
- package/skills/commerce/references/store-admin-agent.md +21 -0
- package/skills/commerce/references/store-settings.md +49 -0
- package/src/commerce/admin/README.md +2 -2
- package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +1 -1
- package/src/commerce/storefront/StorefrontProvider.jsx +106 -20
- package/src/commerce/storefront/blocks/AddToCartBlock.jsx +86 -0
- package/src/commerce/storefront/blocks/AddressFieldsBlock.jsx +96 -0
- package/src/commerce/storefront/blocks/BreadcrumbsBlock.jsx +52 -0
- package/src/commerce/storefront/blocks/CartLinesBlock.jsx +98 -0
- package/src/commerce/storefront/blocks/CheckoutBlock.jsx +247 -0
- package/src/commerce/storefront/blocks/CouponFieldBlock.jsx +84 -0
- package/src/commerce/storefront/blocks/OrderReceivedBlock.jsx +129 -0
- package/src/commerce/storefront/blocks/ProductGalleryBlock.jsx +66 -0
- package/src/commerce/storefront/blocks/ProductSpecsBlock.jsx +33 -0
- package/src/commerce/storefront/blocks/ProductStripBlock.jsx +55 -0
- package/src/commerce/storefront/blocks/QuantityStepper.jsx +62 -0
- package/src/commerce/storefront/blocks/ReviewsBlock.jsx +191 -0
- package/src/commerce/storefront/blocks/TotalsBlock.jsx +42 -0
- package/src/commerce/storefront/blocks/VariantSelectorBlock.jsx +81 -0
- package/src/commerce/storefront/blocks/index.js +44 -0
- package/src/commerce/storefront/index.js +59 -21
- package/src/commerce/storefront/internal/useAsyncData.js +86 -0
- package/src/commerce/storefront/pickers.jsx +20 -5
- package/src/commerce/storefront/useAddressForm.js +96 -0
- package/src/commerce/storefront/useCartLine.js +184 -0
- package/src/commerce/storefront/useCheckout.jsx +38 -11
- package/src/commerce/storefront/useProduct.js +227 -0
- package/src/commerce/storefront/useProductGallery.js +74 -0
- package/src/commerce/storefront/useProductList.js +153 -0
- package/src/commerce/storefront/useProductPrice.js +58 -0
- package/src/commerce/storefront/useProductReviews.js +242 -0
- package/src/commerce/storefront/useStorefrontSeo.js +204 -0
- package/src/commerce/storefront/useTotalsLines.js +109 -0
- package/src/commerce/utils/address-spec.js +89 -0
- package/src/commerce/utils/images.js +45 -0
- package/src/commerce/utils/index.js +18 -6
- package/src/commerce/utils/price.js +95 -0
- package/src/commerce/utils/storefront.js +47 -3
- package/src/commerce/utils/totals.js +110 -0
- package/src/commerce/utils/variants.js +10 -2
- package/skills/commerce/installation-guidelines.md +0 -93
- package/skills/commerce/post-installation.md +0 -495
- package/skills/commerce/references/limits-and-performance.md +0 -16
- package/skills/commerce/references/media-and-downloads.md +0 -4
- package/skills/commerce/references/product-render.md +0 -89
- package/skills/commerce/references/scheduled-work.md +0 -19
- package/skills/commerce/references/storefront-product-page.md +0 -83
- package/skills/commerce/references/webhooks.md +0 -10
package/skills/commerce/SKILL.md
CHANGED
|
@@ -1,66 +1,122 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: commerce
|
|
3
|
-
description: Base44 Commerce
|
|
3
|
+
description: Base44 Commerce — commerce.* entities, commerce/* backend functions (storefront + admin APIs, payments, webhooks), the shared engine under base44/shared/commerce/, the Store Management UI at /store-admin, and the storefront hooks + UI blocks under src/commerce/. Read before working on store features: installing, building a storefront, seeding a catalog, shipping zones, payments, or the admin.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Commerce
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
8
|
+
**This file is the map. It tells you what to read, when, and what you can skip.**
|
|
9
|
+
Nothing else in this skill needs to be open right now.
|
|
10
|
+
|
|
11
|
+
The kit ships, already written and tested: 20 `commerce.*` entities, the
|
|
12
|
+
`commerce/*` backend functions (storefront API, admin API, payments, webhooks,
|
|
13
|
+
emails), the shared commerce engine, a complete admin back office at
|
|
14
|
+
`/store-admin`, the `commerce/StoreAdmin` copilot, and the storefront's logic
|
|
15
|
+
and commodity UI (`@/commerce/storefront`, `@/commerce/utils`). None of that
|
|
16
|
+
costs you generation — your work is the *store*, not the plumbing.
|
|
17
|
+
|
|
18
|
+
## Installing right now?
|
|
19
|
+
|
|
20
|
+
Read **[`install/01-install.md`](./install/01-install.md)** and follow it. It
|
|
21
|
+
routes you to `install/02-storefront.md` when you start the UI and
|
|
22
|
+
`install/03-data.md` when you seed the catalog — in that order, at those
|
|
23
|
+
moments. Read nothing else up front: those three files are the whole install,
|
|
24
|
+
and each one says when a reference is genuinely needed.
|
|
25
|
+
|
|
26
|
+
## Four things to hold from the start
|
|
27
|
+
|
|
28
|
+
- **Entity names are dotted; SDK access is bracket syntax only** —
|
|
29
|
+
`base44.entities["commerce.Product"]`. `commerce__Product` and `Product` do
|
|
30
|
+
not exist. The full data-model map is [`docs/entities.md`](./docs/entities.md);
|
|
31
|
+
never scan `base44/entities/` to find a name.
|
|
32
|
+
- **Store configuration is declared in one seed call**, not assembled by editing
|
|
33
|
+
records: catalog, shipping zones ("€20 in Europe, €100 worldwide" is a
|
|
34
|
+
six-line `locations` payload), currency, payment methods. See
|
|
35
|
+
[`install/03-data.md`](./install/03-data.md).
|
|
36
|
+
- **Don't weaken the admin gating.** Three layers — the UI guard, entity RLS,
|
|
37
|
+
and `requireAdmin()` in every admin function — see
|
|
38
|
+
[`install/01-install.md`](./install/01-install.md). Keep all three when you
|
|
39
|
+
touch routes or schemas.
|
|
40
|
+
- **Online card payments are optional and off by default.** A seeded store takes
|
|
41
|
+
manual (`offline`) payment and works end to end. Cards are a late, deliberate
|
|
42
|
+
step, and the decision plus the timing live in
|
|
43
|
+
[`install/03-data.md`](./install/03-data.md).
|
|
44
|
+
|
|
45
|
+
## The storefront: two tiers
|
|
46
|
+
|
|
47
|
+
**Identity surfaces are yours to design** — the home page, the collection grid,
|
|
48
|
+
the product page's layout, the product card, the theme. That is where a brief
|
|
49
|
+
like "make it feel like <site>" lives, and the kit deliberately ships no markup
|
|
50
|
+
for it.
|
|
51
|
+
|
|
52
|
+
**Commodity surfaces ship as working blocks** that inherit your theme — checkout,
|
|
53
|
+
cart lines, totals, the coupon field, reviews, order-received, and the product
|
|
54
|
+
page's internals (variant selector, gallery, specs, breadcrumbs, product
|
|
55
|
+
strips). Every store's version of these is functionally identical. Restyle them
|
|
56
|
+
with your CSS, swap one region with `slots`, or drop to the hooks each block is
|
|
57
|
+
built from when the design needs different structure — but never hand-roll their
|
|
58
|
+
logic.
|
|
59
|
+
|
|
60
|
+
```jsx
|
|
61
|
+
import { CheckoutBlock } from "@/commerce/storefront";
|
|
62
|
+
// the whole checkout page body:
|
|
63
|
+
<main className="mx-auto max-w-3xl px-6 py-16"><CheckoutBlock /></main>
|
|
64
|
+
```
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
The admin UI (`src/commerce/admin/`) is finished and **also yours to change** —
|
|
67
|
+
restyle it, add pages, rework flows. To understand the backend it talks to, read
|
|
68
|
+
[`docs/api-admin.md`](./docs/api-admin.md).
|
|
38
69
|
|
|
39
|
-
|
|
70
|
+
Four rules the API enforces — a storefront that skips them cannot complete a
|
|
71
|
+
purchase. In a React app the blocks and hooks above already implement all four;
|
|
72
|
+
the API-level statements are in
|
|
73
|
+
[`docs/api-storefront.md`](./docs/api-storefront.md) for non-React clients:
|
|
40
74
|
|
|
41
|
-
|
|
75
|
+
1. A product with variants needs **one selector per attribute**, resolved to a
|
|
76
|
+
`variation_id` (`<VariantSelectorBlock/>` / `useProduct`).
|
|
77
|
+
2. Checkout must **recalculate shipping from the address and send a choice**
|
|
78
|
+
(`<CheckoutBlock/>` / `useCheckout`).
|
|
79
|
+
3. **`/order-received` must exist** and render the return state, including a
|
|
80
|
+
manual order's payment instructions (`<OrderReceivedBlock/>`).
|
|
81
|
+
4. **Never advertise what isn't configured** — no free-shipping banner without a
|
|
82
|
+
real rate, no coupon codes without a field to redeem them in.
|
|
42
83
|
|
|
43
|
-
All functions return the envelope `{ success, data }
|
|
84
|
+
All backend functions return the envelope `{ success, data }`; with the SDK the
|
|
85
|
+
payload is `res.data.data`:
|
|
44
86
|
|
|
45
87
|
```js
|
|
46
88
|
const res = await base44.functions.invoke("commerce/storefront-catalog", { action: "list-products", per_page: 12 });
|
|
47
|
-
const { products, has_next } = res.data.data;
|
|
89
|
+
const { products, has_next } = res.data.data;
|
|
48
90
|
```
|
|
49
91
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
Open
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
92
|
+
## Where to look for what
|
|
93
|
+
|
|
94
|
+
Open a file when its work starts — not while planning. The last column is there
|
|
95
|
+
so you can answer "would that file help?" without paying for it.
|
|
96
|
+
|
|
97
|
+
| Topic | Open when | Already covered without opening | Size |
|
|
98
|
+
|---|---|---|---|
|
|
99
|
+
| [`install/01-install.md`](./install/01-install.md) | installing — it routes you to 02 and 03 | — | 8K |
|
|
100
|
+
| [`install/02-storefront.md`](./install/02-storefront.md) | building storefront pages | — | 14K |
|
|
101
|
+
| [`install/03-data.md`](./install/03-data.md) | seeding the catalog, shipping, payments decision | — | 14K |
|
|
102
|
+
| [`docs/entities.md`](./docs/entities.md) | any direct entity read/write, or "which entity holds X" | function-mediated flows never need it; the addressing rule is above | 11K |
|
|
103
|
+
| [`references/catalog-rendering.md`](./references/catalog-rendering.md) | which fields each catalog call returns, variant edge cases | the install's product list/page chunks already render correct cards, prices and selectors | 12K |
|
|
104
|
+
| [`references/shipping-and-tax.md`](./references/shipping-and-tax.md) | zones beyond the standard recipe, taxes, editing locations later | "€X in a region, €Y worldwide" is inline in `install/03-data.md` | 8K |
|
|
105
|
+
| [`references/online-payments.md`](./references/online-payments.md) | the store opted into cards and you are wiring the provider **now** | the decision and its timing are in `install/03-data.md`; wiring Stripe is a one-file copy, not code to write | 9K |
|
|
106
|
+
| [`references/reviews.md`](./references/reviews.md) | moderation, or a policy beyond the `policy` prop | `<ReviewsBlock/>` covers list + form + policies | 4K |
|
|
107
|
+
| [`references/store-settings.md`](./references/store-settings.md) | changing store behavior through settings keys | the seeded defaults are right for a new store | 5K |
|
|
108
|
+
| [`references/emails.md`](./references/emails.md) | order-email recipients, subjects, per-type overrides, the log | transactional emails already send | 5K |
|
|
109
|
+
| [`references/admin-product-form.md`](./references/admin-product-form.md) | editing the shipped product editor | the editor works as shipped | 4K |
|
|
110
|
+
| [`references/store-admin-agent.md`](./references/store-admin-agent.md) | changing the StoreAdmin copilot or its tools | it registers and works on install | 3K |
|
|
111
|
+
| [`references/guest-access-security.md`](./references/guest-access-security.md) | **adding your own function or entity**, or questions about RLS/identity | the shipped functions already handle guest access | 5K |
|
|
112
|
+
| [`references/operations.md`](./references/operations.md) | scheduled maintenance, scaling limits, outbound webhooks | nothing here is needed to ship a store | 5K |
|
|
113
|
+
| [`docs/api-storefront.md`](./docs/api-storefront.md) | filters, customer accounts, refunds, a non-React client — anything past the happy path | the entire buy path | 40K |
|
|
114
|
+
| [`docs/api-admin.md`](./docs/api-admin.md) | changing admin pages, automating against admin functions, the full seed contract | — | 28K |
|
|
115
|
+
|
|
116
|
+
**The read budget.** Content you pull into context is re-read on every later
|
|
117
|
+
call, so a file opened while planning costs many times what the same file costs
|
|
118
|
+
opened at the moment it is used. Open one reference when its task starts, take
|
|
119
|
+
what you need, and when a stage's checklist passes, record that stage's
|
|
120
|
+
carry-forward lines and treat the file as gone. If a reference and this map
|
|
121
|
+
disagree, the reference wins — but if a *rule* appears in code (a hook's return
|
|
122
|
+
value, a block's markup), the code wins over both.
|
|
@@ -2,27 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
For building store automation or an alternative admin UI against the Base44 Commerce Template. The bundled admin UI (`src/commerce/admin/`) uses exactly this surface.
|
|
4
4
|
|
|
5
|
-
## Data
|
|
6
|
-
|
|
7
|
-
Two
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|---|---|---|---|
|
|
11
|
-
| commerce.Product, commerce.ProductVariation | direct (`filter`/`get`/`list`) | **`commerce/admin-products`** | derived pricing/stock, taxonomy counts, webhooks |
|
|
12
|
-
| commerce.Order, commerce.OrderNote | direct | **`commerce/admin-orders`** | lifecycle side effects (stock, emails, webhooks, dates) |
|
|
13
|
-
| commerce.OrderRefund | direct | **`commerce/admin-refunds`** | restock, totals, refund status transition |
|
|
14
|
-
| commerce.Coupon | direct | **`commerce/admin-coupons`** | code normalization/uniqueness, webhooks |
|
|
15
|
-
| commerce.Customer | direct | **`commerce/admin-customers`** | email uniqueness, invite/link, stats |
|
|
16
|
-
| commerce.ProductReview | direct | **`commerce/admin-reviews`** | rating recalculation |
|
|
17
|
-
| commerce.ProductCategory, commerce.ProductRibbon | direct | **direct CRUD**, or `commerce/admin-products` `save-term`/`delete-term`/`list-terms` (the API/agent path) | category slug uniqueness; ribbon get-or-create by name |
|
|
18
|
-
| commerce.ProductAttribute, commerce.ProductAttributeTerm | direct | **direct CRUD**, or `commerce/admin-products` `save-term`/`delete-term`/`list-terms` (the API/agent path) | attribute `code` uniqueness; value rename rewrites products; attribute delete cascades its values |
|
|
19
|
-
| commerce.ShippingTaxLocation | direct | **direct CRUD** | config; consumed by the pricing engine at read time |
|
|
20
|
-
| commerce.PaymentGateway | direct | **`commerce/seed-store`** `payment_methods` to switch methods on/off; **direct CRUD** to change a record (add or rename a manual option, its `description`, bank accounts, `order`) | one seed call converges every gateway row to the given set, so "enable cards", "card-only" and "offline-only" are one idempotent call; the record's own fields are ordinary config — the admin's Settings → Payments screen edits them directly |
|
|
21
|
-
| commerce.StoreSettings | direct | **direct CRUD** (one record per `group_id`) | grouped config |
|
|
22
|
-
| commerce.Webhook | direct | **direct CRUD** (+ `commerce/admin-webhooks` for test/redeliver) | definition is data; dispatch is engine |
|
|
23
|
-
| commerce.WebhookDelivery, commerce.EmailLog | direct (read-only logs) | written by the engine | audit logs |
|
|
24
|
-
|
|
25
|
-
**Every entity is admin-only on read *and* write** — including the catalog. Storefront access goes exclusively through the `commerce/storefront-*` functions, which run with the service role and project only what is safe; a direct entity read or write from a non-admin is rejected by the backend regardless of the UI. See [`references/guest-access-security.md`](../references/guest-access-security.md).
|
|
5
|
+
## Data access
|
|
6
|
+
|
|
7
|
+
Two styles. **Reads are direct** entity SDK calls. **Mutations with side effects go through `commerce/admin-*` functions**; plain config entities are direct CRUD. Which entity is which, its key fields, its derived fields and the exact write path is the catalog table in [**`entities.md`**](./entities.md) — including the addressing rule (`base44.entities["commerce.<Name>"]`, bracket syntax only) that a guessed spelling gets wrong.
|
|
8
|
+
|
|
9
|
+
**Every entity is admin-only on read *and* write**, catalog included. Storefront access goes exclusively through the `commerce/storefront-*` functions, which run with the service role and project only what is safe; a direct entity read or write from a non-admin is rejected by the backend regardless of the UI. See [`../references/guest-access-security.md`](../references/guest-access-security.md).
|
|
26
10
|
|
|
27
11
|
## Invocation & envelope
|
|
28
12
|
|
|
@@ -45,7 +29,7 @@ Success: `{ success: true, data }`. Failure: `{ success: false, error, code }` w
|
|
|
45
29
|
Actions: `save` · `delete` · `batch` · `duplicate` · `set-stock` · `search` · `save-term` · `delete-term` · `list-terms`
|
|
46
30
|
|
|
47
31
|
- **`save`** — `{ product, variations? }`. Upserts the product (create if no `id`); when `variations` is provided, diffs them (create/update/delete-missing).
|
|
48
|
-
> **Selling variants takes both of these in *this* call**: the attribute listed **on the product** as `attributes: [{ attribute_id, name, position, options: [...] }]`, plus a `variations` array with one entry per stocked combination (each `{ attributes: [{ attribute_id, name, option }], sku, regular_price, manage_stock: "yes", stock_quantity, status: "publish" }`) and `default_attributes` for the pre-selected combination. There is **no `type` field**: carrying attributes is what makes a product sell variants, so a `commerce.ProductAttribute` record on its own changes nothing — and a product listing an attribute with no variations cannot be added to a cart at all (`400 variation_required`). Descriptive properties belong in `meta_data`, not `attributes`. **Don't set the parent's price** —
|
|
32
|
+
> **Selling variants takes both of these in *this* call**: the attribute listed **on the product** as `attributes: [{ attribute_id, name, position, options: [...] }]`, plus a `variations` array with one entry per stocked combination (each `{ attributes: [{ attribute_id, name, option }], sku, regular_price, manage_stock: "yes", stock_quantity, status: "publish" }`) and `default_attributes` for the pre-selected combination. There is **no `type` field**: carrying attributes is what makes a product sell variants, so a `commerce.ProductAttribute` record on its own changes nothing — and a product listing an attribute with no variations cannot be added to a cart at all (`400 variation_required`). Descriptive properties belong in `meta_data`, not `attributes`. **Don't set the parent's price** — it is rolled up from the cheapest publishable variant on every save ([derived fields](./entities.md#derived-fields--never-write-these)). Enforces SKU + slug uniqueness across products *and* variations (auto-suffixes slug on collision; `duplicate_sku` on SKU clash); derives `price`/`on_sale` from the sale window, `stock_status` when stock is managed, and category/ribbon `count`; fires `product.created`/`product.updated`. → `{ product, variations }`.
|
|
49
33
|
- **`delete`** — `{ id }`. Cascades variations, decrements counts, fires `product.deleted`.
|
|
50
34
|
- **`batch`** — `{ create?: [], update?: [], delete?: [] }` (≤100 total) → per-item results.
|
|
51
35
|
- **`duplicate`** — `{ id }` → new draft copy (name "(Copy)", suffixed SKU, reset sales/ratings) incl. variations.
|
|
@@ -142,7 +126,7 @@ Actions: `test` · `redeliver` (webhook definitions themselves are direct `comme
|
|
|
142
126
|
|
|
143
127
|
## commerce/admin-reports
|
|
144
128
|
|
|
145
|
-
All actions scan orders on demand (counted = `date_paid` set, or status `processing`/`completed`).
|
|
129
|
+
All actions scan orders on demand (counted = `date_paid` set, or status `processing`/`completed`). Scaling: [`../references/operations.md`](../references/operations.md).
|
|
146
130
|
|
|
147
131
|
| Action | Payload | Returns |
|
|
148
132
|
|---|---|---|
|
|
@@ -175,16 +159,93 @@ Actions: `status` · `admin-email-recipients` · `recount-terms` · `recount-cou
|
|
|
175
159
|
|
|
176
160
|
Actions: `create-link` · `complete-return` · `verify` — the admin side of online payments (the same function serves customers, who authorize with `order_key` instead; see [`api-storefront.md`](./api-storefront.md)).
|
|
177
161
|
|
|
178
|
-
- **`create-link`** — `{ order_id }` → `{ url, reference }`: a provider-hosted payment page for an unpaid order
|
|
162
|
+
- **`create-link`** — `{ order_id }` → `{ url, reference }`: a provider-hosted payment page for an unpaid order — the **payment link** to send a customer. Accepts an order on **any** payment method (including none, as admin-created orders start) and switches it onto the card gateway, logging the change: refunds key off `payment_method`, so this keeps the order honest about how it was paid. `409 already_paid`, `400 card_payments_disabled`, `503 no_card_payment_provider`.
|
|
179
163
|
- **`verify`** — `{ order_id }` → `{ paid, already_confirmed, status, order }`: re-asks the provider and moves the order to `processing` when the money is there. Idempotent.
|
|
180
|
-
- **`complete-return`** —
|
|
164
|
+
- **`complete-return`** — what the storefront's mandatory `/order-received` page calls; full contract in [`api-storefront.md`](./api-storefront.md#commercepayments--online-payment-for-an-order).
|
|
181
165
|
|
|
182
166
|
`commerce/payment-webhook` is the provider's server-to-server callback (raw body, no `action` envelope). The function is **premade** — it validates every event through `card-payment.ts`'s `parseWebhook` and never trusts an event body on its own: an unverified event only names an order, and payment is verified through `checkCardPaymentPaid` against the provider's API, so no signing secret is needed. Until `card-payment.ts` is implemented it answers `400 webhook_not_implemented`; events it can't tie to an order are acknowledged with `200 { ignored: true }` so providers don't retry. See [`../references/online-payments.md`](../references/online-payments.md).
|
|
183
167
|
|
|
184
168
|
## commerce/seed-store
|
|
185
169
|
|
|
186
|
-
Not action-routed
|
|
170
|
+
Not action-routed — the body **is** the payload. Requires admin. This is the **canonical seed contract**; the worked example lives in [`../install/03-data.md`](../install/03-data.md).
|
|
171
|
+
|
|
172
|
+
**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.
|
|
173
|
+
|
|
174
|
+
### Payload — store setup
|
|
175
|
+
|
|
176
|
+
| Key | Type | Effect |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| `store_name` | string | **Required on a first seed** (the `emails` group doesn't exist yet) — otherwise **`400 store_name_required`**. Lands in `emails.store_name`: both the name in email subjects and the sender name, so a nameless store renders `[]: New order #1002`. On an already-seeded store it **fills a blank** name and never overwrites one the merchant chose. |
|
|
179
|
+
| `currency` | ISO code | Sets `general.currency`, validated against the shared currencies table. Prices are formatted with `Intl.NumberFormat` — there are no format settings. |
|
|
180
|
+
| `weight_unit` | `kg`\|`g`\|`lbs`\|`oz` | Sets `general.weight_unit`. |
|
|
181
|
+
| `dimension_unit` | `cm`\|`m`\|`mm`\|`in`\|`yd` | Sets `general.dimension_unit`. |
|
|
182
|
+
| `payment_methods` | non-empty array of gateway slugs, e.g. `["card"]` | Enables exactly these and **disables every other gateway row**, so "card-only"/"offline-only"/"both" is one idempotent call, no entity editing. Unknown slugs fail `400 invalid_payload` listing the known ones. |
|
|
183
|
+
| `with_sample_data` | boolean | The template's demo catalog, **only when the store has zero products**. Cannot combine with `products`/`coupons`/`locations`. |
|
|
184
|
+
| `products` `coupons` `locations` | arrays | The catalog payload — below. |
|
|
185
|
+
|
|
186
|
+
`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.
|
|
187
|
+
|
|
188
|
+
**Omit `payment_methods` and the seed enables `offline`, leaving `card` disabled** — cards are off by default and only belong on with a provider wired, since an enabled card option with nothing behind it answers `503 no_card_payment_provider` at checkout. The decision: [`../install/03-data.md`](../install/03-data.md).
|
|
189
|
+
|
|
190
|
+
### Payload — `products` / `coupons`
|
|
191
|
+
|
|
192
|
+
The one-call catalog bootstrap. Entries reference categories/ribbons/attributes **by display name** — taxonomy is get-or-created (slugs/codes derived, existing records matched case-insensitively and reused, their casing canonicalized into the product), so no id juggling.
|
|
193
|
+
|
|
194
|
+
| Key | Shape | Notes |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `categories` `ribbons` | `["Dresses"]` | display names; created if new, reused if not |
|
|
197
|
+
| `attributes` | `[{ name, options }]` or `{ <name>: [options] }` | declares the variant axes |
|
|
198
|
+
| `variations` | `[{ options: { <name>: <option> }, ...overrides }]` | the stocked combinations. **Omit it to auto-generate all** combinations, each inheriting the product-level price/sale fields |
|
|
199
|
+
| `default_options` | `{ <name>: <option> }` | the pre-selected combination |
|
|
200
|
+
| everything else | `commerce.Product` fields | `name` (required), `slug`, `sku`, `status`, `regular_price`, `sale_price`, `images`, `featured`, `virtual`, `downloadable`, `downloads`, `tax_status`, `tax_group`, `stock_quantity`, `weight`, `dimensions`, `meta_data` |
|
|
201
|
+
|
|
202
|
+
A variation with its own `stock_quantity` gets `manage_stock: "yes"`; without one it draws on the parent's pooled stock (`"parent"`), and its SKU is synthesized from the parent's when absent. Parent price and `stock_status` are derived by the same helpers as `save` ([derived fields](./entities.md#derived-fields--never-write-these)), but **no `product.created` webhooks fire** — bootstrap precedes subscribers, so use `admin-products` for webhook-visible creates. **Re-runs converge**: a product whose `sku` (or, with none, derived slug) already exists is skipped and reported, so retries never duplicate; an explicit variation SKU already in use is **`409 duplicate_sku`**. `coupons` is a thin passthrough (code lowercased, skip-if-exists).
|
|
187
203
|
|
|
188
|
-
|
|
204
|
+
**Limits:** ≤100 products, ≤50 variations per product, ≤500 variations per call, ≤50 coupons, ≤50 locations.
|
|
205
|
+
|
|
206
|
+
### Payload — `locations`
|
|
207
|
+
|
|
208
|
+
Seeding `locations` is **the** way to set shipping up. Each entry becomes a `commerce.ShippingTaxLocation`, skip-if-exists **by name**.
|
|
209
|
+
|
|
210
|
+
| Key | Shape | Notes |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `name` | string, required | the skip-if-exists match key |
|
|
213
|
+
| `countries` | `["IL", "DE"]` | ISO country codes → `{ type: "country" }` regions |
|
|
214
|
+
| `continents` | `["EU"]` | one of `AF` `AN` `AS` `EU` `NA` `OC` `SA`, sparing you a 51-code country list. `EU` is the **continent** Europe, not the European Union. An unknown code fails listing all seven |
|
|
215
|
+
| `regions` | `[{ type, code }]` | raw escape hatch (`country`\|`state`\|`continent`; state codes are `US:CA`) |
|
|
216
|
+
| `rest_of_world` | `true` | the **catch-all** every unmatched address falls to. It has no regions, so combining it with `countries`/`continents`/`regions` is a contradiction and fails validation |
|
|
217
|
+
| `order` | integer | match priority, **ascending**. Defaults to the **payload position**, so the array reads as the priority; an explicit value wins |
|
|
218
|
+
| `shipping_rates` | `[{ id?, name, cost, free_over? }]` | one checkout choice each. `id` is minted from the location + rate names when omitted, and must stay stable (carts and orders reference the chosen rate by it). `free_over` = discounted items subtotal at which it becomes free. Negative `cost` fails |
|
|
219
|
+
| `tax_groups` | `[{ name, rates: [{ name, rate }] }]` | a `Products` group is prepended when missing (products pick one by name via `tax_group`). A `rate` outside 0–100 fails |
|
|
220
|
+
| `shipping_tax` | `{ type: "percent"\|"fixed", value }` | tax on the shipping line |
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
locations: [
|
|
224
|
+
{ name: "Europe", continents: ["EU"], shipping_rates: [{ name: "Standard", cost: 20 }] },
|
|
225
|
+
{ name: "Worldwide", rest_of_world: true, shipping_rates: [{ name: "International", cost: 100 }] },
|
|
226
|
+
]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**A payload carrying `locations` suppresses the seeded `Rest of the world` fallback** (free standard delivery, `order: 999`) — yours become the store's only shipping data, since a second no-region location beside them would be a fallback nobody asked for: shadowed today, live the moment theirs is reordered or deleted. The cost: **locations with no catch-all ship only to the listed regions**, every other address getting `shipping_not_available` at checkout. Plausibly intended, so not an error — it returns as `warnings: ["no_catchall_location: …"]`. Full model: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md).
|
|
230
|
+
|
|
231
|
+
### Response
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
{
|
|
235
|
+
seeded: { settings_groups, gateways, locations }, // counts created by this call
|
|
236
|
+
sample_data: {…} | false, // same shape as `catalog`
|
|
237
|
+
catalog: { // null when no catalog keys were passed
|
|
238
|
+
categories, ribbons, attributes, terms, // each { created, reused }
|
|
239
|
+
products_created, products_skipped, variations_created,
|
|
240
|
+
coupons: { created, skipped }, locations: { created, skipped },
|
|
241
|
+
products: [ { name, id, slug, sku, variation_count }
|
|
242
|
+
| { name, skipped: true, reason: "sku_exists"|"slug_exists", existing_id } ],
|
|
243
|
+
} | null,
|
|
244
|
+
store_name: { value, action: "created"|"filled"|"unchanged"|"kept_existing" },
|
|
245
|
+
currency: { value, action: "created"|"updated"|"unchanged" } | null,
|
|
246
|
+
payment_methods: { enabled: [slug], disabled: [slug] } | null, // null when not passed
|
|
247
|
+
warnings: [ "no_catchall_location: …" ], // ALWAYS present; empty = nothing to flag
|
|
248
|
+
}
|
|
249
|
+
```
|
|
189
250
|
|
|
190
|
-
|
|
251
|
+
Record `products[].slug` — those are the URLs your storefront routes to — and read `warnings` unconditionally.
|