@base44/app-plugin-commerce 0.1.20 → 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/useAddressForm.js +96 -0
- package/src/commerce/storefront/useCartLine.js +184 -0
- 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 -496
- 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/README.md
CHANGED
|
@@ -8,13 +8,13 @@ It provides a full-featured **commerce data model and behavior** (variant-driven
|
|
|
8
8
|
|
|
9
9
|
- **20 entities** — Products (a product sells variants when it carries attributes; no type field), variations, categories, ribbons, attributes + values, reviews, orders (embedded line/shipping/tax/fee/coupon lines), order notes, refunds, coupons, customers, Shipping & Tax Locations (shipping rates + tax groups per location), payment gateways, store settings, webhooks + deliveries, carts, download permissions, email log.
|
|
10
10
|
- **16 backend functions** — 9 admin (`commerce/admin-products`, `commerce/admin-orders`, `commerce/admin-refunds`, `commerce/admin-coupons`, `commerce/admin-customers`, `commerce/admin-reviews`, `commerce/admin-webhooks`, `commerce/admin-reports`, `commerce/admin-tools`), 4 storefront (`commerce/storefront-catalog`, `commerce/storefront-cart`, `commerce/storefront-checkout`, `commerce/storefront-account`), 2 payment (`commerce/payments`, `commerce/payment-webhook`), and an idempotent `commerce/seed-store` — one call seeds the business defaults **and the whole catalog** (products with attributes in, variants/categories/taxonomy created internally).
|
|
11
|
-
- **Payments: manual methods work out of the box; online cards are opt-in** — the seed enables the manual `offline` method (bank transfer, cash on delivery, pickup: on-hold + instructions, no code) and leaves the `card` gateway **disabled**.
|
|
11
|
+
- **Payments: manual methods work out of the box; online cards are opt-in** — the seed enables the manual `offline` method (bank transfer, cash on delivery, pickup: on-hold + instructions, no code) and leaves the `card` gateway **disabled**. The order side of card payments *is* premade — checkout routing, payment links for unpaid orders, two idempotent confirmation paths (customer return + webhook) and refund records — so a store that opts in wires a provider by implementing **four functions in one file**, `base44/shared/commerce/card-payment.ts`. **For Stripe there is nothing to write**: `base44/shared/commerce/card-payment.stripe.ts` is a complete implementation — copy it over the stub, store the secret, register the webhook, enable the gateway. Any other provider (PayPal, Adyen, a local PSP) follows the same shape. Enable the gateway only with a provider behind it, or checkout answers `503 no_card_payment_provider`. The rule and timing: [`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md); provider mechanics: [`skills/commerce/references/online-payments.md`](./skills/commerce/references/online-payments.md). The admin can add more manual methods in Settings → Payments.
|
|
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
|
-
- **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/return-page calls); `variants.js` maps an attribute selection (Size, Color) onto a `ProductVariation` and back, plus per-option availability and
|
|
15
|
-
- **Storefront React layer** (`src/commerce/storefront/`) —
|
|
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/`) — **two tiers.** The *identity* surfaces stay yours to design (home, collection grid, product-page layout, product card, theme — the kit ships no markup for them). The *commodity* surfaces ship as **blocks** that inherit your theme and can be restyled, slotted or replaced: `CheckoutBlock`, `CartLinesBlock`, `TotalsBlock`, `CouponFieldBlock`, `QuantityStepper`, `OrderReceivedBlock`, `ReviewsBlock`, `AddressFieldsBlock`, plus the product page's internals (`VariantSelectorBlock`, `AddToCartBlock`, `ProductGalleryBlock`, `ProductSpecsBlock`, `BreadcrumbsBlock`, `ProductStripBlock`). Each is a thin composition of the hooks underneath: `StorefrontProvider`, `useProductList`/`useCategories`/`useRibbons`, `useProduct`/`useAddToCart`/`useProductPrice`/`useProductGallery`, `useProductReviews`, `useCart`/`useCartLine`/`useCoupon`, `useCheckout` + `useAddressForm`/`useTotalsLines`/`useCheckoutBlockers`, the headless `ShippingMethodPicker`/`PaymentMethodPicker`, `useOrderReturn`, and `useStorefrontSeo`. 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
|
-
- **Docs** — this README plus the commerce skill folder [`skills/commerce/`](./skills/commerce/)
|
|
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
|
|
|
19
19
|
## Repo map
|
|
20
20
|
|
|
@@ -31,23 +31,26 @@ base44-commerce-template/
|
|
|
31
31
|
├── src/
|
|
32
32
|
│ └── commerce/
|
|
33
33
|
│ ├── admin/ React admin UI (copy into your app's src/commerce/)
|
|
34
|
-
│ ├── utils/ storefront helpers — API client,
|
|
35
|
-
│ └── storefront/ storefront React layer —
|
|
36
|
-
│
|
|
34
|
+
│ ├── utils/ storefront helpers — API client, variants, price/totals rules
|
|
35
|
+
│ └── storefront/ storefront React layer — hooks (cart, checkout, catalog,
|
|
36
|
+
│ └── blocks/ reviews, SEO) + default markup for the commodity UI
|
|
37
37
|
├── scripts/
|
|
38
38
|
│ └── install.js static installer (run from <app>/examples/commerce/scripts/)
|
|
39
39
|
├── skills/
|
|
40
40
|
│ └── commerce/ commerce skill — copied into the app's .agents/skills/
|
|
41
41
|
│ │ so agents know the store natively
|
|
42
|
-
│ ├── SKILL.md the map:
|
|
43
|
-
│ ├──
|
|
44
|
-
│ ├──
|
|
45
|
-
│ ├──
|
|
46
|
-
│ │
|
|
47
|
-
│
|
|
42
|
+
│ ├── SKILL.md the map: what to read, when, and what to skip
|
|
43
|
+
│ ├── install/ the whole install, in three staged files
|
|
44
|
+
│ │ ├── 01-install.md files, admin mount, role gating, the schedule
|
|
45
|
+
│ │ ├── 02-storefront.md storefront pages on the hooks + blocks
|
|
46
|
+
│ │ └── 03-data.md seeding, shipping zones, images, payments decision
|
|
47
|
+
│ ├── references/ opened on demand (catalog rendering, shipping & tax,
|
|
48
|
+
│ │ online payments, reviews, store settings, emails,
|
|
49
|
+
│ │ admin product form, StoreAdmin agent, security, operations)
|
|
48
50
|
│ └── docs/
|
|
49
|
-
│ ├──
|
|
50
|
-
│
|
|
51
|
+
│ ├── entities.md the data-model map (addressing, all 20 entities)
|
|
52
|
+
│ ├── api-admin.md admin function reference + the seed contract
|
|
53
|
+
│ └── api-storefront.md storefront function reference
|
|
51
54
|
└── README.md
|
|
52
55
|
```
|
|
53
56
|
|
|
@@ -68,7 +71,7 @@ node examples/commerce/scripts/install.js
|
|
|
68
71
|
|
|
69
72
|
From your existing Base44 app:
|
|
70
73
|
|
|
71
|
-
1. **Copy the files** — either copy this whole repo into your app at `examples/commerce/` and run `node examples/commerce/scripts/install.js`, or merge `entities/`, `functions/`, `shared/` into your app's `base44/` directory by hand (see [`skills/commerce/
|
|
74
|
+
1. **Copy the files** — either copy this whole repo into your app at `examples/commerce/` and run `node examples/commerce/scripts/install.js`, or merge `entities/`, `functions/`, `shared/` into your app's `base44/` directory by hand (see [`skills/commerce/install/01-install.md`](./skills/commerce/install/01-install.md)). Confirm your `base44/config.jsonc` `entitiesDir`/`functionsDir` point at these folders.
|
|
72
75
|
2. **Push the schema, functions and agent:**
|
|
73
76
|
```bash
|
|
74
77
|
npx base44 entities push
|
|
@@ -89,7 +92,7 @@ From your existing Base44 app:
|
|
|
89
92
|
<Route path="/store-admin/*" element={<AdminApp />} />
|
|
90
93
|
```
|
|
91
94
|
6. **Grant yourself the `admin` role** (Base44 dashboard → users, or `users.inviteUser(email, "admin")`). The admin UI refuses non-admins.
|
|
92
|
-
7. **Seed the store.** Either open `/store-admin` and click **Initialize store defaults** on the first-run setup screen, or call `commerce/seed-store` directly — it creates the settings groups, the payment gateway rows (`offline` enabled, `card` off — enable it only with a provider wired) and — unless you pass your own `locations` — a fallback Shipping & Tax Location, plus the catalog: pass `products` (whole products with attributes — variants, categories, ribbons and taxonomy are created internally) or `with_sample_data: true` for the generic demo. Either way pass `store_name` (the app's name) — it is required on a first seed and becomes both the email subject prefix and the sender name. Once the `general` settings group exists the store counts as ready and the first-run screen stops appearing.
|
|
95
|
+
7. **Seed the store.** Either open `/store-admin` and click **Initialize store defaults** on the first-run setup screen, or call `commerce/seed-store` directly — it creates the settings groups, the payment gateway rows (`offline` enabled, `card` off — enable it only with a provider wired) and — unless you pass your own `locations` — a fallback Shipping & Tax Location, plus the catalog: pass `products` (whole products with attributes — variants, categories, ribbons and taxonomy are created internally) or `with_sample_data: true` for the generic demo. Either way pass `store_name` (the app's name) — it is required on a first seed and becomes both the email subject prefix and the sender name. Once the `general` settings group exists the store counts as ready and the first-run screen stops appearing. Worked example: [`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md); the full payload contract: [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md). Shipping zones are part of the same call — `locations` takes `continents: ["EU"]` and `rest_of_world: true`, so "€20 in Europe, €100 worldwide" is six lines.
|
|
93
96
|
|
|
94
97
|
## Quick start (Base44 MCP / hosted apps)
|
|
95
98
|
|
|
@@ -97,17 +100,17 @@ If you build on Base44's hosted platform, use the Base44 agent/MCP to write the
|
|
|
97
100
|
|
|
98
101
|
1. Copy this whole repo into the target app at `examples/commerce/` (e.g. download + extract a tarball with `run_command`), then run `node examples/commerce/scripts/install.js` via `run_command` — or use `write_file` to copy every file under `base44/` and `src/commerce/` individually (use `list_directory`/`read_file` to adapt to the app's actual layout — e.g. the `@/api/base44Client` path and your router file).
|
|
99
102
|
2. Wait for the app to build (`get_app_status`), then confirm entities exist (`list_entity_schemas`).
|
|
100
|
-
3. Grant your user the `admin` role, then seed the store's data — one `commerce/seed-store` call takes the whole catalog via `products`, or `with_sample_data: true` for the demo catalog; leave both out for defaults only, or skip the call for the admin's first-run **Initialize store defaults** screen ([`skills/commerce/
|
|
103
|
+
3. Grant your user the `admin` role, then seed the store's data — one `commerce/seed-store` call takes the whole catalog via `products`, or `with_sample_data: true` for the demo catalog; leave both out for defaults only, or skip the call for the admin's first-run **Initialize store defaults** screen ([`skills/commerce/install/03-data.md`](./skills/commerce/install/03-data.md)).
|
|
101
104
|
|
|
102
105
|
## What's NOT included
|
|
103
106
|
|
|
104
|
-
- **No
|
|
105
|
-
- **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` (
|
|
106
|
-
- **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
|
|
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, the hooks, and default markup for the commodity UI (checkout, cart, totals, coupon field, reviews, order-received, and the product page's internals), all restylable and replaceable — 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
|
+
- **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` (for Stripe, copy the shipped `card-payment.stripe.ts` over it — see `skills/commerce/references/online-payments.md`) and 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
|
+
- **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).
|
|
107
110
|
|
|
108
111
|
## Next steps
|
|
109
112
|
|
|
110
|
-
- **Install into your app:** [`skills/commerce/
|
|
113
|
+
- **Install into your app:** [`skills/commerce/install/01-install.md`](./skills/commerce/install/01-install.md)
|
|
111
114
|
- **Operate & extend:** the commerce skill — [`skills/commerce/SKILL.md`](./skills/commerce/SKILL.md)
|
|
112
115
|
- **Build a storefront:** [`skills/commerce/docs/api-storefront.md`](./skills/commerce/docs/api-storefront.md)
|
|
113
116
|
- **Admin automation / alternative admin:** [`skills/commerce/docs/api-admin.md`](./skills/commerce/docs/api-admin.md)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* commerce/admin-reports — on-demand report aggregation over orders.
|
|
3
3
|
* Fine to ~10k orders; larger stores should materialize stats
|
|
4
|
-
* (see .agents/skills/commerce/references/
|
|
4
|
+
* (see .agents/skills/commerce/references/operations.md).
|
|
5
5
|
*
|
|
6
6
|
* Actions: summary | sales | top-sellers | stock |
|
|
7
7
|
* orders-totals | products-totals | customers-totals | coupons-totals |
|
|
@@ -21,6 +21,27 @@
|
|
|
21
21
|
* payment_methods?, with_sample_data?, products?, coupons?, locations? }
|
|
22
22
|
* — with_sample_data cannot be combined with the catalog keys.
|
|
23
23
|
*
|
|
24
|
+
* `locations` are the store's Shipping & Tax Locations, and seeding them here is
|
|
25
|
+
* THE way to set shipping up — each is
|
|
26
|
+
* { name, countries?: ["IL"], continents?: ["EU"], rest_of_world?: true,
|
|
27
|
+
* regions?: [{ type, code }], order?, shipping_rates?, tax_groups?,
|
|
28
|
+
* shipping_tax? }
|
|
29
|
+
* with `continents` taking the 7 codes from shared/commerce/data/continents.ts
|
|
30
|
+
* and `rest_of_world: true` marking the catch-all (no regions — a location
|
|
31
|
+
* cannot be both). Locations match in `order` asc and `order` defaults to the
|
|
32
|
+
* array position, so the payload reads as the priority:
|
|
33
|
+
*
|
|
34
|
+
* locations: [
|
|
35
|
+
* { name: "Europe", continents: ["EU"], shipping_rates: [{ name: "Standard", cost: 20 }] },
|
|
36
|
+
* { name: "Worldwide", rest_of_world: true, shipping_rates: [{ name: "International", cost: 100 }] },
|
|
37
|
+
* ]
|
|
38
|
+
*
|
|
39
|
+
* A payload carrying `locations` suppresses the seeded fallback (see (3)), so
|
|
40
|
+
* without a catch-all among them the store ships ONLY to the listed regions —
|
|
41
|
+
* every other address gets `shipping_not_available` at checkout. That case comes
|
|
42
|
+
* back in the response's `warnings`, which is always present and empty when
|
|
43
|
+
* there is nothing to flag.
|
|
44
|
+
*
|
|
24
45
|
* `payment_methods` (e.g. ["card"]) names the checkout methods the store
|
|
25
46
|
* offers: listed gateways are enabled, every other gateway row is disabled —
|
|
26
47
|
* the one call covers "card-only" stores with no entity editing. Explicit
|
|
@@ -218,6 +239,16 @@ Deno.serve(async (req) => {
|
|
|
218
239
|
// location the merchant never asked for (shadowed while the caller's has a
|
|
219
240
|
// lower `order`, live the moment theirs is deleted or reordered). Only
|
|
220
241
|
// create the default when the payload brings no locations of its own.
|
|
242
|
+
//
|
|
243
|
+
// Which makes the missing catch-all a silent trap: locations scoped to
|
|
244
|
+
// regions (say the 51 European country codes) and no fallback behind them is
|
|
245
|
+
// a store that ships to Europe and answers shipping_not_available to
|
|
246
|
+
// everyone else — plausibly intended, so not an error, but never something
|
|
247
|
+
// to discover from a customer. Say it out loud instead.
|
|
248
|
+
const warnings: string[] = [];
|
|
249
|
+
if (catalogSpec?.locations?.length && !catalogSpec.locations.some((l: any) => !l.regions?.length)) {
|
|
250
|
+
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
|
+
}
|
|
221
252
|
if (!catalogSpec?.locations?.length) {
|
|
222
253
|
const fallback = ((await sr.entities["commerce.ShippingTaxLocation"].filter({ name: REST_OF_WORLD_LOCATION.name }, undefined, 1)) ?? [])[0];
|
|
223
254
|
if (!fallback) {
|
|
@@ -250,6 +281,9 @@ Deno.serve(async (req) => {
|
|
|
250
281
|
store_name: { value: storeNameAction === "kept_existing" ? currentName : storeName, action: storeNameAction },
|
|
251
282
|
currency: currencyInfo ? { value: currencyInfo.code, action: currencyAction } : null,
|
|
252
283
|
payment_methods: paymentMethods,
|
|
284
|
+
// Always present so a caller can read it unconditionally; empty means the
|
|
285
|
+
// seed has nothing to flag.
|
|
286
|
+
warnings,
|
|
253
287
|
});
|
|
254
288
|
} catch (e) {
|
|
255
289
|
if (e instanceof HttpError) return fail(e.status, e.message, e.code);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* variations?: [{ options: { Size: "S" }, regular_price?, stock_quantity?, sku?, image? }] }
|
|
19
19
|
*/
|
|
20
20
|
import { HttpError } from "../../../shared/commerce/auth.ts";
|
|
21
|
+
import { CONTINENTS } from "../../../shared/commerce/data/continents.ts";
|
|
21
22
|
import { getSettings } from "../../../shared/commerce/settings.ts";
|
|
22
23
|
import { scanAll } from "../../../shared/commerce/scan.ts";
|
|
23
24
|
import {
|
|
@@ -172,7 +173,8 @@ export function normalizeCatalogPayload(body: any): CatalogSpec | null {
|
|
|
172
173
|
if (!Number.isFinite(Number(c.amount)) || Number(c.amount) < 0) err(`${path}.amount`, "must be a non-negative number");
|
|
173
174
|
});
|
|
174
175
|
|
|
175
|
-
|
|
176
|
+
// The index is the location's default `order` — see normalizeLocation.
|
|
177
|
+
const locations = rawLocations.map((l, i) => normalizeLocation(l, i, `locations[${i}]`, err)).filter(Boolean);
|
|
176
178
|
|
|
177
179
|
if (errors.length) throw new CatalogPayloadError(errors);
|
|
178
180
|
|
|
@@ -187,15 +189,24 @@ export function normalizeCatalogPayload(body: any): CatalogSpec | null {
|
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
/**
|
|
190
|
-
* A caller location is Wix-shaped: countries
|
|
191
|
-
*
|
|
192
|
+
* A caller location is Wix-shaped: a scope (countries as ISO codes, whole
|
|
193
|
+
* continents, explicit regions, or `rest_of_world` for the catch-all), shipping
|
|
194
|
+
* rates, named tax groups and an optional shipping tax:
|
|
192
195
|
*
|
|
193
196
|
* { name: "Israel", countries: ["IL"],
|
|
194
197
|
* shipping_rates: [{ name: "Standard", cost: 20, free_over: 150 }],
|
|
195
198
|
* tax_groups: [{ name: "Products", rates: [{ name: "VAT", rate: 18 }] }],
|
|
196
199
|
* shipping_tax: { type: "percent", value: 18 } }
|
|
200
|
+
*
|
|
201
|
+
* Continents spare the caller a 51-code country list, and `rest_of_world: true`
|
|
202
|
+
* names the catch-all instead of relying on empty-array magic. Locations match
|
|
203
|
+
* in `order` asc, and `order` defaults to the payload position, so the array
|
|
204
|
+
* reads as the priority — Europe first, worldwide last:
|
|
205
|
+
*
|
|
206
|
+
* [{ name: "Europe", continents: ["EU"], shipping_rates: [{ name: "Standard", cost: 20 }] },
|
|
207
|
+
* { name: "Worldwide", rest_of_world: true, shipping_rates: [{ name: "International", cost: 100 }] }]
|
|
197
208
|
*/
|
|
198
|
-
function normalizeLocation(l: any, path: string, err: (p: string, e: string) => void): any | null {
|
|
209
|
+
function normalizeLocation(l: any, index: number, path: string, err: (p: string, e: string) => void): any | null {
|
|
199
210
|
if (!l || typeof l !== "object" || Array.isArray(l)) {
|
|
200
211
|
err(path, "must be an object");
|
|
201
212
|
return null;
|
|
@@ -210,9 +221,27 @@ function normalizeLocation(l: any, path: string, err: (p: string, e: string) =>
|
|
|
210
221
|
const code = String(c ?? "").trim().toUpperCase();
|
|
211
222
|
if (code) regions.push({ type: "country", code });
|
|
212
223
|
}
|
|
224
|
+
for (const c of l.continents ?? []) {
|
|
225
|
+
const code = String(c ?? "").trim().toUpperCase();
|
|
226
|
+
if (!code) continue;
|
|
227
|
+
if (!CONTINENTS.some((continent) => continent.code === code)) {
|
|
228
|
+
err(`${path}.continents`, `unknown continent code: ${code} — known: ${CONTINENTS.map((continent) => `${continent.code} (${continent.name})`).join(", ")}`);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
regions.push({ type: "continent", code });
|
|
232
|
+
}
|
|
213
233
|
for (const r of l.regions ?? []) {
|
|
214
234
|
if (r?.type && r?.code) regions.push({ type: String(r.type), code: String(r.code).toUpperCase() });
|
|
215
235
|
}
|
|
236
|
+
// The catch-all is defined by having NO regions, so a scoped "rest of the
|
|
237
|
+
// world" is a contradiction, not a merge: silently dropping either half would
|
|
238
|
+
// ship a store that either over- or under-delivers shipping.
|
|
239
|
+
if (l.rest_of_world) {
|
|
240
|
+
if (regions.length) {
|
|
241
|
+
err(path, "rest_of_world is the catch-all for every address no other location matches — it cannot also be scoped with countries/continents/regions");
|
|
242
|
+
}
|
|
243
|
+
regions.length = 0;
|
|
244
|
+
}
|
|
216
245
|
const slugBase = slugify(name);
|
|
217
246
|
const shippingRates = (Array.isArray(l.shipping_rates) ? l.shipping_rates : []).map((r: any, i: number) => {
|
|
218
247
|
const rate = { id: r?.id || `${slugBase}-${slugify(String(r?.name || `rate-${i + 1}`))}`,
|
|
@@ -241,7 +270,12 @@ function normalizeLocation(l: any, path: string, err: (p: string, e: string) =>
|
|
|
241
270
|
if (!Number.isFinite(value) || value < 0) err(`${path}.shipping_tax.value`, "must be a non-negative number");
|
|
242
271
|
shippingTax = { type, value: Number.isFinite(value) ? value : 0 };
|
|
243
272
|
}
|
|
244
|
-
|
|
273
|
+
// Payload position IS match priority when `order` is omitted; an explicit
|
|
274
|
+
// `order` always wins. Positions are 0,1,2… — far below the seeded fallback's
|
|
275
|
+
// 999, so "Rest of the world" stays the last location checked even when a
|
|
276
|
+
// caller's locations are added to a store that already has it.
|
|
277
|
+
const order = l.order != null && Number.isFinite(Number(l.order)) ? Number(l.order) : index;
|
|
278
|
+
return { name, order, regions, shipping_rates: shippingRates, tax_groups: taxGroups, shipping_tax: shippingTax };
|
|
245
279
|
}
|
|
246
280
|
|
|
247
281
|
function normalizeProduct(
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stripe — a READY-TO-USE card payment provider. Nothing to transcribe, nothing
|
|
3
|
+
* to fill in: this file is a complete implementation of the four functions
|
|
4
|
+
* `shared/commerce/card-payment.ts` declares, in the exact shape its callers
|
|
5
|
+
* import (`payments.ts` and the premade `commerce/payment-webhook`).
|
|
6
|
+
*
|
|
7
|
+
* WIRE IT BY COPYING IT OVER THE STUB — one call, no editing:
|
|
8
|
+
*
|
|
9
|
+
* fs.copyFileSync(
|
|
10
|
+
* "base44/shared/commerce/card-payment.stripe.ts",
|
|
11
|
+
* "base44/shared/commerce/card-payment.ts",
|
|
12
|
+
* );
|
|
13
|
+
*
|
|
14
|
+
* then (1) store the merchant's Stripe **secret key** as the `STRIPE_SECRET_KEY`
|
|
15
|
+
* app secret and redeploy the backend so the functions see it, (2) register
|
|
16
|
+
* `https://<app-domain>/functions/commerce/payment-webhook` with Stripe for
|
|
17
|
+
* `checkout.session.completed` — there is no signing secret to store, see
|
|
18
|
+
* `parseWebhook` below — and (3) enable the `card` gateway
|
|
19
|
+
* (`commerce/seed-store` with `payment_methods: ["offline", "card"]`), which is
|
|
20
|
+
* what makes any of this visible at checkout.
|
|
21
|
+
*
|
|
22
|
+
* NEVER patch this file (or the stub it replaces) with partial edits. A
|
|
23
|
+
* find_replace that leaves the originals behind gives every commerce function a
|
|
24
|
+
* duplicate-export bundle error ("Multiple exports with the same name …") and
|
|
25
|
+
* the whole backend stops deploying. The fix is always the whole-file copy
|
|
26
|
+
* above — which is also why this file exists.
|
|
27
|
+
*
|
|
28
|
+
* It lives **beside** the stub rather than in a subfolder precisely so its
|
|
29
|
+
* relative imports (`./auth.ts`) are valid both here and at the copy's
|
|
30
|
+
* destination — the copy can never produce a broken specifier, and no function
|
|
31
|
+
* imports this file while it waits, so it is never bundled.
|
|
32
|
+
*
|
|
33
|
+
* A later kit update re-copies `shared/commerce/` over the app and restores the
|
|
34
|
+
* stub, so re-run the copy after updating the template.
|
|
35
|
+
*
|
|
36
|
+
* A different provider (PayPal, Adyen, a local PSP) implements these same four
|
|
37
|
+
* functions against its own API, and ships the same way —
|
|
38
|
+
* `card-payment.<provider>.ts` next to the stub. `references/online-payments.md`
|
|
39
|
+
* has the rules; this file is the worked model.
|
|
40
|
+
*/
|
|
41
|
+
import Stripe from "npm:stripe@18";
|
|
42
|
+
import { HttpError } from "./auth.ts";
|
|
43
|
+
|
|
44
|
+
/** A hosted payment page for one order. */
|
|
45
|
+
export interface CardPaymentPage {
|
|
46
|
+
/** Where the customer goes to pay. */
|
|
47
|
+
url: string;
|
|
48
|
+
/** Stripe's id for this payment (the Checkout Session id), stored on the
|
|
49
|
+
* order and handed back to checkCardPaymentPaid / refundCardPayment. */
|
|
50
|
+
reference: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Credentials come from backend env, never an entity and never the client. Read
|
|
55
|
+
* lazily (per call, not at module load) so a store that has the file but not yet
|
|
56
|
+
* the secret answers a clean 503 at checkout instead of failing to boot every
|
|
57
|
+
* commerce function that imports it.
|
|
58
|
+
*/
|
|
59
|
+
const stripe = () => {
|
|
60
|
+
const key = Deno.env.get("STRIPE_SECRET_KEY");
|
|
61
|
+
if (!key) {
|
|
62
|
+
throw new HttpError(503, "Card payments are not configured — the STRIPE_SECRET_KEY secret is missing.", "no_card_payment_provider");
|
|
63
|
+
}
|
|
64
|
+
return new Stripe(key);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Stripe amounts are in minor units; these currencies have none, so ×100 would
|
|
68
|
+
// charge a hundred times the total.
|
|
69
|
+
const ZERO_DECIMAL = new Set(["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]);
|
|
70
|
+
const minorUnits = (amount: number, currency: string) =>
|
|
71
|
+
Math.round(Number(amount) * (ZERO_DECIMAL.has(String(currency).toUpperCase()) ? 1 : 100));
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a payment page for `order.total` in `order.currency`. The customer
|
|
75
|
+
* comes back to successUrl / cancelUrl (both already carry order_id/order_key
|
|
76
|
+
* for the return page).
|
|
77
|
+
*/
|
|
78
|
+
export async function createCardPayment(
|
|
79
|
+
_sr: any,
|
|
80
|
+
order: any,
|
|
81
|
+
opts: { successUrl: string; cancelUrl: string; customerEmail?: string },
|
|
82
|
+
): Promise<CardPaymentPage> {
|
|
83
|
+
// order_id + order_key in the metadata is how the premade payment-webhook
|
|
84
|
+
// names the order when Stripe's event arrives, and what checkCardPaymentPaid
|
|
85
|
+
// compares against — keep it on both objects (the session's own metadata is
|
|
86
|
+
// not copied to the payment intent).
|
|
87
|
+
const metadata = { order_id: String(order.id), order_key: String(order.order_key) };
|
|
88
|
+
const session = await stripe().checkout.sessions.create({
|
|
89
|
+
mode: "payment",
|
|
90
|
+
line_items: [{
|
|
91
|
+
quantity: 1,
|
|
92
|
+
price_data: {
|
|
93
|
+
currency: String(order.currency || "USD").toLowerCase(),
|
|
94
|
+
product_data: { name: `Order #${order.order_number}` },
|
|
95
|
+
unit_amount: minorUnits(order.total, order.currency),
|
|
96
|
+
},
|
|
97
|
+
}],
|
|
98
|
+
customer_email: opts.customerEmail || undefined,
|
|
99
|
+
metadata,
|
|
100
|
+
payment_intent_data: { metadata },
|
|
101
|
+
success_url: opts.successUrl,
|
|
102
|
+
cancel_url: opts.cancelUrl,
|
|
103
|
+
});
|
|
104
|
+
if (!session.url) throw new HttpError(502, "Stripe did not return a payment page URL.", "payment_session_failed");
|
|
105
|
+
return { url: session.url, reference: session.id };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Has Stripe actually received the money? Asked of Stripe's API — never of the
|
|
110
|
+
* caller. Runs on the customer-return page, the webhook's unverified path and
|
|
111
|
+
* the admin's "Check payment" button.
|
|
112
|
+
*/
|
|
113
|
+
export async function checkCardPaymentPaid(_sr: any, order: any, reference: string): Promise<boolean> {
|
|
114
|
+
const session = await stripe().checkout.sessions.retrieve(reference);
|
|
115
|
+
// The payment must be for THIS order — stops a reference to some other
|
|
116
|
+
// (genuinely paid) session being replayed against a different order.
|
|
117
|
+
return session.payment_status === "paid" && session.metadata?.order_id === String(order.id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Refund `amount` (display units, e.g. 12.34) at Stripe. Called BEFORE the
|
|
122
|
+
* local refund record is written, so throwing here writes nothing.
|
|
123
|
+
*/
|
|
124
|
+
export async function refundCardPayment(_sr: any, _order: any, opts: {
|
|
125
|
+
reference: string;
|
|
126
|
+
amount: number;
|
|
127
|
+
currency: string;
|
|
128
|
+
reason?: string;
|
|
129
|
+
}): Promise<{ refund_id: string }> {
|
|
130
|
+
// The stored reference is the Checkout Session; the refundable object is the
|
|
131
|
+
// payment intent behind it, which only exists once the session was paid.
|
|
132
|
+
const session = await stripe().checkout.sessions.retrieve(opts.reference);
|
|
133
|
+
if (!session.payment_intent) {
|
|
134
|
+
throw new HttpError(409, "This payment has no charge to refund at Stripe.", "no_charge_to_refund");
|
|
135
|
+
}
|
|
136
|
+
const refund = await stripe().refunds.create({
|
|
137
|
+
payment_intent: String(session.payment_intent),
|
|
138
|
+
amount: minorUnits(opts.amount, opts.currency),
|
|
139
|
+
});
|
|
140
|
+
return { refund_id: refund.id };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** What parseWebhook distills an event into — the premade webhook's contract. */
|
|
144
|
+
export interface CardWebhookEvent {
|
|
145
|
+
/** The order the event is about, read from the metadata createCardPayment
|
|
146
|
+
* attached. */
|
|
147
|
+
order_id: string;
|
|
148
|
+
/** Must match the order's key, or the premade webhook rejects the event — so
|
|
149
|
+
* an event cannot be pointed at another order. */
|
|
150
|
+
order_key: string;
|
|
151
|
+
/** True ONLY after verifying the request signature over the raw payload
|
|
152
|
+
* bytes; false makes the event an unverified nudge, verified against Stripe's
|
|
153
|
+
* API instead. */
|
|
154
|
+
paid: boolean;
|
|
155
|
+
/** Stripe's payment id, ONLY from a signature-verified event; unset means the
|
|
156
|
+
* premade flow uses the reference stored on the order at checkout. */
|
|
157
|
+
reference?: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Stripe webhook events. The body is never trusted: this only names the order
|
|
162
|
+
* (from the metadata createCardPayment attached) and returns `paid: false`, so
|
|
163
|
+
* the premade webhook verifies with Stripe through checkCardPaymentPaid. Hence
|
|
164
|
+
* NO signing secret to store — a forged call can at worst trigger a re-check,
|
|
165
|
+
* never mark an order paid. (The optional fast path that skips the API
|
|
166
|
+
* round-trip verifies the signature and returns `paid: true` —
|
|
167
|
+
* `references/online-payments.md`.)
|
|
168
|
+
*
|
|
169
|
+
* null for anything that isn't a payment event for one of this store's orders;
|
|
170
|
+
* the premade function answers 200 so Stripe stops retrying.
|
|
171
|
+
*/
|
|
172
|
+
export async function parseWebhook(_req: Request, payload: string): Promise<CardWebhookEvent | null> {
|
|
173
|
+
let event: any;
|
|
174
|
+
try { event = JSON.parse(payload); } catch { return null; }
|
|
175
|
+
const metadata = event?.data?.object?.metadata;
|
|
176
|
+
if (!metadata?.order_id || !metadata?.order_key) return null;
|
|
177
|
+
return { order_id: String(metadata.order_id), order_key: String(metadata.order_key), paid: false };
|
|
178
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Full-collection scan helper. Base44's filter() is exact-match with a 5k page
|
|
3
3
|
* cap and no total count, so server-side search/aggregation loops pages of 500.
|
|
4
4
|
* Keep `cap` sane — reports over very large stores should move to a
|
|
5
|
-
* materialized stats entity (see .agents/skills/commerce/references/
|
|
5
|
+
* materialized stats entity (see .agents/skills/commerce/references/operations.md).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export interface ScanOpts {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* NOTE ON CONCURRENCY: Base44 has no transactions, so nextOrderNumber() is a
|
|
5
5
|
* read-max-then-increment and two simultaneous checkouts could race. The window
|
|
6
6
|
* is tiny and order ids (not numbers) are the primary key; see
|
|
7
|
-
* .agents/skills/commerce/references/
|
|
7
|
+
* .agents/skills/commerce/references/operations.md for mitigations.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const ORDER_NUMBER_START = 1001;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@base44/app-plugin-commerce",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Base44 Commerce plugin — entities, backend functions, shared commerce engine, admin UI and the commerce skill, shipped as copyable source",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"base44",
|
package/scripts/install.js
CHANGED
|
@@ -25,12 +25,11 @@
|
|
|
25
25
|
* overwritten (so re-running after a template update is safe), everything
|
|
26
26
|
* else in the app is left untouched.
|
|
27
27
|
*
|
|
28
|
-
* This is only the static part of the install.
|
|
29
|
-
* ../skills/commerce/
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* documentation into the app.
|
|
28
|
+
* This is only the static part of the install. Everything after it lives in
|
|
29
|
+
* ../skills/commerce/install/01-install.md (deps, the /store-admin mount, the
|
|
30
|
+
* admin-role gating), which routes on to 02-storefront.md and 03-data.md at the
|
|
31
|
+
* right moments; ../skills/commerce/SKILL.md is the map for all later work. The
|
|
32
|
+
* skill folder carries all of that documentation into the app.
|
|
34
33
|
*
|
|
35
34
|
* Written with dynamic import() and process.argv[1] (instead of require/
|
|
36
35
|
* __dirname) so it runs unchanged whether the host app's package.json is
|
|
@@ -132,6 +131,17 @@
|
|
|
132
131
|
["src", "commerce", "admin", "pages", "settings", "TaxRatesTable.jsx"],
|
|
133
132
|
["src", "commerce", "admin", "pages", "settings", "ShippingSettings.jsx"],
|
|
134
133
|
["src", "commerce", "admin", "pages", "settings", "ShippingZoneEditor.jsx"],
|
|
134
|
+
// The skill's docs were restructured into stage files (install/01..03) plus a
|
|
135
|
+
// smaller reference set. Stale guidance is worse than none — agents read
|
|
136
|
+
// whatever is in .agents/skills/ — so every retired doc goes, by name.
|
|
137
|
+
[".agents", "skills", "commerce", "post-installation.md"], // → install/01..03
|
|
138
|
+
[".agents", "skills", "commerce", "installation-guidelines.md"], // → install/01-install.md + references/store-admin-agent.md
|
|
139
|
+
[".agents", "skills", "commerce", "references", "product-render.md"], // → references/catalog-rendering.md
|
|
140
|
+
[".agents", "skills", "commerce", "references", "storefront-product-page.md"], // → references/catalog-rendering.md
|
|
141
|
+
[".agents", "skills", "commerce", "references", "scheduled-work.md"], // → references/operations.md
|
|
142
|
+
[".agents", "skills", "commerce", "references", "limits-and-performance.md"], // → references/operations.md
|
|
143
|
+
[".agents", "skills", "commerce", "references", "webhooks.md"], // → references/operations.md
|
|
144
|
+
[".agents", "skills", "commerce", "references", "media-and-downloads.md"], // → docs/api-storefront.md + install/03-data.md
|
|
135
145
|
];
|
|
136
146
|
let retiredRemoved = 0;
|
|
137
147
|
for (const parts of RETIRED) {
|
|
@@ -186,16 +196,16 @@
|
|
|
186
196
|
(retiredRemoved ? ` (${retiredRemoved} retired file${retiredRemoved === 1 ? "" : "s"} removed)` : ""),
|
|
187
197
|
);
|
|
188
198
|
console.log(
|
|
189
|
-
"\nNext
|
|
199
|
+
"\nNext: read .agents/skills/commerce/install/01-install.md and follow it.\n" +
|
|
200
|
+
"It is the whole install, and it says when to open 02-storefront.md and\n" +
|
|
201
|
+
"03-data.md — read nothing else up front.\n" +
|
|
202
|
+
"\n" +
|
|
190
203
|
" 1. No deps to add: sonner, recharts and react-markdown ship with the default\n" +
|
|
191
|
-
" Base44 template — check package.json and npm i only what is truly missing
|
|
192
|
-
" do not re-install packages already listed as dependencies\n" +
|
|
204
|
+
" Base44 template — check package.json and npm i only what is truly missing\n" +
|
|
193
205
|
' 2. Mount the admin router: <Route path="/store-admin/*" element={<AdminApp />} />\n' +
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
" call (store_name required) takes the whole catalog via products, or the demo data\n" +
|
|
198
|
-
" via with_sample_data, or defaults only (post-installation.md §3)\n" +
|
|
206
|
+
" plus the mandatory /order-received route (<OrderReceivedBlock/>)\n" +
|
|
207
|
+
" 3. Seed the store — one commerce/seed-store call (store_name required) takes the\n" +
|
|
208
|
+
" catalog, currency, shipping locations and payment methods\n" +
|
|
199
209
|
" 4. CLI installs only: npx base44 agents push (the hosted runtime syncs agents on write)"
|
|
200
210
|
);
|
|
201
211
|
})();
|