@base44/app-plugin-commerce 0.1.20 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -22
- package/base44/agents/commerce/StoreAdmin.jsonc +1 -1
- package/base44/entities/commerce.OrderRefund.jsonc +1 -1
- package/base44/entities/commerce.PaymentGateway.jsonc +1 -1
- package/base44/entities/commerce.Webhook.jsonc +1 -1
- package/base44/functions/commerce/admin-products/entry.ts +1 -1
- package/base44/functions/commerce/admin-reports/entry.ts +1 -1
- package/base44/functions/commerce/payments/entry.ts +2 -2
- package/base44/functions/commerce/seed-store/defaults.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/functions/commerce/storefront-catalog/entry.ts +1 -1
- package/base44/functions/commerce/storefront-checkout/entry.ts +1 -1
- package/base44/shared/commerce/card-payment.stripe.ts +198 -0
- package/base44/shared/commerce/card-payment.ts +1 -1
- package/base44/shared/commerce/payments.ts +2 -2
- package/base44/shared/commerce/scan.ts +1 -1
- package/base44/shared/commerce/sequence.ts +2 -2
- package/package.json +1 -1
- package/scripts/install.js +24 -14
- package/skills/commerce/SKILL.md +117 -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 +444 -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 +49 -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 +6 -3
- package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
- package/src/commerce/admin/pages/products/Reviews.jsx +1 -1
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +1 -1
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +1 -1
- package/src/commerce/storefront/StorefrontProvider.jsx +106 -20
- package/src/commerce/storefront/index.js +74 -24
- package/src/commerce/storefront/internal/useAsyncData.js +86 -0
- package/src/commerce/storefront/useAddressForm.js +96 -0
- package/src/commerce/storefront/useCartLine.js +221 -0
- package/src/commerce/storefront/useCheckout.jsx +18 -6
- package/src/commerce/storefront/useOrderReturn.js +36 -10
- package/src/commerce/storefront/useProduct.js +295 -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 +22 -7
- package/src/commerce/utils/price.js +95 -0
- package/src/commerce/utils/shipping-promos.js +2 -2
- package/src/commerce/utils/specs.js +26 -0
- package/src/commerce/utils/storefront.js +47 -3
- package/src/commerce/utils/totals.js +110 -0
- package/src/commerce/utils/variants.js +58 -3
- 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
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Entities — the data-model map
|
|
2
|
+
|
|
3
|
+
Every `commerce.*` entity, what holds what, and who may write it. Read this before **any** direct entity read or write; function-mediated flows never need it.
|
|
4
|
+
|
|
5
|
+
## Addressing — read this first
|
|
6
|
+
|
|
7
|
+
Entity names are **dotted and namespaced**, so the SDK accessor is **bracket syntax, always**:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
base44.entities["commerce.PaymentGateway"] // ✅ the only spelling that resolves
|
|
11
|
+
base44.entities["commerce__PaymentGateway"] // ❌ 404 Entity schema … not found
|
|
12
|
+
base44.entities["PaymentGateway"] // ❌ there is no un-namespaced alias
|
|
13
|
+
base44.entities.commerce.PaymentGateway // ❌ not a nested object
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Schema files live at **`base44/entities/commerce.<Name>.jsonc`** — one targeted read when you need a field this page doesn't carry.
|
|
17
|
+
|
|
18
|
+
`filter` is **exact-match only**; args are `(query, sort, limit, skip)`, `list` is `(sort, limit, skip)`. Plus `.get(id)` · `.create(fields)` · `.update(id, patch)` · `.delete(id)`.
|
|
19
|
+
|
|
20
|
+
```js
|
|
21
|
+
const gateways = await base44.entities["commerce.PaymentGateway"].list("order", 100);
|
|
22
|
+
const [offline] = await base44.entities["commerce.PaymentGateway"].filter({ slug: "offline" }, undefined, 1);
|
|
23
|
+
await base44.entities["commerce.PaymentGateway"].update(offline.id, { title: "Bank transfer" });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Never** scan `base44/entities/` to discover names, guess a spelling and retry on 404, or web-search a kit API's shape. This page plus [`api-admin.md`](./api-admin.md) and [`api-storefront.md`](./api-storefront.md) are the answer.
|
|
27
|
+
|
|
28
|
+
## The catalog — all 20
|
|
29
|
+
|
|
30
|
+
**RLS is identical on every entity: `admin` on read *and* write**, catalog included. A non-admin call is rejected by the backend whatever the UI does; storefront access goes exclusively through `commerce/storefront-*` (service role, safe projections) — [`../references/guest-access-security.md`](../references/guest-access-security.md). Hence no RLS column: assume admin-only.
|
|
31
|
+
|
|
32
|
+
**Owned by `<fn>` = never write directly** — the function maintains derived fields, counts, emails and webhooks a raw write skips. Names below are all `commerce/<name>`.
|
|
33
|
+
|
|
34
|
+
| Entity | Purpose | Key fields | Write path |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| `Product` | Sellable product; sells variants when it carries `attributes` — there is **no `type` field** | `name` `slug` `status` `price` `category_ids` `attributes[]` `images[]` | owned by **`admin-products`** |
|
|
37
|
+
| `ProductVariation` | One purchasable attribute combination of a parent | `product_id` `attributes[]` `sku` `regular_price` `stock_quantity` `status` | owned by **`admin-products`** — `save` diffs them inside the parent call |
|
|
38
|
+
| `ProductCategory` | Hierarchical category (`parent_id` tree) | `name` `slug` `parent_id` `menu_order` `count` | direct CRUD, or **`admin-products` `save-term`** (slug uniqueness, self-parent guard) |
|
|
39
|
+
| `ProductRibbon` | Flat cross-cutting label ("Best Seller") | `name` `count` | direct CRUD, or **`save-term`** — get-or-create by name, so it can't split a ribbon |
|
|
40
|
+
| `ProductAttribute` | Variant axis shared across products (Color, Size) | `name` `code` `order` | direct CRUD, or **`save-term`** (`code` uniqueness; `code` is what a filter URL carries) |
|
|
41
|
+
| `ProductAttributeTerm` | One value of an attribute (Size → M) | `attribute_id` `name` `order` `count` | direct CRUD, or **`save-term`** — products store values **by name**, so a rename rewrites them |
|
|
42
|
+
| `ProductReview` | Moderated customer review | `product_id` `status` `reviewer` `rating` `review` `verified` | owned by **`admin-reviews`** (recomputes the rating); created by `submit-review` |
|
|
43
|
+
| `Order` | Order with embedded line items, shipping/tax/fee/coupon lines, totals | `order_number` `order_key` `status` `line_items[]` `total` `billing` | owned by **`admin-orders`**; created by `place-order` |
|
|
44
|
+
| `OrderNote` | System, private, or customer-facing note | `order_id` `note` `is_customer_note` `added_by` | owned by **`admin-orders`** `add-note`/`delete-note` — a customer note emails the buyer |
|
|
45
|
+
| `OrderRefund` | Full or partial refund | `order_id` `amount` `line_items[]` `restock_items` | owned by **`admin-refunds`** (totals, restock, status) |
|
|
46
|
+
| `Customer` | Customer; guests have no `user_id` | `email` `first_name` `last_name` `is_guest` `billing` `shipping` | owned by **`admin-customers`**; checkout upserts it, `update-my-addresses` is the owner's path |
|
|
47
|
+
| `Coupon` | Discount coupon with restrictions and usage limits | `code` `enabled` `discount_type` `amount` `usage_count` | owned by **`admin-coupons`** (unique lowercased code) |
|
|
48
|
+
| `Cart` | Ephemeral cart + checkout draft keyed by `cart_token`; items hold **no prices** | `cart_token` `items[]` `coupon_codes` `shipping_address` `expires_at` | owned by **`storefront-cart`/`-checkout`** — never write directly |
|
|
49
|
+
| `PaymentGateway` | One checkout method (`offline`, `card`, manual options); no secrets here | `slug` `title` `description` `enabled` `order` `settings` | **mixed**: `enabled` via `seed-store` `payment_methods`; the rest direct CRUD |
|
|
50
|
+
| `ShippingTaxLocation` | A sales location: regions + its shipping rates, tax groups, shipping tax. Empty `regions` = catch-all | `name` `order` `regions[]` `shipping_rates[]` `tax_groups[]` | **direct CRUD** (or `seed-store` `locations`) — mint stable rate `id`s |
|
|
51
|
+
| `StoreSettings` | Config, **one record per `group_id`** (`general` `products` `inventory` `tax` `shipping` `emails`) | `group_id` `values` | **direct CRUD** — patch `values`, never replace the group |
|
|
52
|
+
| `DownloadPermission` | A customer's access to a purchased file | `order_id` `customer_email` `product_id` `downloads_remaining` `access_expires` | owned by the **order lifecycle** (`admin-orders` `grant-download`/`revoke-download`) |
|
|
53
|
+
| `Webhook` | Outgoing webhook definition | `name` `topic` `delivery_url` `secret` `status` | **direct CRUD** (+ `admin-webhooks` `test`/`redeliver`); `failure_count` engine-owned |
|
|
54
|
+
| `WebhookDelivery` | Log of one delivery attempt | `webhook_id` `topic` `response_code` `success` | **engine-owned, read-only**; prune via `admin-tools` |
|
|
55
|
+
| `EmailLog` | Log of transactional emails sent | `email_type` `recipient` `target` `success` `error` | **engine-owned, read-only** |
|
|
56
|
+
|
|
57
|
+
## Relationships
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Product ──< ProductVariation one row per stocked combination
|
|
61
|
+
├─ category_ids ─> ProductCategory ──< ProductCategory (parent_id tree)
|
|
62
|
+
├─ ribbon_ids ─> ProductRibbon
|
|
63
|
+
├─ attributes[].attribute_id ─> ProductAttribute ──< ProductAttributeTerm
|
|
64
|
+
└──< ProductReview
|
|
65
|
+
|
|
66
|
+
Cart ──place-order──> Order ──< OrderNote
|
|
67
|
+
(cart_token, 48h TTL) │ ──< OrderRefund
|
|
68
|
+
│ ──< DownloadPermission
|
|
69
|
+
└─ customer_id ─> Customer
|
|
70
|
+
|
|
71
|
+
Coupon referenced by code from Cart.coupon_codes / Order.coupon_lines
|
|
72
|
+
StoreSettings one record per group_id PaymentGateway one row per method
|
|
73
|
+
ShippingTaxLocation standalone; matched by `order` ascending at pricing time
|
|
74
|
+
Webhook ──< WebhookDelivery EmailLog append-only
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Derived fields — never write these
|
|
78
|
+
|
|
79
|
+
| Field | Changed instead by |
|
|
80
|
+
|---|---|
|
|
81
|
+
| variant parent's `price` `regular_price` `on_sale` — rolled up from the cheapest publishable variation, which is what makes cards, price sort and price filters agree | `admin-products` `save` / `seed-store`; set prices on the **variations** |
|
|
82
|
+
| `stock_status` (product + variation) — from quantity + `backorders`, or rolled up from variations when the parent doesn't `manage_stock` | `admin-products` `set-stock`/`save`; order stock reduce/restore |
|
|
83
|
+
| `Product.average_rating` `rating_count` — from approved reviews | `admin-reviews`, `storefront-catalog` `submit-review` |
|
|
84
|
+
| `Product.total_sales` — first stock reduction, reversed on restore | the order transition engine |
|
|
85
|
+
| `count` on `ProductCategory` / `ProductRibbon` / `ProductAttributeTerm` | `admin-products`; repair with `admin-tools` `recount-terms` |
|
|
86
|
+
| `Order` totals (`subtotal` `discount_total` `shipping_total` `*_tax` `total`) and per-line totals | `admin-orders` `update`/`recalculate`/`apply-coupon`; `place-order` |
|
|
87
|
+
| `Order` status side effects — `date_paid` `date_completed` `stock_reduced` `coupon_usages_counted` `download_permissions_granted` `emails_sent` `hold_expires_at`; flag-guarded so a re-entered status never double-fires | `admin-orders` `update-status`/`bulk-status` — never a direct `status` write |
|
|
88
|
+
| `Order.total_refunded` | `admin-refunds` |
|
|
89
|
+
| `Customer.orders_count` `total_spent` `is_paying_customer` | `admin-customers` `recalculate-stats` |
|
|
90
|
+
| `Coupon.usage_count` `used_by` | the transition engine; repair with `admin-tools` `recount-coupon-usage` |
|
|
91
|
+
|
|
92
|
+
## Recipes
|
|
93
|
+
|
|
94
|
+
**Enable/disable a payment gateway** — not an entity write. One call converges *every* gateway row to the set you name, validates the slugs, and is idempotent. Cards need a provider wired first ([`../install/03-data.md`](../install/03-data.md)):
|
|
95
|
+
```js
|
|
96
|
+
await base44.functions.invoke("commerce/seed-store", { payment_methods: ["offline", "card"] });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Edit a gateway's copy or position** — ordinary config; direct CRUD, as Settings → Payments does.
|
|
100
|
+
```js
|
|
101
|
+
await base44.entities["commerce.PaymentGateway"].update(id, { title: "Bank transfer", description: "…", order: 1 });
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Read recent orders** — free-text/date search is `admin-orders` `search`; `filter` is exact-match only:
|
|
105
|
+
```js
|
|
106
|
+
const rows = await base44.entities["commerce.Order"].filter({ status: "processing" }, "-created_date", 20);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Add an order note** — via the function: a customer note emails the buyer.
|
|
110
|
+
```js
|
|
111
|
+
await base44.functions.invoke("commerce/admin-orders", { action: "add-note", order_id, note: "Shipped", is_customer_note: true });
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Adjust a variation's stock** — via the function: it re-derives `stock_status` and sends stock alerts.
|
|
115
|
+
```js
|
|
116
|
+
await base44.functions.invoke("commerce/admin-products", { action: "set-stock", id: product_id, variation_id, quantity: 12 });
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Edit a shipping location** — direct CRUD. Rate `id`s must be **stable** (carts and orders reference the chosen rate by id), so mint one per rate and keep it. Regions and matching: [`../references/shipping-and-tax.md`](../references/shipping-and-tax.md):
|
|
120
|
+
```js
|
|
121
|
+
const loc = await base44.entities["commerce.ShippingTaxLocation"].get(id);
|
|
122
|
+
await base44.entities["commerce.ShippingTaxLocation"].update(id, {
|
|
123
|
+
shipping_rates: [...loc.shipping_rates, { id: crypto.randomUUID(), name: "Express", cost: 25, free_over: null }],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Patch a settings group** — merge into `values`; replacing the object drops every other key in the group. Keys per group: [`../references/store-settings.md`](../references/store-settings.md):
|
|
128
|
+
```js
|
|
129
|
+
const [g] = await base44.entities["commerce.StoreSettings"].filter({ group_id: "products" }, undefined, 1);
|
|
130
|
+
await base44.entities["commerce.StoreSettings"].update(g.id, { values: { ...g.values, auto_approve_reviews: true } });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Where the rest lives
|
|
134
|
+
|
|
135
|
+
- A function's actions, payloads and error codes → [`api-admin.md`](./api-admin.md) · [`api-storefront.md`](./api-storefront.md)
|
|
136
|
+
- One entity's complete field list, types, enums, defaults → `base44/entities/commerce.<Name>.jsonc` (one read, never a directory scan)
|
|
137
|
+
- Seeding catalog / locations / payment methods in one call → [`api-admin.md#commerceseed-store`](./api-admin.md#commerceseed-store); worked example in [`../install/03-data.md`](../install/03-data.md)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
stage: install/01
|
|
3
|
+
read_when: "The commerce kit's files were just copied into the app, or you are installing it now."
|
|
4
|
+
skip_when: "The admin already mounts at /store-admin/* behind the shipped AuthGuard and / routes somewhere real."
|
|
5
|
+
forget_when: "The checklist at the bottom of this file passes (admin mounts, / routes somewhere real, /order-received exists)."
|
|
6
|
+
carry_forward:
|
|
7
|
+
- "Admin enforcement is three layers — AuthGuard (UI), admin-only entity RLS, requireAdmin() in every admin function. Never weaken any of them."
|
|
8
|
+
- "/order-received must exist as a route: every payment link returns there, and confirming is what marks an order paid."
|
|
9
|
+
- "Interleave: start image generation first → mount admin + build the storefront while images render → seed when the URLs are back → payments last."
|
|
10
|
+
- "Entities are dotted + bracket-syntax only (`base44.entities[\"commerce.X\"]`); the map is ../docs/entities.md — never scan base44/entities/."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# 01 — Install
|
|
14
|
+
|
|
15
|
+
What lands in the app, how the admin gets mounted, and the order to do the rest of the work in.
|
|
16
|
+
|
|
17
|
+
**Inside the Base44 runtime, writing a resource file *is* the deploy** — entities, functions and the `commerce/StoreAdmin` agent go live the moment the files exist. Nothing to push, no build step. If `scripts/install.js` ran, everything below is already in place.
|
|
18
|
+
|
|
19
|
+
| Copied to | What it is |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `base44/entities/commerce.*.jsonc` | 20 entity schemas, all admin-only RLS |
|
|
22
|
+
| `base44/functions/commerce/` + `shared/` + `agents/` | 16 functions, the engine, the StoreAdmin copilot |
|
|
23
|
+
| `src/commerce/admin/` | the finished admin app — **don't validate it, it ships tested** |
|
|
24
|
+
| `src/commerce/storefront/` + `utils/` | the headless hooks you build the storefront UI on ([`./02-storefront.md`](./02-storefront.md)) |
|
|
25
|
+
| `.agents/skills/commerce/` | these docs |
|
|
26
|
+
|
|
27
|
+
<details>
|
|
28
|
+
<summary>CLI path (outside the runtime only — as a Base44 agent, skip all of it)</summary>
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
node examples/commerce/scripts/install.js # copies the table above
|
|
32
|
+
npx base44 entities push && npx base44 functions deploy && npx base44 agents push
|
|
33
|
+
```
|
|
34
|
+
Confirm `base44/config.jsonc`'s `entitiesDir`/`functionsDir` point at those folders (the defaults do). Granting a user the `admin` role is an operator step, not yours.
|
|
35
|
+
</details>
|
|
36
|
+
|
|
37
|
+
**Dependencies.** Check `package.json` for `sonner`, `recharts` and `react-markdown`, and `npm i` **only** the ones actually absent — all three ship with the default Base44 template, so the normal outcome is installing nothing. Never re-install a package that is already a dependency. The kit needs no other dependency; verify the shadcn primitives listed in `src/commerce/admin/README.md` exist.
|
|
38
|
+
|
|
39
|
+
## Work order — interleave, don't queue
|
|
40
|
+
|
|
41
|
+
Image generation is the slowest step of the install and nothing depends on it until seed time. The storefront doesn't wait on live data either — every shape you build against is documented in [`./02-storefront.md`](./02-storefront.md). So:
|
|
42
|
+
|
|
43
|
+
1. **Start image generation first** — every product image, before anything else.
|
|
44
|
+
2. **Mount the admin (below) and build the storefront** while the images render.
|
|
45
|
+
3. **Seed the moment the image URLs are back** — one `commerce/seed-store` call ([`./03-data.md`](./03-data.md)); don't idle on it, pick the response (slugs) up when you need it.
|
|
46
|
+
4. **Converge**: open the finished pages against the live catalog.
|
|
47
|
+
5. **Payments last, if at all** — cards are off by default and nothing above depends on them ([`./03-data.md`](./03-data.md) decides it).
|
|
48
|
+
|
|
49
|
+
The only real dependency edges are *image URLs → seed payload* and *seed done → real products on the pages*. Everything else overlaps.
|
|
50
|
+
|
|
51
|
+
## Mount the admin router
|
|
52
|
+
|
|
53
|
+
```jsx
|
|
54
|
+
import AdminApp from "@/commerce/admin";
|
|
55
|
+
import { Navigate } from "react-router-dom";
|
|
56
|
+
|
|
57
|
+
<Route path="/store-admin/*" element={<AdminApp />} />
|
|
58
|
+
<Route path="/" element={<Navigate to="/store-admin" replace />} /> {/* until a storefront exists */}
|
|
59
|
+
<Route path="/order-received" element={<OrderReceived />} /> {/* mandatory — see below */}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **The `/*` splat is required.** The admin renders nested routes; a bare `path="/store-admin"` matches only the dashboard and every deeper link 404s. Mounting elsewhere: `<AdminApp basePath="/backoffice" />` — the prefix *without* the splat.
|
|
63
|
+
- **Give `/` something.** A blank Base44 app has no `/` route, so after mounting only the admin the app's own URL renders "page not found", which reads exactly like a broken install. Redirect until the storefront exists.
|
|
64
|
+
- **`/order-received` is mandatory**, even for a store that only ever takes offline payments. Every payment link (checkout, the admin's payment link, emails) returns there, and confirming is what marks an order paid — without the route a paying customer hits a 404 and the order stays unpaid. The page is one hook, `useOrderReturn()`, plus your markup for its states ([`./02-storefront.md`](./02-storefront.md)). A different path must be set in Settings → General → *Payment return path*.
|
|
65
|
+
|
|
66
|
+
## Admin-role enforcement — do not weaken
|
|
67
|
+
|
|
68
|
+
The shipped `AuthGuard` requires an authenticated user whose `role === "admin"`: not signed in → "Please sign in"; signed in without the role → "Admin access required". Grant it in the Base44 dashboard or with `base44.users.inviteUser(email, "admin")`.
|
|
69
|
+
|
|
70
|
+
That guard is the **first of three layers**, and all three are load-bearing:
|
|
71
|
+
|
|
72
|
+
1. **UI guard** — `AuthGuard` around every admin route (client-side; convenience and UX).
|
|
73
|
+
2. **Entity RLS** — every commerce entity carries `"user_condition": { "role": "admin" }` on all operations, so a direct SDK read or write by a non-admin is rejected by the backend.
|
|
74
|
+
3. **Function guard** — every `commerce/admin-*` function and `commerce/seed-store` calls `requireAdmin()` before touching data via the service role: **401** unauthenticated, **403** not an admin.
|
|
75
|
+
|
|
76
|
+
Bypassing the client guard therefore reaches nothing. Storefront functions are public on purpose and verify per action instead (auth session, `cart_token`, or `order_key`). The StoreAdmin agent has no entity tools and no service role for the same reason: its calls run with the chatting user's own credentials, so layer 3 still authorizes them.
|
|
77
|
+
|
|
78
|
+
Check the install at any point:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const { data } = (await base44.functions.invoke("commerce/admin-tools", { action: "status" })).data;
|
|
82
|
+
// → { template_version, seeded, settings_groups, counts: { "commerce.Product": n, … },
|
|
83
|
+
// checks: { has_payment_gateways, has_default_location } }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Done — forget this file
|
|
87
|
+
|
|
88
|
+
- [ ] `/store-admin/*` mounted with the splat, behind the shipped `AuthGuard`; the three enforcement layers untouched.
|
|
89
|
+
- [ ] `/` routes somewhere real — the storefront, or the redirect above.
|
|
90
|
+
- [ ] `/order-received` is a route in the app's `<Routes>`.
|
|
91
|
+
- [ ] `sonner` / `recharts` / `react-markdown` present (installed only if they were missing).
|
|
92
|
+
- [ ] Image generation is already running, or the store has no product images to make.
|
|
93
|
+
|
|
94
|
+
Then continue: **[`./02-storefront.md`](./02-storefront.md) when you start building UI**, **[`./03-data.md`](./03-data.md) when you start the seed payload**. Do **not** read them now, and do not open any `references/` or `docs/` file during an install — the stage files say when one is genuinely needed, and a file read early costs its size on every later call.
|
|
95
|
+
|
|
96
|
+
Record these lines in your working notes; do not re-read this file.
|
|
97
|
+
|
|
98
|
+
- Admin enforcement is three layers — AuthGuard (UI), admin-only entity RLS, requireAdmin() in every admin function. Never weaken any of them.
|
|
99
|
+
- `/order-received` must exist as a route: every payment link returns there, and confirming is what marks an order paid.
|
|
100
|
+
- Interleave: start image generation first → mount admin + build the storefront while images render → seed when the URLs are back → payments last.
|
|
101
|
+
- Entities are dotted + bracket-syntax only (`base44.entities["commerce.X"]`); the map is [`../docs/entities.md`](../docs/entities.md) — never scan `base44/entities/`.
|