@base44/app-plugin-commerce 0.1.12 → 0.1.13
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44/app-plugin-commerce",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
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/skills/commerce/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ Operational guidance for extending, operating and building on the Base44 Commerc
|
|
|
14
14
|
|
|
15
15
|
## IMPORTANT — first-time installation
|
|
16
16
|
|
|
17
|
-
If the template was just installed (or you are installing it right now), read [`.agents/skills/commerce/post-installation.md`](./post-installation.md) **before anything else — and, unless the user has a special requirement, read nothing else**: it alone covers embedding the admin pages, the three-layer admin-role enforcement (do not weaken), seeding the store's data — **one `commerce/seed-store` call takes the whole catalog** (products with attributes; variants, categories, ribbons and Shipping & Tax Locations created internally — §2) — **card payments in one file** (§2.2
|
|
17
|
+
If the template was just installed (or you are installing it right now), read [`.agents/skills/commerce/post-installation.md`](./post-installation.md) **before anything else — and, unless the user has a special requirement, read nothing else**: it alone covers embedding the admin pages, the three-layer admin-role enforcement (do not weaken), seeding the store's data — **one `commerce/seed-store` call takes the whole catalog** (products with attributes; variants, categories, ribbons and Shipping & Tax Locations created internally — §2) — **card payments in one file, wired only if — and whenever in the flow — the store actually needs online payments** (§2.2 opens with that decision, then a complete Stripe implementation to paste over `shared/commerce/card-payment.ts`; the payment webhook is premade), and the **storefront quick start** (§3): logic-only chunks for product list → product page → cart → checkout. The references below and the API docs are for requests that go beyond that happy path, not for the install. The full install-from-scratch steps are in [`.agents/skills/commerce/installation-guidelines.md`](./installation-guidelines.md).
|
|
18
18
|
|
|
19
19
|
## Working on the UI
|
|
20
20
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Post-installation
|
|
2
2
|
|
|
3
|
-
What to do right after the static installation ([`installation-guidelines.md`](./installation-guidelines.md)): embed the admin pages into the app, seed the store's data,
|
|
3
|
+
What to do right after the static installation ([`installation-guidelines.md`](./installation-guidelines.md)): embed the admin pages into the app, seed the store's data, build the storefront from the quick start below, and — **if and when the store needs online payments, a call you make deliberately (§2.2)** — wire a payment provider. Installed into the app at `.agents/skills/commerce/post-installation.md`.
|
|
4
4
|
|
|
5
|
-
> **This file is intentionally the whole job.** Unless the user has a special requirement (a non-Stripe payment provider, signature-verified webhooks, filters/reviews/accounts beyond the happy path), do **not** open the `references/` guides or the `docs/` API references — everything needed to ship a working store (admin mount, seeding, product list, product page, cart, checkout, order-received, card payments) is on this page, ending with the [Done when](#4-done-when) checklist. Reading more first just burns time.
|
|
5
|
+
> **This file is intentionally the whole job.** Unless the user has a special requirement (a non-Stripe payment provider, signature-verified webhooks, filters/reviews/accounts beyond the happy path), do **not** open the `references/` guides or the `docs/` API references — everything needed to ship a working store (admin mount, seeding, product list, product page, cart, checkout, order-received, and card payments for the stores that need them) is on this page, ending with the [Done when](#4-done-when) checklist. Reading more first just burns time.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -145,6 +145,12 @@ A successful response means the data is in — the catalog and settings are live
|
|
|
145
145
|
|
|
146
146
|
The order side of payments is **already implemented** (checkout routing, confirmation, payment links, refund records). The `offline` gateway — and any option the admin adds in Settings → Payments — works with nothing to configure: the order goes on-hold with the option's description as instructions.
|
|
147
147
|
|
|
148
|
+
**Whether the store needs online payments — and when in the install to wire them — is your decision, not a fixed step.** Nothing else on this page depends on this section: the admin, the seed and the entire storefront work with no provider wired (until then the card option answers `503 no_card_payment_provider` at checkout, and §3.4 shows the graceful fallback). Decide from what the user is actually building:
|
|
149
|
+
|
|
150
|
+
- **The store doesn't take online payments** (cash on delivery, bank transfer, invoicing, pickup, quote-based…) → seed `payment_methods` without `"card"` and skip the rest of this section — the manual gateways are complete as shipped.
|
|
151
|
+
- **The store does take online payments** → this section is the how-to (Stripe below is the reference), but *you* pick the point in the flow. The wiring is self-contained and needs the user's provider secret key, so it usually fits **after** the storefront is standing — asking for a Stripe key before there is anything to sell is premature. What's non-negotiable is only the end state: don't hand over a finished store with the card gateway enabled but no provider behind it (wire one, or disable the gateway).
|
|
152
|
+
- **Unclear from the request** → build everything else first and raise payments with the user at the end, or ask.
|
|
153
|
+
|
|
148
154
|
Which methods the store offers is **seed data**: pass `payment_methods` to `commerce/seed-store` (§2) — e.g. `["card"]` for a card-only store — instead of ever editing `commerce.PaymentGateway` records yourself.
|
|
149
155
|
|
|
150
156
|
The **Credit card** option needs a payment provider — **any** provider works (Stripe, PayPal, Adyen, a local PSP…), and whichever one it is, wiring it touches exactly **one file**: `base44/shared/commerce/card-payment.ts` — four functions, each backing a premade flow. `commerce/payment-webhook` (the function) is **premade — do not edit it**: it calls this file's `parseWebhook` to validate each event and **never trusts an event body on its own** — an unverified event only *names* an order, and whether money arrived is asked of the provider itself through `checkCardPaymentPaid`, so a forged webhook call can never mark an order paid and **no signing secret is needed**. Until the file is implemented the card option answers `503 no_card_payment_provider` at checkout (or switch it off in Settings → Payments).
|
|
@@ -287,6 +293,19 @@ export const store = createStorefront(base44);
|
|
|
287
293
|
|
|
288
294
|
The client owns the two things hand-rolled storefronts keep getting wrong, so **don't reimplement either**: the **`cart_token` lifecycle** (sent with every cart/checkout call, re-persisted from every response — a stale token silently starts a fresh cart; rolling 48 h expiry; cleared when checkout consumes the cart) and the **store-info cache** (`payment_gateways`, currency, countries live **only** on `get-store-info` — the cart view never carries them). For anything beyond its methods, `store.inv(fn, payload)` unwraps the `{ success, data }` envelope (`res.data.data`).
|
|
289
295
|
|
|
296
|
+
**What each method resolves to** — the client already unwraps every envelope, so take these shapes at face value (no `.data`, and no `.categories` on a list result):
|
|
297
|
+
|
|
298
|
+
| Method | Resolves to |
|
|
299
|
+
|---|---|
|
|
300
|
+
| `store.getStoreInfo()` | `{ settings, payment_gateways, countries, currencies }` — cached; the **only** source of `payment_gateways` |
|
|
301
|
+
| `store.listProducts(params)` | `{ products, page, per_page, has_next }` — a page object; the array is `products` |
|
|
302
|
+
| `store.getProduct(slug \| { id })` | `{ product, variations, categories, ribbons, reviews }` |
|
|
303
|
+
| `store.listCategories()` | an **array** of root categories, subcategories nested under `children` |
|
|
304
|
+
| `store.listRibbons()` | an **array** of `{ id, name, count }` |
|
|
305
|
+
| `store.getCart()` and every cart mutation | the full cart view (`getCart()` alone may resolve to `null` — no cart yet); `cart.items[n].attributes` is an **array** of `{ name, option }`, never a map |
|
|
306
|
+
| `store.placeOrder(details)` | `{ order_id, order_number, order_key, status, totals, order, payment_instructions, payment }` |
|
|
307
|
+
| `store.completeReturn()` | `{ state, order, payment_link, payment_instructions }` |
|
|
308
|
+
|
|
290
309
|
Keep the UI in **small focused components** (~2–4K characters each — product card, gallery, cart panel, address step, payment step…), not monolithic page files: smaller files are faster to emit, review and fix.
|
|
291
310
|
|
|
292
311
|
### 3.1 Product list
|
|
@@ -305,7 +324,7 @@ const { products, page, per_page, has_next } = await store.listProducts({
|
|
|
305
324
|
}); // sort: -created_date | name | price | -price | popularity | rating
|
|
306
325
|
```
|
|
307
326
|
|
|
308
|
-
Each row is a full product record — for a card use `name`, `images[0]?.src` (**may be empty — render a placeholder, never a broken `<img>`**), `price`, `regular_price`, `on_sale` (sale badge), `short_description`, `stock_status`, `average_rating`/`rating_count` (stars cost no extra call) and `ribbons` (`[{ id, name }]`, may be absent — labels like "Best Seller" for the card corner). **There is no product type flag**: `product.attributes?.length > 0` means the product sells variants and its `price` is a *from*-price rolled up from the cheapest variant — render it as "From …". Categories for the nav come from `store.listCategories()` (
|
|
327
|
+
Each row is a full product record — for a card use `name`, `images[0]?.src` (**may be empty — render a placeholder, never a broken `<img>`**), `price`, `regular_price`, `on_sale` (sale badge), `short_description`, `stock_status`, `average_rating`/`rating_count` (stars cost no extra call) and `ribbons` (`[{ id, name }]`, may be absent — labels like "Best Seller" for the card corner). **There is no product type flag**: `product.attributes?.length > 0` means the product sells variants and its `price` is a *from*-price rolled up from the cheapest variant — render it as "From …". Categories for the nav come from `store.listCategories()` — an **array** of root categories with subcategories nested under `children` (map over it directly; there is no `{ categories }` wrapper on the result). That is the whole card — no other call or reference needed for the list view.
|
|
309
328
|
|
|
310
329
|
**Carry forward:** each card links to the product page by **`slug`**.
|
|
311
330
|
|
|
@@ -348,6 +367,8 @@ A product with attributes is **rejected without a `variation_id`** (`400 variati
|
|
|
348
367
|
```js
|
|
349
368
|
let cart = await store.getCart(); // null → no cart yet (an expired token self-clears)
|
|
350
369
|
// cart.items → [{ item_key, name, image, quantity, price, subtotal, total, attributes, purchasable }]
|
|
370
|
+
// item.attributes is an ARRAY of { name, option } — render "Size: 42" by
|
|
371
|
+
// mapping it (attributes.map(a => `${a.name}: ${a.option}`)); it is never a map
|
|
351
372
|
// cart.totals → { subtotal, discount_total, shipping_total, cart_tax, total_tax, total, … }
|
|
352
373
|
// cart.coupon_notices / cart.removed_items → tell the customer what auto-dropped and why
|
|
353
374
|
//
|
|
@@ -444,7 +465,7 @@ Two shapes to get right when rendering: **`order` carries flat totals** — `ord
|
|
|
444
465
|
|
|
445
466
|
## 4. Done when
|
|
446
467
|
|
|
447
|
-
Post-installation is complete when every line below holds —
|
|
468
|
+
Post-installation is complete when every line below holds — check against this list instead of re-reading docs. **Do not validate the admin part at all**: everything under `/store-admin` ships finished and already tested — there is nothing there for you to verify.
|
|
448
469
|
|
|
449
470
|
- [ ] `/store-admin/*` mounted behind the shipped `AuthGuard`; `/` routes somewhere real (storefront or a redirect).
|
|
450
471
|
- [ ] `commerce/seed-store` ran once and reported the catalog — real products, final image URLs; if the user restricted payment methods, `payment_methods` was passed in that same call.
|
|
@@ -452,8 +473,7 @@ Post-installation is complete when every line below holds — verify against thi
|
|
|
452
473
|
- [ ] Product page renders from `store.getProduct` with one selector per attribute, resolving to `view.addToCart`.
|
|
453
474
|
- [ ] The storefront talks to the API through **one `createStorefront` instance** — no hand-rolled `cart_token` handling, and `payment_gateways` read from `getStoreInfo()` only, never off a cart.
|
|
454
475
|
- [ ] `/order-received` calls `completeReturn` and renders `paid` / `unpaid` / `cancelled`.
|
|
455
|
-
- [ ]
|
|
456
|
-
- [ ] **Verified by driving a real test order end to end** — add to cart → address → shipping → `place-order` → `/order-received` renders the right state — then the test order cancelled/deleted in the admin. Placing and cleaning up a test order is routine install verification, not a destructive action. A button existing is **not** evidence the flow works; only the driven flow is.
|
|
476
|
+
- [ ] Online payments **decided, not defaulted** (§2.2): if the store takes card payments, `card-payment.ts` implemented (Stripe: paste §2.2), `STRIPE_SECRET_KEY` secret set and webhook endpoint registered — wired at whatever point in the flow you judged right; if it doesn't (or not yet), the card gateway disabled (seed `payment_methods` without `"card"`). Either way, checkout never offers a card option with no provider behind it.
|
|
457
477
|
|
|
458
478
|
## 5. Next
|
|
459
479
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* import { base44 } from "@/api/base44Client";
|
|
10
10
|
* export const store = createStorefront(base44);
|
|
11
11
|
*
|
|
12
|
-
* It owns the
|
|
12
|
+
* It owns the things hand-rolled clients keep getting wrong:
|
|
13
13
|
*
|
|
14
14
|
* - The cart_token lifecycle: the token is sent with every cart/checkout call
|
|
15
15
|
* and re-persisted from every response (a stale token silently starts a
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
* - The store-info split: payment_gateways, currency, countries/currencies
|
|
18
18
|
* live ONLY on get-store-info — the cart view never carries them.
|
|
19
19
|
* getStoreInfo() caches the call; read them from there, never off a cart.
|
|
20
|
+
* - Envelope unwrapping: every method resolves straight to the data you use.
|
|
21
|
+
* listCategories() and listRibbons() resolve to ARRAYS — the raw actions'
|
|
22
|
+
* { categories } / { ribbons } wrappers are unwrapped here, so never dig
|
|
23
|
+
* for `.categories` on the result. Only the raw escape hatch inv(fn,
|
|
24
|
+
* payload) returns an action's payload as-is.
|
|
20
25
|
*/
|
|
21
26
|
|
|
22
27
|
/** The stable error code a failed storefront call carries, if any. */
|
|
@@ -65,11 +70,13 @@ export function createStorefront(base44, { storageKey = "cart_token", storage }
|
|
|
65
70
|
const by = typeof ref === "string" ? { slug: ref } : ref;
|
|
66
71
|
return inv("commerce/storefront-catalog", { action: "get-product", ...by });
|
|
67
72
|
},
|
|
73
|
+
/** ARRAY of root categories, subcategories nested under `children`. */
|
|
68
74
|
listCategories() {
|
|
69
|
-
return inv("commerce/storefront-catalog", { action: "list-categories" });
|
|
75
|
+
return inv("commerce/storefront-catalog", { action: "list-categories" }).then((d) => d.categories);
|
|
70
76
|
},
|
|
77
|
+
/** ARRAY of { id, name, count }. */
|
|
71
78
|
listRibbons() {
|
|
72
|
-
return inv("commerce/storefront-catalog", { action: "list-ribbons" });
|
|
79
|
+
return inv("commerce/storefront-catalog", { action: "list-ribbons" }).then((d) => d.ribbons);
|
|
73
80
|
},
|
|
74
81
|
|
|
75
82
|
// ── cart (token handled internally; every call returns the full view) ─
|