@base44/app-plugin-commerce 0.2.1 → 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 +6 -6
- 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/storefront-catalog/entry.ts +1 -1
- package/base44/functions/commerce/storefront-checkout/entry.ts +1 -1
- package/base44/shared/commerce/card-payment.stripe.ts +29 -9
- 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 +1 -1
- package/skills/commerce/SKILL.md +36 -26
- package/skills/commerce/docs/api-storefront.md +6 -6
- package/skills/commerce/install/01-install.md +2 -2
- package/skills/commerce/install/02-storefront.md +355 -99
- package/skills/commerce/install/03-data.md +5 -5
- package/skills/commerce/references/catalog-rendering.md +6 -6
- package/skills/commerce/references/online-payments.md +5 -6
- package/skills/commerce/references/reviews.md +5 -5
- 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/index.js +45 -33
- package/src/commerce/storefront/useCartLine.js +37 -0
- package/src/commerce/storefront/useCheckout.jsx +18 -6
- package/src/commerce/storefront/useOrderReturn.js +36 -10
- package/src/commerce/storefront/useProduct.js +68 -0
- package/src/commerce/utils/index.js +9 -6
- package/src/commerce/utils/shipping-promos.js +2 -2
- package/src/commerce/utils/specs.js +26 -0
- package/src/commerce/utils/variants.js +49 -2
- package/src/commerce/storefront/blocks/AddToCartBlock.jsx +0 -86
- package/src/commerce/storefront/blocks/AddressFieldsBlock.jsx +0 -96
- package/src/commerce/storefront/blocks/BreadcrumbsBlock.jsx +0 -52
- package/src/commerce/storefront/blocks/CartLinesBlock.jsx +0 -98
- package/src/commerce/storefront/blocks/CheckoutBlock.jsx +0 -247
- package/src/commerce/storefront/blocks/CouponFieldBlock.jsx +0 -84
- package/src/commerce/storefront/blocks/OrderReceivedBlock.jsx +0 -129
- package/src/commerce/storefront/blocks/ProductGalleryBlock.jsx +0 -66
- package/src/commerce/storefront/blocks/ProductSpecsBlock.jsx +0 -33
- package/src/commerce/storefront/blocks/ProductStripBlock.jsx +0 -55
- package/src/commerce/storefront/blocks/QuantityStepper.jsx +0 -62
- package/src/commerce/storefront/blocks/ReviewsBlock.jsx +0 -191
- package/src/commerce/storefront/blocks/TotalsBlock.jsx +0 -42
- package/src/commerce/storefront/blocks/VariantSelectorBlock.jsx +0 -81
- package/src/commerce/storefront/blocks/index.js +0 -44
package/README.md
CHANGED
|
@@ -8,11 +8,11 @@ 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**. 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
|
|
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 used as-is — copy it over the stub and 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
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/`) — **
|
|
15
|
+
- **Storefront React layer** (`src/commerce/storefront/`) — **headless: the logic is premade, the UI never is.** Nothing in the layer renders markup or carries CSS; every element, class and word of copy in the storefront you build is yours, so a brief like "make it feel like <site>" applies to the whole store, checkout included. What ships is every piece of logic that is the same in all stores, as hooks returning complete view-models: `StorefrontProvider`, `useProductList`/`useCategories`/`useRibbons`, `useProduct`/`useAddToCart`/`useAddToCartButton`/`useProductPrice`/`useProductGallery` (+ the `variantAxes`/`productSpecs` render-model helpers), `useProductReviews`, `useCart`/`useCartLine`/`useCoupon`, `useCheckout` + `useAddressForm`/`useTotalsLines`/`useCheckoutBlockers`, `useOrderReturn`, and `useStorefrontSeo` — plus three render-prop components that stay just as headless (`ShippingMethodPicker`/`PaymentMethodPicker` for the two checkout choices that are store data, `CartLine` for per-row cart bindings). Each hook's doc comment states the render rules that keep a store correct (an unbuyable variant option renders disabled, not hidden; a receipt page must render `paymentInstructions`; …). Needs React and nothing else.
|
|
16
16
|
- **StoreAdmin agent + bot** — an AI copilot (`base44/agents/commerce/StoreAdmin.jsonc`, registered as `commerce/StoreAdmin`) with the `commerce/*` functions attached directly as tools (calls run as the chatting user → `requireAdmin()` still applies), variant-aware order editing, plus a chat panel in the admin sidebar with GFM markdown-table rendering.
|
|
17
17
|
- **Docs** — this README plus the commerce skill folder [`skills/commerce/`](./skills/commerce/): [`SKILL.md`](./skills/commerce/SKILL.md) is the map every agent starts from (and the only path the platform needs to know); [`install/`](./skills/commerce/install/) holds the three stage files that are the whole install (`01-install` → `02-storefront` → `03-data`, each read at the moment its work starts and dropped when its checklist passes); [`references/`](./skills/commerce/references/) holds per-topic guides opened only on demand; [`docs/`](./skills/commerce/docs/) holds the data-model map ([`entities.md`](./skills/commerce/docs/entities.md)) and the two API references. The whole folder is installed into the app at `.agents/skills/commerce/` so agents pick it up natively.
|
|
18
18
|
|
|
@@ -32,8 +32,8 @@ base44-commerce-template/
|
|
|
32
32
|
│ └── commerce/
|
|
33
33
|
│ ├── admin/ React admin UI (copy into your app's src/commerce/)
|
|
34
34
|
│ ├── utils/ storefront helpers — API client, variants, price/totals rules
|
|
35
|
-
│ └── storefront/ storefront React layer — hooks (cart, checkout,
|
|
36
|
-
│
|
|
35
|
+
│ └── storefront/ storefront React layer — headless hooks (cart, checkout,
|
|
36
|
+
│ catalog, reviews, SEO); no markup or CSS ships
|
|
37
37
|
├── scripts/
|
|
38
38
|
│ └── install.js static installer (run from <app>/examples/commerce/scripts/)
|
|
39
39
|
├── skills/
|
|
@@ -42,7 +42,7 @@ base44-commerce-template/
|
|
|
42
42
|
│ ├── SKILL.md the map: what to read, when, and what to skip
|
|
43
43
|
│ ├── install/ the whole install, in three staged files
|
|
44
44
|
│ │ ├── 01-install.md files, admin mount, role gating, the schedule
|
|
45
|
-
│ │ ├── 02-storefront.md storefront pages on the hooks
|
|
45
|
+
│ │ ├── 02-storefront.md storefront pages on the headless hooks
|
|
46
46
|
│ │ └── 03-data.md seeding, shipping zones, images, payments decision
|
|
47
47
|
│ ├── references/ opened on demand (catalog rendering, shipping & tax,
|
|
48
48
|
│ │ online payments, reviews, store settings, emails,
|
|
@@ -105,7 +105,7 @@ If you build on Base44's hosted platform, use the Base44 agent/MCP to write the
|
|
|
105
105
|
## What's NOT included
|
|
106
106
|
|
|
107
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
|
|
108
|
+
- **No payment provider — and cards are off by default.** The order side of card payments is premade (see above), but charging a card needs a provider, so `commerce/seed-store` enables the manual **`offline`** method (bank transfer, cash on delivery, pickup — no code, no credentials) and leaves the **`card`** gateway **switched off**. **Enable cards only if a provider is wired, or is about to be** — implement the four functions in `base44/shared/commerce/card-payment.ts`, or for Stripe copy the shipped `card-payment.stripe.ts` over it and use it as-is (`skills/commerce/references/online-payments.md`), then enable the gateway via the seed's `payment_methods: ["offline", "card"]`; enabled with nothing behind it, checkout answers `503 no_card_payment_provider`. The rule and why it belongs at the end of a build rather than its start: `skills/commerce/install/03-data.md`.
|
|
109
109
|
- **No scheduled workflows shipped.** Base44 *does* have a scheduler, but this template ships no workflow files — time-based jobs (stock-hold release, cart expiry, webhook-log pruning) run **opportunistically** where possible, and for the rest you (or the Base44 agent) create scheduled workflows that call `commerce/admin-tools`/`commerce/admin-orders` actions — see [`skills/commerce/references/operations.md`](./skills/commerce/references/operations.md).
|
|
110
110
|
|
|
111
111
|
## Next steps
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "StoreAdmin",
|
|
12
12
|
"description": "Store administration copilot for the commerce template: manage products, orders, refunds, coupons, customers, reviews, reports and maintenance.",
|
|
13
|
-
"instructions": "You are StoreAdmin, the store administration copilot for this shop's back office. You help store operators inspect and manage the store: products, orders, refunds, coupons, customers, reviews, webhooks, reports, and maintenance.\n\n## How you access the store\nEvery tool takes a JSON body of the form {\"action\": \"<action>\", ...payload} (exception: commerce/seed-store takes {store_name, currency?, weight_unit?, dimension_unit?, with_sample_data?, products?, coupons?, locations?} with no action key — store_name is REQUIRED on a first seed, and products can bootstrap a whole catalog in one call, variants and ribbons included; see
|
|
13
|
+
"instructions": "You are StoreAdmin, the store administration copilot for this shop's back office. You help store operators inspect and manage the store: products, orders, refunds, coupons, customers, reviews, webhooks, reports, and maintenance.\n\n## How you access the store\nEvery tool takes a JSON body of the form {\"action\": \"<action>\", ...payload} (exception: commerce/seed-store takes {store_name, currency?, weight_unit?, dimension_unit?, with_sample_data?, products?, coupons?, locations?} with no action key — store_name is REQUIRED on a first seed, and products can bootstrap a whole catalog in one call, variants and ribbons included; see the commerce skill's docs/api-admin.md) and responds {success, data} or {success:false, error, code}. Search/list actions return {rows, has_next} using limit+skip pagination (there are no total counts). Use search actions for free-text lookups (product name, customer email, coupon code, order number).\n\nStore configuration (settings, Shipping & Tax Locations, payment gateways, webhook definitions) is not editable through your tools — see \"Sending the operator to a screen\" below.\n\n## Sending the operator to a screen\nSome configuration is only editable in the admin UI. When one of those is asked for, say plainly that you cannot change it from chat, name the screen, and give a link the operator can click — never just \"do it manually\", and never imply you tried and failed. Do not blame permissions or a security error: the reason is simply that the UI is the only place that configuration is edited.\n\nLinks use the `admin:` scheme with a path from the table below — `[Settings → Shipping & Tax](admin:settings/shipping-tax)`. The chat resolves that to wherever the admin is mounted, so never write `/store-admin/...` yourself.\n\n| Ask | Screen | Link |\n|---|---|---|\n| Currency, measurement units, payment return path | Settings → General | admin:settings/general |\n| Stock thresholds, hold minutes | Settings → Inventory | admin:settings/inventory |\n| Locations, shipping rates, tax groups, shipping tax | Settings → Shipping & Tax | admin:settings/shipping-tax |\n| Enabling a gateway, offline bank details | Settings → Payments | admin:settings/payments |\n| Store name (subjects + sender), admin + stock notification recipients, per-email overrides | Settings → Emails | admin:settings/emails |\n| Auto-approve reviews toggle | Products → Reviews | admin:products/reviews |\n| Creating or deleting a webhook (you CAN test and redeliver) | Settings → Webhooks | admin:settings/webhooks |\n\nExample: \"Tax rates aren't something I can change from here — they live per location. Open [Settings → Shipping & Tax](admin:settings/shipping-tax), edit the location and add the rate, then tell me and I'll re-check the order's totals.\"\n\nCatalog taxonomy is the opposite: you CAN create categories, ribbons, attributes and attribute values yourself with commerce/admin-products save-term. Never send the operator to a screen to create one — link to admin:products/categories if they want to review the category list by hand. Ribbons and attributes have no screen of their own: attributes are edited in the Attributes section of a product's Price & Inventory section, ribbons in the Ribbons card of the product sidebar — so link to the product (admin:products) instead.\n\n## Product variants — be careful\nA product that carries attributes is sold through its variants (there is no product type), and every variant can differ in attributes (e.g. size/color), SKU, price and stock.\n- NEVER pick a variation automatically. When an order line, stock change, or download grant involves a product that carries attributes, first fetch its variations with commerce/storefront-catalog {\"action\":\"get-product\",\"id\":...} (returns {product, variations}), present them in a table (attributes, SKU, price, stock status), and ask the operator which variation to use — then include that variation_id in the item spec.\n- If the operator already named an exact variation (by SKU or full attribute combination), match it against the fetched variations and confirm the match in your reply; if the description is partial or matches more than one variation, ask.\n- Order item specs for commerce/admin-orders create/update are {product_id, variation_id?, quantity, price_override?} — variation_id is REQUIRED for a product with attributes.\n- The same applies to commerce/admin-products set-stock (pass variation_id to change a variation's stock, not the parent's).\n\n## Behavior\n- Be concise and operational. Confirm before destructive or irreversible operations (delete, refund, bulk-status, prune, clear-abandoned-carts) by restating what will happen and asking the user to confirm — unless the user's message already explicitly confirms it.\n- When showing lists or reports, format them as GitHub-flavored markdown tables (| col | col | with a |---| separator row). Keep tables ≤ 8 columns; prefer the most decision-relevant fields (name/number, status, total, date). Format money with the store currency.\n- After a mutation, report exactly what changed (ids, statuses, totals) and surface any error/code verbatim.\n- If a request is ambiguous (which order? which product?), search first and present the candidates in a table, then ask.\n- For store health questions, start with commerce/admin-tools {\"action\":\"status\"} and commerce/admin-reports {\"action\":\"summary\"}.\n- Payments: for an unpaid order paid online, commerce/payments create-link {order_id} gives a payment page link to send the customer, and verify {order_id} re-checks whether the money arrived. If no payment provider is connected, say \"no payment provider is connected\" and that connecting one enables card payments — don't name or troubleshoot a specific provider. Never invent a payment link or claim an order is paid without verifying.\n- You act with store-operator privileges; do not attempt to weaken or bypass access controls, and never expose secrets (webhook secrets, tokens).",
|
|
14
14
|
"tool_configs": [
|
|
15
15
|
{
|
|
16
16
|
"function_name": "commerce/admin-products",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"refunded_payment": {
|
|
22
22
|
"type": "boolean",
|
|
23
23
|
"default": false,
|
|
24
|
-
"description": "True when a real refund was made at the payment provider (via shared/commerce/card-payment.ts refundCardPayment — see
|
|
24
|
+
"description": "True when a real refund was made at the payment provider (via shared/commerce/card-payment.ts refundCardPayment — see the commerce skill's references/online-payments.md)"
|
|
25
25
|
},
|
|
26
26
|
"restock_items": {
|
|
27
27
|
"type": "boolean",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"settings": {
|
|
37
37
|
"type": "object",
|
|
38
|
-
"description": "Per-gateway settings. offline: {account_details: [{account_name, account_number, bank_name, sort_code, iban, bic}]}; card: {} — provider config lives in code and backend secrets, never here; see
|
|
38
|
+
"description": "Per-gateway settings. offline: {account_details: [{account_name, account_number, bank_name, sort_code, iban, bic}]}; card: {} — provider config lives in code and backend secrets, never here; see the commerce skill's references/online-payments.md"
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"required": ["slug"],
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"secret": {
|
|
36
36
|
"type": "string",
|
|
37
|
-
"description": "HMAC-SHA256 signing secret (see
|
|
37
|
+
"description": "HMAC-SHA256 signing secret (see the commerce skill's references/operations.md on storage tradeoffs)"
|
|
38
38
|
},
|
|
39
39
|
"api_version": {
|
|
40
40
|
"type": "string",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Actions: save | delete | batch | duplicate | set-stock | search |
|
|
6
6
|
* save-term | delete-term | list-terms
|
|
7
|
-
* Body: { action, ...payload } — see
|
|
7
|
+
* Body: { action, ...payload } — see the commerce skill's docs/api-admin.md.
|
|
8
8
|
*/
|
|
9
9
|
import { createClientFromRequest } from "npm:@base44/sdk";
|
|
10
10
|
import { HttpError, requireAdmin } from "../../../shared/commerce/auth.ts";
|
|
@@ -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
|
|
4
|
+
* (see the commerce skill's references/operations.md).
|
|
5
5
|
*
|
|
6
6
|
* Actions: summary | sales | top-sellers | stock |
|
|
7
7
|
* orders-totals | products-totals | customers-totals | coupons-totals |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* commerce/payments — online payment for an order.
|
|
3
3
|
*
|
|
4
|
-
* The provider specifics live in the two implementable files (see
|
|
5
|
-
*
|
|
4
|
+
* The provider specifics live in the two implementable files (see the commerce
|
|
5
|
+
* skill's references/online-payments.md); this function is
|
|
6
6
|
* the premade API the storefront and the admin use.
|
|
7
7
|
*
|
|
8
8
|
* Actions:
|
|
@@ -42,7 +42,7 @@ export const SETTINGS_DEFAULTS: Array<{ group_id: string; values: Record<string,
|
|
|
42
42
|
values: {
|
|
43
43
|
// The one review switch the server enforces; every other review policy
|
|
44
44
|
// (login-gated, verified-only, required rating) belongs to the storefront
|
|
45
|
-
// UI — see
|
|
45
|
+
// UI — see the commerce skill's references/reviews.md.
|
|
46
46
|
auto_approve_reviews: false,
|
|
47
47
|
},
|
|
48
48
|
},
|
|
@@ -375,7 +375,7 @@ async function getStoreInfo(sr: any): Promise<any> {
|
|
|
375
375
|
* signed-in session supplies the email (and can't spoof another); a guest
|
|
376
376
|
* passes `email` in the payload. Stricter policies (login-gated forms,
|
|
377
377
|
* verified-buyers-only, required ratings) are the storefront's to enforce in
|
|
378
|
-
* its UI — see
|
|
378
|
+
* its UI — see the commerce skill's references/reviews.md. The one server switch is
|
|
379
379
|
* auto-approval; everything else submits as `hold` for moderation.
|
|
380
380
|
*/
|
|
381
381
|
async function submitReview(sr: any, p: any, user: any): Promise<any> {
|
|
@@ -220,7 +220,7 @@ async function placeOrder(sr: any, req: Request, user: any, payload: any): Promi
|
|
|
220
220
|
// (4) customer upsert by billing email
|
|
221
221
|
const notices: string[] = [];
|
|
222
222
|
if (payload.create_account && !user) {
|
|
223
|
-
notices.push("account_creation_requires_login"); // see
|
|
223
|
+
notices.push("account_creation_requires_login"); // see the commerce skill's references/guest-access-security.md
|
|
224
224
|
}
|
|
225
225
|
const customer = await upsertCustomer(sr, billing, shippingAddress, user);
|
|
226
226
|
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
* "base44/shared/commerce/card-payment.ts",
|
|
12
12
|
* );
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* and **enable the `card` gateway** — `commerce/seed-store` with
|
|
15
|
+
* `payment_methods: ["offline", "card"]`. It ships disabled, so that is what
|
|
16
|
+
* makes card payment visible at checkout.
|
|
17
|
+
*
|
|
18
|
+
* That is the whole job. This file assumes the app is connected to Stripe and
|
|
19
|
+
* reads the secret key the connection publishes (`stripeSecret()` below); how
|
|
20
|
+
* the app gets connected is not this file's concern and nothing here needs
|
|
21
|
+
* filling in.
|
|
21
22
|
*
|
|
22
23
|
* NEVER patch this file (or the stub it replaces) with partial edits. A
|
|
23
24
|
* find_replace that leaves the originals behind gives every commerce function a
|
|
@@ -56,10 +57,29 @@ export interface CardPaymentPage {
|
|
|
56
57
|
* the secret answers a clean 503 at checkout instead of failing to boot every
|
|
57
58
|
* commerce function that imports it.
|
|
58
59
|
*/
|
|
60
|
+
/**
|
|
61
|
+
* Stripe's secret key, as published to the backend environment by the app's
|
|
62
|
+
* Stripe connection. The names below are the conventional ones; if the key
|
|
63
|
+
* arrives under a different name, this list is the only thing to change.
|
|
64
|
+
*/
|
|
65
|
+
const STRIPE_KEY_VARS = ["STRIPE_SECRET_KEY", "STRIPE_API_KEY", "STRIPE_KEY"];
|
|
66
|
+
|
|
67
|
+
const stripeSecret = () => {
|
|
68
|
+
for (const name of STRIPE_KEY_VARS) {
|
|
69
|
+
const value = Deno.env.get(name);
|
|
70
|
+
if (value) return value;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
|
|
59
75
|
const stripe = () => {
|
|
60
|
-
const key =
|
|
76
|
+
const key = stripeSecret();
|
|
61
77
|
if (!key) {
|
|
62
|
-
throw new HttpError(
|
|
78
|
+
throw new HttpError(
|
|
79
|
+
503,
|
|
80
|
+
`Card payments are not configured — no Stripe secret key in the backend environment (looked for ${STRIPE_KEY_VARS.join(", ")}).`,
|
|
81
|
+
"no_card_payment_provider",
|
|
82
|
+
);
|
|
63
83
|
}
|
|
64
84
|
return new Stripe(key);
|
|
65
85
|
};
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* NO PROVIDER SHIPS WITH THE TEMPLATE, and card payments are off by default
|
|
12
12
|
* (the `card` gateway is seeded disabled) — this file stays stubs unless a
|
|
13
13
|
* store deliberately opts into online cards. When one does, any provider
|
|
14
|
-
* works:
|
|
14
|
+
* works: the commerce skill's references/online-payments.md has the
|
|
15
15
|
* rules, plus a complete Stripe implementation to paste over this file if
|
|
16
16
|
* Stripe is the provider chosen. Another provider implements the same four
|
|
17
17
|
* functions against its own API.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* event). No provider ships with the template and the `card` gateway is
|
|
8
8
|
* seeded disabled, so this half is dormant until a store opts into online
|
|
9
9
|
* cards. Any provider works; the rules — and a complete Stripe
|
|
10
|
-
* implementation, for that one common choice — are in
|
|
11
|
-
*
|
|
10
|
+
* implementation, for that one common choice — are in the commerce skill's
|
|
11
|
+
* references/online-payments.md.
|
|
12
12
|
*
|
|
13
13
|
* Everything here — return URLs, storing the payment reference on the order,
|
|
14
14
|
* idempotent confirmation that moves the order to processing, refund routing —
|
|
@@ -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
|
|
5
|
+
* materialized stats entity (see the commerce skill's references/operations.md).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export interface ScanOpts {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
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
|
-
* is tiny and order ids (not numbers) are the primary key; see
|
|
7
|
-
*
|
|
6
|
+
* is tiny and order ids (not numbers) are the primary key; see the commerce
|
|
7
|
+
* skill's 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.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Base44 Commerce plugin — entities, backend functions, shared commerce engine, admin UI and the commerce skill, shipped as copyable source",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"base44",
|
package/scripts/install.js
CHANGED
|
@@ -203,7 +203,7 @@
|
|
|
203
203
|
" 1. No deps to add: sonner, recharts and react-markdown ship with the default\n" +
|
|
204
204
|
" Base44 template — check package.json and npm i only what is truly missing\n" +
|
|
205
205
|
' 2. Mount the admin router: <Route path="/store-admin/*" element={<AdminApp />} />\n' +
|
|
206
|
-
" plus the mandatory /order-received route (
|
|
206
|
+
" plus the mandatory /order-received route (useOrderReturn + your markup)\n" +
|
|
207
207
|
" 3. Seed the store — one commerce/seed-store call (store_name required) takes the\n" +
|
|
208
208
|
" catalog, currency, shipping locations and payment methods\n" +
|
|
209
209
|
" 4. CLI installs only: npx base44 agents push (the hosted runtime syncs agents on write)"
|
package/skills/commerce/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 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
|
|
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 headless storefront hooks 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
|
|
@@ -11,9 +11,19 @@ Nothing else in this skill needs to be open right now.
|
|
|
11
11
|
The kit ships, already written and tested: 20 `commerce.*` entities, the
|
|
12
12
|
`commerce/*` backend functions (storefront API, admin API, payments, webhooks,
|
|
13
13
|
emails), the shared commerce engine, a complete admin back office at
|
|
14
|
-
`/store-admin`, the `commerce/StoreAdmin` copilot, and the storefront's
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
`/store-admin`, the `commerce/StoreAdmin` copilot, and the storefront's entire
|
|
15
|
+
logic layer (`@/commerce/storefront`, `@/commerce/utils`). None of that costs
|
|
16
|
+
you generation — your work is the store's *UI and data*, not the plumbing.
|
|
17
|
+
|
|
18
|
+
**Paths in this skill.** Every skill file named anywhere — in these docs, in a
|
|
19
|
+
checklist, in a code comment — is written **relative to this skill's folder**
|
|
20
|
+
(`.agents/skills/commerce/` in an installed app). So `references/online-payments.md`
|
|
21
|
+
means `.agents/skills/commerce/references/online-payments.md`, and
|
|
22
|
+
`install/03-data.md` means `.agents/skills/commerce/install/03-data.md`. Resolve
|
|
23
|
+
them from the skill folder, never from the directory of the file that mentioned
|
|
24
|
+
them — a comment in `src/…` naming `install/03-data.md` is not
|
|
25
|
+
`src/install/03-data.md`. (Markdown links between skill files are ordinary
|
|
26
|
+
relative links and resolve as written.)
|
|
17
27
|
|
|
18
28
|
## Installing right now?
|
|
19
29
|
|
|
@@ -42,25 +52,25 @@ and each one says when a reference is genuinely needed.
|
|
|
42
52
|
step, and the decision plus the timing live in
|
|
43
53
|
[`install/03-data.md`](./install/03-data.md).
|
|
44
54
|
|
|
45
|
-
## The storefront:
|
|
55
|
+
## The storefront: logic is premade, UI never is
|
|
46
56
|
|
|
47
|
-
**
|
|
48
|
-
the
|
|
49
|
-
like "make it feel like <site>" lives, and the kit deliberately ships no
|
|
50
|
-
|
|
57
|
+
**The UI is yours, all of it** — every page, element, class and word of copy,
|
|
58
|
+
from the home page to the checkout's place-order button. That is where a brief
|
|
59
|
+
like "make it feel like <site>" lives, and the kit deliberately ships **no
|
|
60
|
+
markup and no CSS anywhere**: there are no premade components to drop in or
|
|
61
|
+
restyle. You design the storefront the way you would any app.
|
|
51
62
|
|
|
52
|
-
**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
**The logic ships as headless hooks** (`@/commerce/storefront`) — checkout
|
|
64
|
+
repricing from the address, variant resolution, cart state, coupon redemption,
|
|
65
|
+
review policies, order-return verification. Every store's version of these is
|
|
66
|
+
functionally identical and hand-writing them is where storefront bugs cluster,
|
|
67
|
+
so **never re-implement what a hook does**. Each hook returns a complete
|
|
68
|
+
view-model — a `status` to branch on, ready-to-map arrays, handlers, error
|
|
69
|
+
objects — that your markup renders:
|
|
59
70
|
|
|
60
71
|
```jsx
|
|
61
|
-
import {
|
|
62
|
-
//
|
|
63
|
-
<main className="mx-auto max-w-3xl px-6 py-16"><CheckoutBlock /></main>
|
|
72
|
+
import { useCart, CartLine, useTotalsLines } from "@/commerce/storefront";
|
|
73
|
+
const { status, lines } = useCart(); // branch on status, map lines into YOUR rows
|
|
64
74
|
```
|
|
65
75
|
|
|
66
76
|
The admin UI (`src/commerce/admin/`) is finished and **also yours to change** —
|
|
@@ -68,16 +78,16 @@ restyle it, add pages, rework flows. To understand the backend it talks to, read
|
|
|
68
78
|
[`docs/api-admin.md`](./docs/api-admin.md).
|
|
69
79
|
|
|
70
80
|
Four rules the API enforces — a storefront that skips them cannot complete a
|
|
71
|
-
purchase. In a React app the
|
|
72
|
-
|
|
81
|
+
purchase. In a React app the hooks implement all four; your markup just has to
|
|
82
|
+
render what they hand back. The API-level statements are in
|
|
73
83
|
[`docs/api-storefront.md`](./docs/api-storefront.md) for non-React clients:
|
|
74
84
|
|
|
75
85
|
1. A product with variants needs **one selector per attribute**, resolved to a
|
|
76
|
-
`variation_id` (
|
|
86
|
+
`variation_id` (`useProduct` + `variantAxes`).
|
|
77
87
|
2. Checkout must **recalculate shipping from the address and send a choice**
|
|
78
|
-
(
|
|
88
|
+
(`useCheckout` — automatic).
|
|
79
89
|
3. **`/order-received` must exist** and render the return state, including a
|
|
80
|
-
manual order's payment instructions (
|
|
90
|
+
manual order's payment instructions (`useOrderReturn`).
|
|
81
91
|
4. **Never advertise what isn't configured** — no free-shipping banner without a
|
|
82
92
|
real rate, no coupon codes without a field to redeem them in.
|
|
83
93
|
|
|
@@ -103,7 +113,7 @@ so you can answer "would that file help?" without paying for it.
|
|
|
103
113
|
| [`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
114
|
| [`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
115
|
| [`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 |
|
|
116
|
+
| [`references/reviews.md`](./references/reviews.md) | moderation, or a policy beyond the `policy` prop | `useProductReviews` covers list + form + policies | 4K |
|
|
107
117
|
| [`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
118
|
| [`references/emails.md`](./references/emails.md) | order-email recipients, subjects, per-type overrides, the log | transactional emails already send | 5K |
|
|
109
119
|
| [`references/admin-product-form.md`](./references/admin-product-form.md) | editing the shipped product editor | the editor works as shipped | 4K |
|
|
@@ -119,4 +129,4 @@ opened at the moment it is used. Open one reference when its task starts, take
|
|
|
119
129
|
what you need, and when a stage's checklist passes, record that stage's
|
|
120
130
|
carry-forward lines and treat the file as gone. If a reference and this map
|
|
121
131
|
disagree, the reference wins — but if a *rule* appears in code (a hook's return
|
|
122
|
-
value,
|
|
132
|
+
value, its doc comment), the code wins over both.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The raw HTTP/SDK surface behind a customer-facing shopfront. **A React storefront should not call most of it directly** — `src/commerce/storefront/` ships the logic layer, and this file is the reference for what lies beyond it, for non-React and headless clients, and for the exact payloads and error codes.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Logic is premade, UI never is.** In a React app, every surface here has a headless hook in `@/commerce/storefront` — `useProductList`, `useProduct` (+ `variantAxes`, `useAddToCartButton`), `useCart`/`useCartLine`/`useCoupon`, `useCheckout` (+ `useAddressForm`, the pickers), `useOrderReturn`, `useProductReviews`, `useStorefrontSeo`. The hooks own the API calls and the branching below and hand you a view-model; **all markup and styling are yours** — nothing in the kit renders UI. Never hand-roll a hook's logic. Framework-free helpers (API client, variant resolution, price and totals rules, free-shipping rules) live in `src/commerce/utils/`.
|
|
6
6
|
|
|
7
7
|
## Contents
|
|
8
8
|
|
|
@@ -49,7 +49,7 @@ Four public functions — **`storefront-catalog`**, **`storefront-cart`**, **`st
|
|
|
49
49
|
| **Digital products** | entitlement-checked downloads with remaining-count, expiry, signed URLs for private files | [`get-download`](#commercestorefront-account) |
|
|
50
50
|
| **Store config** | currency, units, catalog/cart price display — honour it instead of hardcoding | [`get-store-info`](#get-store-info) |
|
|
51
51
|
|
|
52
|
-
Not in the backend: the visitor UI (
|
|
52
|
+
Not in the backend: the visitor UI (all of it is yours — the hooks above carry the logic), and the card-payment provider integration, which only a store that opts into cards needs — cards are off by default, the decision and its timing live in [`../install/03-data.md`](../install/03-data.md), the provider code in [`../references/online-payments.md`](../references/online-payments.md). Payment methods and the currency are admin-owned data — always render them from `get-store-info`, never a hardcoded list, and format prices with `Intl.NumberFormat(undefined, { style: "currency", currency })`.
|
|
53
53
|
|
|
54
54
|
## Conventions
|
|
55
55
|
|
|
@@ -131,7 +131,7 @@ await cat({ category_id, sort: "popularity", per_page: 4 }); // top in catego
|
|
|
131
131
|
```
|
|
132
132
|
**Errors:** `404 not_found` (missing / not published / hidden).
|
|
133
133
|
|
|
134
|
-
> **`variations[]` is not a list of choices to show.** Build **one control per `product.attributes[]` entry** (Size, Color, …) — every attribute is an axis — and resolve the combination to a variation client-side; `add-item` needs that `variation_id`. Variant prices come from `variations[]`, never from `product.price` (the parent's price is a rolled-up from-price). A React page gets all of this from `useProduct` + `
|
|
134
|
+
> **`variations[]` is not a list of choices to show.** Build **one control per `product.attributes[]` entry** (Size, Color, …) — every attribute is an axis — and resolve the combination to a variation client-side; `add-item` needs that `variation_id`. Variant prices come from `variations[]`, never from `product.price` (the parent's price is a rolled-up from-price). A React page gets all of this from `useProduct` + `useAddToCartButton` (map `variantAxes(view, pick)` into your controls, gate on `view.purchasable`); a non-React client uses the framework-free resolver:
|
|
135
135
|
> ```js
|
|
136
136
|
> import { resolveSelection, defaultSelection, selectOption } from "@/commerce/utils";
|
|
137
137
|
> const view = resolveSelection(product, variations, selection); // selection from defaultSelection(), updated via selectOption()
|
|
@@ -159,7 +159,7 @@ No payload. Returns `{ "attributes": [ { ...attribute, "terms": [ ...values ] }
|
|
|
159
159
|
### `submit-review`
|
|
160
160
|
**Payload:** `{ product_id, reviewer?, review, rating }`.
|
|
161
161
|
|
|
162
|
-
> A React storefront gets the whole reviews surface — this call, the paginated list from `get-product`, refresh, and the store's moderation policy — from **`useProductReviews
|
|
162
|
+
> A React storefront gets the whole reviews surface — this call, the paginated list from `get-product`, refresh, and the store's moderation policy — from **`useProductReviews`**; don't hand-roll it. Read on for the raw contract.
|
|
163
163
|
|
|
164
164
|
**Public by default: anyone can review with an email address — no login.** A guest passes `email`; for a signed-in caller the session email always wins (the payload cannot impersonate). `reviewer` is the display name only, defaulting to the account's `full_name` then the email's local part. `rating` is optional (0–5). `verified` is derived from the email's order history. Status is `hold` unless `products.auto_approve_reviews` — the one server-side switch, so a hardcoded "awaiting approval" message is wrong when it is on. Stricter policies (login-gated, verified buyers only, rating required) are a `policy` option on the hook, not code you write: [`../references/reviews.md`](../references/reviews.md).
|
|
165
165
|
|
|
@@ -318,7 +318,7 @@ Separate function, same guest-bearer rule: `order_id` + `order_key` (an admin ma
|
|
|
318
318
|
| `complete-return` | `{ order_id, order_key, payment?, return_url? }` | `{ state: "paid"\|"cancelled"\|"unpaid", paid, already_confirmed, status, order, payment_link, payment_instructions }` — the whole return flow in one call: confirms with the provider, progresses the order, adds a fresh `payment_link: { url, reference }` while a card order is unpaid, and re-supplies `payment_instructions: { type, description, account_details }` for unpaid **manual** orders so bank details render on every visit. `payment` is only a hint — a hand-edited `?payment=success` can never yield `paid`. **`order` carries flat totals** (`order.total`, `order.shipping_total`); there is no `order.totals` — that shape belongs to the cart view |
|
|
319
319
|
| `verify` | `{ order_id, order_key }` | `{ paid, already_confirmed, status, order }` — the same confirmation without the render-ready extras. **Idempotent**; money is asked of the provider about the reference stored on the order |
|
|
320
320
|
|
|
321
|
-
> **The `/order-received` route is mandatory** (requirement 4): without it a paying customer lands on a 404 *and* the order is never marked paid. It only has to call `complete-return` and render its three states —
|
|
321
|
+
> **The `/order-received` route is mandatory** (requirement 4): without it a paying customer lands on a 404 *and* the order is never marked paid. It only has to call `complete-return` and render its three states — `useOrderReturn` does exactly that (your markup renders the states, including `paymentInstructions`).
|
|
322
322
|
|
|
323
323
|
Provider callbacks land on `commerce/payment-webhook` (server-to-server) — the second confirmation path, for buyers who pay and close the tab; whichever path runs second is a no-op. That function is premade (it verifies through the provider's API, so no signing secret). Wiring a provider: [`../references/online-payments.md`](../references/online-payments.md).
|
|
324
324
|
|
|
@@ -354,7 +354,7 @@ Two access modes: **auth** (Base44 session) or **`order_key` bearer** (guest tra
|
|
|
354
354
|
|
|
355
355
|
## Walkthrough A — guest checkout
|
|
356
356
|
|
|
357
|
-
Raw-call sequence for a non-React client (a React app gets this from `useCart` + `
|
|
357
|
+
Raw-call sequence for a non-React client (a React app gets this from `useCart` + `useCheckout`).
|
|
358
358
|
|
|
359
359
|
```js
|
|
360
360
|
const inv = (fn, payload) => base44.functions.invoke(fn, payload).then(r => r.data.data);
|
|
@@ -21,7 +21,7 @@ What lands in the app, how the admin gets mounted, and the order to do the rest
|
|
|
21
21
|
| `base44/entities/commerce.*.jsonc` | 20 entity schemas, all admin-only RLS |
|
|
22
22
|
| `base44/functions/commerce/` + `shared/` + `agents/` | 16 functions, the engine, the StoreAdmin copilot |
|
|
23
23
|
| `src/commerce/admin/` | the finished admin app — **don't validate it, it ships tested** |
|
|
24
|
-
| `src/commerce/storefront/` + `utils/` | the hooks
|
|
24
|
+
| `src/commerce/storefront/` + `utils/` | the headless hooks you build the storefront UI on ([`./02-storefront.md`](./02-storefront.md)) |
|
|
25
25
|
| `.agents/skills/commerce/` | these docs |
|
|
26
26
|
|
|
27
27
|
<details>
|
|
@@ -61,7 +61,7 @@ import { Navigate } from "react-router-dom";
|
|
|
61
61
|
|
|
62
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
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
|
|
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
65
|
|
|
66
66
|
## Admin-role enforcement — do not weaken
|
|
67
67
|
|