@wix/agent-skills 1.16.3 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +134 -127
- package/skills/wix-app/references/SITE_PLUGIN.md +2 -2
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +182 -99
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +92 -48
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +15 -6
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +29 -42
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +153 -449
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +53 -29
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
- package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
- package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
- package/skills/wix-base44-connector/SKILL.md +34 -17
- package/skills/wix-base44-connector/scripts/utils.js +33 -25
- package/skills/wix-headless/entry/bootstrap.mjs +9 -2
- package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +42 -23
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +10 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +5 -0
- package/skills/wix-manage/SKILL.md +11 -1
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
- package/skills/wix-manage/references/seo/manage-seo-tags.md +315 -0
- package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
- package/skills/wix-manage/references/sites/query-sites.md +72 -161
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -9
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +10 -7
- package/skills/wix-vibe-headless/platforms/base44.md +13 -10
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +2 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +9 -8
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +35 -35
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// Wix docs helpers for the Base44 sandbox
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// pre-pointed at it.
|
|
1
|
+
// Wix docs helpers for the Base44 sandbox. Small results return inline; anything over
|
|
2
|
+
// ~4,000 chars (exec results clip at ~5,000) is saved under .agents/skills/wix-base44-connector/tmp and comes
|
|
3
|
+
// back as { path, bytes, lines, outline } with the read tools pre-pointed at it.
|
|
5
4
|
//
|
|
6
5
|
// Load per exec (execs share no state) — from disk, network only as first-touch fallback:
|
|
7
6
|
// const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
|
|
@@ -14,14 +13,13 @@
|
|
|
14
13
|
// to find, read_file(<path>) with offset/limit to window (numbered lines, 45K cap — no exec
|
|
15
14
|
// round needed), pipelines for the rest (GNU grep/sed; awk is mawk; no rg).
|
|
16
15
|
//
|
|
17
|
-
// API responses are site data and
|
|
18
|
-
// post() never saves — project responses to facts.
|
|
16
|
+
// API responses are site data and never saved — post() projects them to facts.
|
|
19
17
|
|
|
20
18
|
const fs = require("fs");
|
|
21
19
|
const path = require("path");
|
|
22
20
|
|
|
23
21
|
const BUDGET = 4000;
|
|
24
|
-
const SCRATCH = ".agents/skills/wix-base44-connector/
|
|
22
|
+
const SCRATCH = ".agents/skills/wix-base44-connector/tmp";
|
|
25
23
|
|
|
26
24
|
const clip = (out) => {
|
|
27
25
|
if (typeof out === "string")
|
|
@@ -48,7 +46,7 @@ async function post(url, body, token) {
|
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
function save(name, text) {
|
|
51
|
-
const dir =
|
|
49
|
+
const dir = SCRATCH;
|
|
52
50
|
fs.mkdirSync(dir, { recursive: true });
|
|
53
51
|
fs.writeFileSync(path.join(dir, name), text);
|
|
54
52
|
return { path: SCRATCH + "/" + name, bytes: Buffer.byteLength(text), lines: text.split("\n").length };
|
|
@@ -60,15 +58,18 @@ const outlineOf = (lines, cap = 30) => {
|
|
|
60
58
|
return { outline: heads.slice(0, cap), outlineOmitted: Math.max(0, heads.length - cap) };
|
|
61
59
|
};
|
|
62
60
|
|
|
63
|
-
// A ref is a
|
|
61
|
+
// A ref is a saved path or the URL it came from — resolve to lines, fetching+saving
|
|
64
62
|
// URLs on first touch (the .md suffix is appended for extensionless docs URLs).
|
|
65
|
-
|
|
63
|
+
// two path segments, not one: doc leaves repeat across products (every API has an
|
|
64
|
+
// introduction.md), and a one-segment name makes resolveRef's cache return the WRONG document
|
|
65
|
+
const slugOf = (url) => url.replace(/\?.*$/, "").replace(/\.md$/, "")
|
|
66
|
+
.split("/").filter(Boolean).slice(-2).join("-") + ".md";
|
|
66
67
|
async function resolveRef(ref) {
|
|
67
68
|
const isUrl = /^https?:/.test(ref);
|
|
68
69
|
const name = isUrl ? slugOf(ref) : ref.split("/").pop();
|
|
69
|
-
const file = path.join(
|
|
70
|
+
const file = path.join(SCRATCH, name);
|
|
70
71
|
if (fs.existsSync(file)) return { path: SCRATCH + "/" + name, lines: fs.readFileSync(file, "utf8").split("\n") };
|
|
71
|
-
if (!isUrl) throw new Error("no such
|
|
72
|
+
if (!isUrl) throw new Error("no such saved file: " + ref + " — pass a returned path or the source URL");
|
|
72
73
|
const mdUrl = /\.[a-z]{2,5}$/.test(ref.replace(/\?.*$/, "")) ? ref : ref.replace(/\?.*$/, "") + ".md";
|
|
73
74
|
const res = await fetch(mdUrl);
|
|
74
75
|
if (!res.ok) throw new Error(res.status + " on " + mdUrl + " — not a docs page; take URLs from output, don't compose");
|
|
@@ -109,7 +110,8 @@ async function browse(menuUrl, { include, filter, depth } = {}) {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
// Semantic search — ranks, never says "no match". The reduced hits come back inline
|
|
112
|
-
// AND the full raw content is saved for grep/window follow-ups.
|
|
113
|
+
// AND the full raw content is saved for grep/window follow-ups. { type } picks the portal:
|
|
114
|
+
// REST (default) · WIX_HEADLESS.
|
|
113
115
|
async function search(term, { type = "REST", max = 5, lines = 6 } = {}) {
|
|
114
116
|
const { content } = await post("https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown",
|
|
115
117
|
{ search_term: term, document_type: type, maximum_results: max, lines_in_each_result: lines });
|
|
@@ -146,7 +148,7 @@ async function page(url) {
|
|
|
146
148
|
|
|
147
149
|
// ── shell ─────────────────────────────────────────────────────────────────────
|
|
148
150
|
|
|
149
|
-
// Compose native pipelines over
|
|
151
|
+
// Compose native pipelines over .agents/skills/wix-base44-connector/tmp — grep -n, sed -n, mawk, sort, uniq, wc
|
|
150
152
|
// (GNU grep/sed; awk is mawk; no rg). Cap your own output (| head -40); the return
|
|
151
153
|
// clips regardless. grep's exit 1 means no match, not failure — the return says so.
|
|
152
154
|
function bash(cmd) {
|
|
@@ -165,29 +167,35 @@ function bash(cmd) {
|
|
|
165
167
|
|
|
166
168
|
// ── the spec index ────────────────────────────────────────────────────────────
|
|
167
169
|
|
|
168
|
-
//
|
|
169
|
-
// .
|
|
170
|
-
// publicUrl [callable], docsUrl)
|
|
171
|
-
//
|
|
172
|
-
//
|
|
170
|
+
// Inspect a located method's schema — request body, responses, enums, and the
|
|
171
|
+
// filterable-fields map. In scope: lightIndex (RESOURCES with .methods — operationId,
|
|
172
|
+
// summary, httpMethod, path [PARTIAL — never call it], publicUrl [callable], docsUrl)
|
|
173
|
+
// and getResourceSchemaByUrl(docsUrl) → s.methods (each with requestBody, responses,
|
|
174
|
+
// legacyExamples, queryMethodData.queryFieldsCapabilitiesMap; $circular via
|
|
175
|
+
// s.components.schemas). Arrive with a docsUrl from search/browse — not a discovery
|
|
176
|
+
// tool. A big result is saved as JSON; grep it for the keys you saw in its head.
|
|
173
177
|
async function spec(code) {
|
|
174
178
|
if (!/^\s*async function/.test(code)) code = `async function(){ ${code} }`;
|
|
175
179
|
const { result } = await post("https://mcp.wix.com/api/code-mode/search", { code });
|
|
176
180
|
if (result == null || (Array.isArray(result) && !result.length))
|
|
177
|
-
return { result, note: "empty — the query missed the shape
|
|
181
|
+
return { result, note: "empty — the query missed the shape; match your docsUrl against s.methods[].docsUrl" };
|
|
178
182
|
const text = JSON.stringify(result, null, 1);
|
|
179
183
|
if (text.length <= BUDGET) return result;
|
|
180
|
-
|
|
184
|
+
let h = 5381;
|
|
185
|
+
for (const ch of code) h = ((h * 33) ^ ch.charCodeAt(0)) >>> 0; // same query → same file
|
|
186
|
+
return { ...save("spec-" + h.toString(36) + ".json", text),
|
|
181
187
|
shape: Array.isArray(result) ? `Array(${result.length})` : Object.keys(result || {}).slice(0, 15),
|
|
182
188
|
head: text.slice(0, 600) };
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
// ── management recipes ────────────────────────────────────────────────────────
|
|
186
192
|
|
|
187
|
-
// ~100 curated multi-step admin flows
|
|
188
|
-
//
|
|
193
|
+
// ~100 curated multi-step MANAGEMENT (admin) flows across 23 categories — ecommerce,
|
|
194
|
+
// bookings, stores, cms, contacts, sites, get-paid, marketing, pricing-plans, events,
|
|
195
|
+
// blog, forms, restaurants, domains, media, … No arg → categories with counts; a
|
|
196
|
+
// category name → its recipes; any other term → search every recipe's name + gist.
|
|
189
197
|
// Read the chosen url with page(url), then grep/window/fields.
|
|
190
|
-
async function
|
|
198
|
+
async function mgmtRecipes(q) {
|
|
191
199
|
const { base, files } = await (await fetch("https://dev.wix.com/docs/skills/manage.manifest.json")).json();
|
|
192
200
|
const cat = f => (f.path.match(/^references\/([^/]+)\//) || [])[1];
|
|
193
201
|
const row = f => ({ name: f.name, cat: cat(f), gist: (f.description || "").slice(0, 120),
|
|
@@ -209,4 +217,4 @@ async function recipes(q) {
|
|
|
209
217
|
return clip(m.map(row));
|
|
210
218
|
}
|
|
211
219
|
|
|
212
|
-
module.exports = { post, clip, context, browse, search, page, bash, spec,
|
|
220
|
+
module.exports = { post, clip, context, browse, search, page, bash, spec, mgmtRecipes };
|
|
@@ -40,7 +40,14 @@ function checkCli() {
|
|
|
40
40
|
emit('cli_ok', { version });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// ── 2.
|
|
43
|
+
// ── 2. Reuse an existing session, or start device login ──────────────────────
|
|
44
|
+
function hasExistingSession() {
|
|
45
|
+
const r = capture(WIX[0], [...WIX.slice(1), 'whoami']);
|
|
46
|
+
if (r.status !== 0) return false;
|
|
47
|
+
emit('logged_in');
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
function login() {
|
|
45
52
|
return new Promise((resolve) => {
|
|
46
53
|
const child = spawn(WIX[0], [...WIX.slice(1), 'login'], { shell: isWin, env: AGENT_ENV });
|
|
@@ -90,4 +97,4 @@ function login() {
|
|
|
90
97
|
|
|
91
98
|
// ── main ────────────────────────────────────────────────────────────────────
|
|
92
99
|
checkCli();
|
|
93
|
-
await login();
|
|
100
|
+
if (!hasExistingSession()) await login();
|
|
@@ -68,7 +68,7 @@ Everything else the host resolves live from the queries in §3. (Static platform
|
|
|
68
68
|
- Frontend CMS access is **read**; visitor writes go through **Forms** submissions.
|
|
69
69
|
- **Blog comments are member-gated — read public, write authenticated.** Querying/rendering comments is public, so list them server-side in SSR. **Submitting** needs a logged-in member, so don't gate the form behind an upfront login check (that branch is what burns deliberation): render the form always and resolve identity at submit — POST to a backend endpoint (`src/pages/api/*.ts`) that calls `createComment` with the request session; if the caller isn't a member, redirect to the built-in `/api/auth/login?returnUrl=…`. **Do not build the comment form as a client island** — the API-endpoint + session path is the documented shape and avoids the browser-auth detour. The comment API keys (post `referenceId`, Blog appDefId, author lookup via `post.memberId`) are in `how-to-code-a-blog.md`.
|
|
70
70
|
- **Member auth is one mechanism split on the frontend axis (the §3 members note routes to the right recipe), and orthogonal to elevation.** Sign-up and log-in are the *same* flow (the Wix login page logs in **or** registers); log-out is its inverse. Two layers stay separate: **identity** (logged-in vs not — no app install) vs **profile** (name/photo/roles — needs the **Wix Members Area app** installed, `SETUP.md`). **pricing-plans is a hard dependency**: subscribing requires a logged-in member; for the other verticals, member login is a soft add-on for their "my …" surfaces only. **A member reading their own data (own orders/bookings/subscriptions, plan-gated content) uses the member token with NO `auth.elevate`** — elevation is the separate admin/permission axis (site-wide reads, server-side only), not something member features need.
|
|
71
|
-
- **Gating is a LIVE signal, never a hardcoded id list.** Whenever content is gated (members-only articles, plan-eligible class booking, any "premium" surface), decide eligibility from a signal read **at request time** — a queryable flag carried on the content (a `members-only` blog category/tag or a boolean CMS field) and live coverage/eligibility (`
|
|
71
|
+
- **Gating is a LIVE signal, never a hardcoded id list.** Whenever content is gated (members-only articles, plan-eligible class booking, any "premium" surface), decide eligibility from a signal read **at request time** — a queryable flag carried on the content (a `members-only` blog category/tag or a boolean CMS field) and live coverage/eligibility (Cart V2's `currentCartV2.calculateCurrentCart()` → `summary.paymentSummary.memberships`, or the member's active-order `planId`s matched against **live** Benefit-Program coverage). **Never** gate on a frozen set of "premium" slugs/ids or a seed-time `plan→service` map: a gated post or a newly-covered service the owner adds later would silently read as public/ineligible. See `how-to-code-a-blog.md` (member features) and `how-to-code-pricing-plans.md` (coverage read).
|
|
72
72
|
|
|
73
73
|
### 6 · What a complete site must include (per loaded capability)
|
|
74
74
|
For each loaded capability, carry its **Required site features** and **Implementation checklist** from `references/CAPABILITIES.md` into the guide — in plain product language, lightly tailored to what was seeded. **This is the build spec, not optional polish:** the host should build every *required feature* and cover every *checklist* item. For example, a blog must show the **author** (name + photo), the publish date and reading time, the cover image, and the **full formatted content** (not flattened text) — a posts-list-plus-plain-text-body is incomplete. The host maps these onto its own components using the packages/docs in §3 and the seeded schema in §4.
|
|
@@ -24,22 +24,24 @@ A concise contract for writing the **frontend code** of a storefront against a C
|
|
|
24
24
|
| Products (list, get, search, filter) | `@wix/stores` | `productsV3` |
|
|
25
25
|
| Variants (to resolve `variantId`) | `@wix/stores` | `readOnlyVariantsV3` |
|
|
26
26
|
| Categories | `@wix/stores` | `categories` |
|
|
27
|
-
| Cart (add / get / checkout) | `@wix/ecom` | `
|
|
27
|
+
| Cart (add / get / checkout) | `@wix/ecom` | `currentCartV2` |
|
|
28
28
|
| Redirect to hosted checkout | `@wix/redirects` | `redirects` |
|
|
29
29
|
|
|
30
|
+
> Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
|
|
31
|
+
|
|
30
32
|
**Never** import the V1 `products` or `collections` modules from `@wix/stores`.
|
|
31
33
|
|
|
32
34
|
**Auth / client — framework split:**
|
|
33
|
-
- **Astro (Wix-managed):** authentication is ambient. Call `
|
|
35
|
+
- **Astro (Wix-managed):** authentication is ambient. Call `currentCartV2` / `productsV3` / `readOnlyVariantsV3` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
|
|
34
36
|
- **Non-Astro (Vite/React/Vue/static):** build one manual visitor client and reuse it:
|
|
35
37
|
```js
|
|
36
38
|
import { createClient, OAuthStrategy } from '@wix/sdk';
|
|
37
39
|
import { productsV3, readOnlyVariantsV3 } from '@wix/stores';
|
|
38
|
-
import {
|
|
40
|
+
import { currentCartV2 } from '@wix/ecom';
|
|
39
41
|
import { redirects } from '@wix/redirects';
|
|
40
42
|
|
|
41
43
|
const client = createClient({
|
|
42
|
-
modules: { productsV3, readOnlyVariantsV3,
|
|
44
|
+
modules: { productsV3, readOnlyVariantsV3, currentCartV2, redirects },
|
|
43
45
|
auth: OAuthStrategy({ clientId: /* the project's PUBLIC OAuth client id */ }),
|
|
44
46
|
});
|
|
45
47
|
```
|
|
@@ -49,7 +51,7 @@ A concise contract for writing the **frontend code** of a storefront against a C
|
|
|
49
51
|
|
|
50
52
|
## The shapes you read (field cheat-sheet)
|
|
51
53
|
|
|
52
|
-
The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to
|
|
54
|
+
The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to request params (note the redirect session's `checkoutId`, which is just the cart's `_id`).
|
|
53
55
|
|
|
54
56
|
```jsonc
|
|
55
57
|
// productsV3.queryProducts().…find() → result.items[]
|
|
@@ -72,11 +74,12 @@ variant = {
|
|
|
72
74
|
inventoryStatus: { inStock }, // variant-level stock (boolean)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
//
|
|
76
|
-
lineItem = {
|
|
77
|
+
// currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: returns { cart } — destructure it
|
|
78
|
+
lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity }, pricing: { unitPrice: { amount } }, attributes: { image } }
|
|
79
|
+
// price → pricing.unitPrice (ConvertedMoney, NO formatted string in V2 — format it yourself; .amount is site currency, .convertedAmount the buyer's display currency); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve)
|
|
77
80
|
|
|
78
|
-
//
|
|
79
|
-
// redirects.createRedirectSession({ ecomCheckout: { checkoutId }, callbacks }) → { redirectSession: { fullUrl } }
|
|
81
|
+
// the cart's _id is the checkout id → pass to the redirect session:
|
|
82
|
+
// redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks }) → { redirectSession: { fullUrl } }
|
|
80
83
|
```
|
|
81
84
|
|
|
82
85
|
---
|
|
@@ -94,7 +97,7 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v
|
|
|
94
97
|
|
|
95
98
|
**⚠️ CRITICAL: the entity id is `_id`, NOT `id`.** The SDK normalizes every entity's id to **`_id`**. `product.id` is `undefined` in SDK code. This is the cart-killer: feeding `product.id` into the cart's `catalogItemId` sends an empty string and the add returns **HTTP 500** (`"catalogItemId" has size 0`). Use `product._id` everywhere — in links, as the cart `catalogItemId`, and as the variant-query filter value. (If a field name surprises you, you are probably reading the REST doc view — re-open it with `?apiView=SDK`.)
|
|
96
99
|
|
|
97
|
-
**Scope of the `_id` rule — entity reads only.** `_id` is the id of a read **entity** (product, variant, cart line item). It is **not** a universal "every id field is `_id`" rule:
|
|
100
|
+
**Scope of the `_id` rule — entity reads only.** `_id` is the id of a read **entity** (product, variant, cart line item). It is **not** a universal "every id field is `_id`" rule: request params name their own fields (e.g. the redirect session takes `ecomCheckout.checkoutId`, *not* `_id` — see the *Checkout* section below — even though the value you pass is the cart's `_id`). Don't assume every id-shaped field is spelled `_id`.
|
|
98
101
|
|
|
99
102
|
**Visibility:** only `visible: true` products are returned to a visitor token, so a missing product usually means it wasn't seeded visible — not a query bug.
|
|
100
103
|
|
|
@@ -149,11 +152,11 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v
|
|
|
149
152
|
|
|
150
153
|
Each `variant` carries `variant.optionChoices[].optionChoiceNames` — `{ optionName, choiceName }`. Match the buyer's selected options (Size = "Small", Color = "Red", …) against those names to pick the variant. For a **single-variant** product, use the only item. Fall back to `items[0]` if matching yields nothing. The id to send to the cart is **`variant.variantId ?? variant._id`**.
|
|
151
154
|
|
|
152
|
-
**2 · Add it.** Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/add-to-current-cart.md?apiView=SDK> · catalogReference contract: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md?apiView=SDK>
|
|
155
|
+
**2 · Add it.** Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md?apiView=SDK> · catalogReference contract: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md?apiView=SDK>
|
|
153
156
|
|
|
154
157
|
```js
|
|
155
|
-
await
|
|
156
|
-
|
|
158
|
+
await currentCartV2.addLineItemsToCurrentCart({
|
|
159
|
+
catalogItems: [{ // the write shape uses `catalogItems`
|
|
157
160
|
quantity,
|
|
158
161
|
catalogReference: {
|
|
159
162
|
catalogItemId: product._id, // the product's _id (the `_id` rule above)
|
|
@@ -164,28 +167,44 @@ await currentCart.addToCurrentCart({
|
|
|
164
167
|
});
|
|
165
168
|
```
|
|
166
169
|
|
|
167
|
-
**⚠️ CRITICAL: `options.variantId` is MANDATORY for any product that has variants.** Adding by `catalogItemId` alone
|
|
170
|
+
**⚠️ CRITICAL: `options.variantId` is MANDATORY for any product that has variants.** Adding by `catalogItemId` alone **fails** — the catalog can't resolve a variant-bearing product without it, and Cart V2 **rejects the add with an explicit error** rather than accepting an invalid line. The cart method's required-params list omits `variantId`, so it's an easy one to miss. Always resolve and include it (part 1 above).
|
|
168
171
|
|
|
169
|
-
**⚠️ CRITICAL: `options.options` is for MODIFIERS, not variant selection.** Product option selections (Size/Color) are resolved to a **variant** and referenced by `variantId`. `options.options` is only for free-text / TEXT_CHOICES add-on **modifiers**. Do **not** encode Size/Color as `options.options` — that is the coffee-grind bug
|
|
172
|
+
**⚠️ CRITICAL: `options.options` is for MODIFIERS, not variant selection.** Product option selections (Size/Color) are resolved to a **variant** and referenced by `variantId`. `options.options` is only for free-text / TEXT_CHOICES add-on **modifiers**. Do **not** encode Size/Color as `options.options` — that is the coffee-grind bug: the variant never resolves, so Cart V2 rejects the add with an explicit error.
|
|
170
173
|
|
|
171
|
-
### Checkout
|
|
174
|
+
### Checkout — redirect to the hosted checkout page
|
|
172
175
|
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
The cart's `_id` **is** the checkout id — pass it into the redirect session's `ecomCheckout.checkoutId`. Read the current cart, then hand its id to a redirect session, which carries the visitor/member session across to the hosted checkout on its own domain.
|
|
177
|
+
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md?apiView=SDK>
|
|
175
178
|
|
|
176
179
|
```js
|
|
177
|
-
const
|
|
180
|
+
const { cart } = await currentCartV2.getCurrentCart(); // NOTE: returns { cart } — destructure it
|
|
178
181
|
const session = await redirects.createRedirectSession({
|
|
179
|
-
ecomCheckout: { checkoutId:
|
|
182
|
+
ecomCheckout: { checkoutId: cart._id }, // the cart's _id IS the checkout id
|
|
180
183
|
callbacks: { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` },
|
|
181
184
|
});
|
|
182
185
|
window.location.href = session.redirectSession.fullUrl; // the hosted-checkout URL
|
|
183
186
|
```
|
|
184
187
|
|
|
185
|
-
**⚠️
|
|
188
|
+
**⚠️ The cart's `_id` is the checkout id.** Pass `cart._id` straight into the redirect session's `ecomCheckout.checkoutId`. And `getCurrentCart()` returns **`{ cart }`** — destructure it, or `cart` is `undefined` and `cart._id` throws *"Cannot read properties of undefined (reading '_id')"*.
|
|
186
189
|
|
|
187
190
|
**⚠️ CRITICAL: `origin` for `postFlowUrl`/`thankYouPageUrl` MUST be the `https://` published host — derive it from `window.location.origin`, NEVER `new URL(request.url).origin`.** The Headless redirect allowlist registers the site's **`https://`** host and treats **`http://<same host>` as a different, unlisted origin**. When the buyer returns from the hosted checkout (e.g. clicks "Continue Browsing"), the redirect goes through the allowlist — and an `http://` `postFlowUrl` **403s** with *"… isn't listed as an allowed redirect domain."* If you build the redirect session in a **server route** (`src/pages/api/*`), `new URL(request.url).origin` resolves to **`http://`** behind Wix's TLS-terminating proxy → guaranteed 403 on return. So **pass `window.location.origin` from the client** into the route (don't read the origin off the request), or force the scheme to `https`. Doc: <https://dev.wix.com/docs/go-headless/getting-started/setup/manage-urls/add-allowed-redirect-domains>.
|
|
188
191
|
|
|
192
|
+
### Formatting cart prices
|
|
193
|
+
|
|
194
|
+
**Product** prices from `productsV3` still carry a ready-to-show `actualPriceRange.minValue.formattedAmount` — use it directly. But **Cart V2 money does not**: every cart amount — line-item `pricing.unitPrice` / `pricing.totalPrice` **and** the `estimateCurrentCart`/`calculateCurrentCart` `summary.priceSummary.*` — is a `ConvertedMoney` `{ amount, convertedAmount }` with **no** formatted string. So once items are in the cart, you format the price yourself. The currency isn't on the money object; read it from the cart (`cart.customerInfo?.currencyCode ?? cart.businessInfo?.currencyCode`), and use `convertedAmount` (buyer's display currency) when present, else `amount` (site currency):
|
|
195
|
+
|
|
196
|
+
```js
|
|
197
|
+
function formatCartMoney(money, cart) {
|
|
198
|
+
const value = money?.convertedAmount ?? money?.amount;
|
|
199
|
+
const currency = cart?.customerInfo?.currencyCode ?? cart?.businessInfo?.currencyCode ?? 'USD';
|
|
200
|
+
return value == null ? '' : new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(Number(value));
|
|
201
|
+
}
|
|
202
|
+
// e.g. line item: formatCartMoney(item.pricing.totalPrice, cart)
|
|
203
|
+
// subtotal: formatCartMoney(estimate.summary.priceSummary.subtotal, cart)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Never hardcode `$` or assume USD — stores run in EUR/GBP too.
|
|
207
|
+
|
|
189
208
|
### Showing stock state
|
|
190
209
|
|
|
191
210
|
Read the **V3** inventory fields: product-level in-stock is `product.inventory.availabilityStatus` (`"IN_STOCK"`); variant-level is `variant.inventoryStatus.inStock`. Reading the V1 inventory field on V3 data returns `undefined` → everything renders out-of-stock (the all-OOS bug). These come from `productsV3` / `readOnlyVariantsV3`, not the V1 module.
|
|
@@ -208,7 +227,7 @@ function imgSrc(mediaMain, w = 600, h = 600) {
|
|
|
208
227
|
|
|
209
228
|
**Never hand-build a `static.wixstatic.com/.../v1/fit/...` URL** either — the format is easy to get wrong and the image then **403s**. Only `wix:image://` values need resolving; an already-absolute `https://` URL goes straight into `<img src>`. Doc: <https://dev.wix.com/docs/sdk/core-modules/sdk/media>
|
|
210
229
|
|
|
211
|
-
**This applies to cart line-item images too, not just product reads.** A cart `lineItem.image` is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before `<img src>`. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.)
|
|
230
|
+
**This applies to cart line-item images too, not just product reads.** A cart `lineItem.attributes.image` is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before `<img src>`. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.)
|
|
212
231
|
|
|
213
232
|
### Rendering product descriptions
|
|
214
233
|
|
|
@@ -230,7 +249,7 @@ Optional: render a `Product` schema.org JSON-LD `<script>` from the fetched prod
|
|
|
230
249
|
|
|
231
250
|
## Conclusion
|
|
232
251
|
A correct Catalog V3 storefront frontend:
|
|
233
|
-
- imports **`productsV3` / `readOnlyVariantsV3` / `categories` / `
|
|
252
|
+
- imports **`productsV3` / `readOnlyVariantsV3` / `categories` / `currentCartV2` / `redirects`** — never the V1 `products`/`collections` modules;
|
|
234
253
|
- uses **`product._id`** (never `product.id`) as the cart's `catalogItemId`;
|
|
235
254
|
- resolves the **mandatory `variantId`** via `readOnlyVariantsV3` and passes it as `options.variantId` (not `options.options`);
|
|
236
255
|
- builds its category nav from a **live `categories.queryCategories()`** and filters category pages server-side with **`searchProducts` + `$matchItems: [{ id: categoryId }]`** keyed on the live `categoryId` — never a frozen seed-time `productIds` map, never `queryProducts` for category filtering, never `$hasSome`, never V1 `collectionIds`;
|
|
@@ -23,7 +23,9 @@ A contract for the **frontend code** of a pricing-plans site: showing the plans
|
|
|
23
23
|
| List / read plans (the grid) | `@wix/pricing-plans` | `plansV3` (Plans V3 — `queryPlans`, `getPlan`) |
|
|
24
24
|
| Order a plan + read a member's orders | `@wix/pricing-plans` | `orders` (`createOnlineOrder`, `memberListOrders`, `memberGetOrder`) |
|
|
25
25
|
| Member login / current member | `@wix/members` + `@wix/sdk` auth | see the members recipe (`getCurrentMember`, `loggedIn()`) |
|
|
26
|
-
| Book a service with a membership (integration) | `@wix/bookings` + `@wix/ecom` (+ `@wix/redirects`) | `bookings` (`createBooking`), ecom `
|
|
26
|
+
| Book a service with a membership (integration) | `@wix/bookings` + `@wix/ecom` (+ `@wix/redirects`) | `bookings` (`createBooking`), ecom `currentCartV2` (line-item `membershipPayment`) — see *Booking with a membership* |
|
|
27
|
+
|
|
28
|
+
> Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
|
|
27
29
|
|
|
28
30
|
**Never** import `@wix/site-pricing-plans` in headless code, and don't reach for a V1 `plans`-collection query — Plans V3 (`queryPlans` → `pricingVariants`) is the shape the seed creates.
|
|
29
31
|
|
|
@@ -98,15 +100,15 @@ This is the payoff of the seed's STEP 2: a member who holds a plan that **covers
|
|
|
98
100
|
|
|
99
101
|
1. **Require login** — membership payment resolves against the member identity (a covered service booked anonymously just falls back to pay-per-booking).
|
|
100
102
|
2. **On `createBooking`, set `selectedPaymentOption: "MEMBERSHIP"`** (instead of `ONLINE`/`OFFLINE`). Enum values: `ONLINE`, `OFFLINE`, **`MEMBERSHIP`** (pay with a pricing plan), `MEMBERSHIP_OFFLINE`. Doc: <https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking.md?apiView=SDK>
|
|
101
|
-
3. **The booking still rides the ecom
|
|
102
|
-
- `
|
|
103
|
-
- apply
|
|
104
|
-
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/
|
|
103
|
+
3. **The booking still rides the ecom cart** (Bookings is an ecom catalog, appId `13d21c63-b5ec-5912-8397-c3a5ddb27a97`) — the membership is applied **on the cart line item**, not on the booking object. Set membership selection on the line item via `updateLineItemsInCurrentCart`:
|
|
104
|
+
- **Apply one** by setting the line item's `membershipPayment` via `currentCartV2.updateLineItemsInCurrentCart({ lineItems: [{ lineItemId: <booking line item id>, membershipPayment: { existingMembership: { membershipId, appId } } }] })` — `membershipId`/`appId` identify the member's existing plan membership (the Pricing Plans app); the line item reads it back as `paymentConfig.membership`. When the membership covers the full price, the calculated total drops to `0`.
|
|
105
|
+
- **Which plan to apply / eligibility:** the cart has **no** candidate-memberships read in V2 — `calculateCurrentCart` → `summary.paymentSummary.memberships` lists memberships **already applied**, not candidates — so which plan to apply comes from the member's plans + live coverage (see the LIVE-read note below). **⚠️ VERIFY IN A LIVE BUILD** which `appId`/`membershipId` a headless member token must pass.
|
|
106
|
+
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/update-line-items.md?apiView=SDK>
|
|
105
107
|
4. **⚠️ Do NOT call `confirmBooking`** — for a membership payment Wix **auto-confirms** the booking on redemption (the same "no confirmBooking" rule as `how-to-code-bookings.md`; the membership drives confirmation).
|
|
106
108
|
|
|
107
|
-
- **⚠️ Eligibility / coverage is a LIVE read, never a hardcoded plan→service map.** A new eligible plan, or a service newly added to a plan's coverage in the backoffice, **must be honored with no code change** — so never freeze the seed-time `{ planId → serviceIds }` map into the frontend.
|
|
109
|
+
- **⚠️ Eligibility / coverage is a LIVE read, never a hardcoded plan→service map.** A new eligible plan, or a service newly added to a plan's coverage in the backoffice, **must be honored with no code change** — so never freeze the seed-time `{ planId → serviceIds }` map into the frontend. Since Cart V2 has no per-line "eligible memberships" read, compute eligibility from the member's own plans against live coverage: list the member's active orders (`orders.memberListOrders`, `status: ACTIVE` within the date window) and match each `planId` against the plan's **live** Benefit-Program coverage (query the program's items — the same API `setup-pricing-plans.md` STEP 2 wrote to — at request time; do **not** read a coverage map baked into code). Set `selectedPaymentOption: "MEMBERSHIP"` on the booking + the line item's `membershipPayment.existingMembership` from the matched plan.
|
|
108
110
|
- **⚠️ A `MEMBERSHIP` booking on a service the plan does NOT cover fails at checkout** (unless `skipSelectedPaymentOptionValidation`, which needs elevation — don't use in a member flow). Only offer "book with membership" for services actually covered by one of the member's active plans; otherwise fall back to the normal paid booking (`how-to-code-bookings.md`).
|
|
109
|
-
- **Credit/session plans:** for a limited (non-unlimited) plan, `
|
|
111
|
+
- **Credit/session plans:** for a limited (non-unlimited) plan, don't pre-read a balance — apply the membership, then `calculateCurrentCart`/`estimateCurrentCart`. The summary lists the applied memberships (`summary.paymentSummary.memberships`), the covered line's `paymentConfig.membership` carries `redemptionCost` (balance deducted for this booking; `0` = unlimited) + `redemptionType` (`CREDITS`/`SESSIONS`), and an exhausted/over-limit plan surfaces **explicitly** as a `summary.violations` entry (or an error at place-order) — treat that as "not eligible." The ecom cart itself carries no remaining-balance read; the balance is owned by the plan's **Benefit-Program** domain (the same source as the coverage check above), so if you need the actual number read it there — not off the cart. An unlimited plan (seed `price:"0"`) has no ceiling.
|
|
110
112
|
|
|
111
113
|
### Out of scope (do not build these)
|
|
112
114
|
- **Completing/activating payment from code** — connecting a payments account, marking orders paid, forcing an order `ACTIVE`, "activating" a subscription server-side. Payment is the member's hosted-flow step or a merchant dashboard/config task, never frontend code (see the SCOPE callout under *Subscribing*).
|
|
@@ -121,4 +123,4 @@ A correct Pricing Plans frontend:
|
|
|
121
123
|
- imports **`@wix/pricing-plans`** (`plansV3`, `orders`) — **never** `@wix/site-pricing-plans` (its `startOnlinePurchase` is Wix-site page-code, not headless);
|
|
122
124
|
- lists the grid publicly with **`plansV3.queryPlans().eq('visibility','PUBLIC')`**, reads **`plan._id`** and price from **`pricingVariants[].pricingStrategies[].flatRate.amount`** (decimal string) — never a top-level `price` — and only shows a buy button on `buyable` plans;
|
|
123
125
|
- treats **subscribe and my-subscription as login-gated** (the hard members dependency), orders with **`orders.createOnlineOrder(planId)`** (no `onBehalf`, no elevate), renders free plans as `ACTIVE` immediately, and drives paid plans through a payment redirect (**exact headless redirect to be confirmed in a live build**) — and **stops at the redirect**: no payments-account connect, no order activation / mark-as-paid from code (see *Out of scope*);
|
|
124
|
-
- for the Bookings integration, books a covered service by setting **`selectedPaymentOption: "MEMBERSHIP"`** on `createBooking`, applies the membership on the **ecom
|
|
126
|
+
- for the Bookings integration, books a covered service by setting **`selectedPaymentOption: "MEMBERSHIP"`** on `createBooking`, applies the membership on the **ecom cart line item** (`membershipPayment.existingMembership` via `updateLineItemsInCurrentCart`), never calls `confirmBooking`, and falls back to matching the member's active-order `planId`s to the service's coverage when the cart eligibility field isn't readable client-side.
|
|
@@ -26,28 +26,30 @@ A contract for the **frontend code** that lets a visitor **order** from a Wix Re
|
|
|
26
26
|
| Menus / sections / items (display) | `@wix/restaurants` | `menus` / `sections` / `items` — see `how-to-code-restaurants.md` |
|
|
27
27
|
| The ordering operation (for `operationId`) | `@wix/restaurants` | `operations` |
|
|
28
28
|
| Fulfillment methods (pickup/delivery to show) | `@wix/restaurants` | `fulfillmentMethods` |
|
|
29
|
-
| Cart (add / get / checkout) | `@wix/ecom` | `
|
|
29
|
+
| Cart (add / get / checkout) | `@wix/ecom` | `currentCartV2` |
|
|
30
30
|
| Redirect to hosted checkout | `@wix/redirects` | `redirects` |
|
|
31
31
|
|
|
32
|
+
> Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
|
|
33
|
+
|
|
32
34
|
**Auth / client — framework split:**
|
|
33
|
-
- **Astro (Wix-managed):** authentication is ambient. Call `operations` / `fulfillmentMethods` / `
|
|
35
|
+
- **Astro (Wix-managed):** authentication is ambient. Call `operations` / `fulfillmentMethods` / `currentCartV2` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
|
|
34
36
|
```js
|
|
35
37
|
import { operations, fulfillmentMethods } from '@wix/restaurants';
|
|
36
|
-
import {
|
|
38
|
+
import { currentCartV2 } from '@wix/ecom';
|
|
37
39
|
const { operations: ops } = await operations.listOperations();
|
|
38
40
|
```
|
|
39
41
|
- **Non-Astro (Vite/React/Vue/static):** build one manual visitor client and reuse it:
|
|
40
42
|
```js
|
|
41
43
|
import { createClient, OAuthStrategy } from '@wix/sdk';
|
|
42
44
|
import { menus, sections, items, operations, fulfillmentMethods } from '@wix/restaurants';
|
|
43
|
-
import {
|
|
45
|
+
import { currentCartV2 } from '@wix/ecom';
|
|
44
46
|
import { redirects } from '@wix/redirects';
|
|
45
47
|
|
|
46
48
|
const client = createClient({
|
|
47
|
-
modules: { menus, sections, items, operations, fulfillmentMethods,
|
|
49
|
+
modules: { menus, sections, items, operations, fulfillmentMethods, currentCartV2, redirects },
|
|
48
50
|
auth: OAuthStrategy({ clientId: /* the project's PUBLIC OAuth client id */ }),
|
|
49
51
|
});
|
|
50
|
-
// then: await client.operations.listOperations(), await client.
|
|
52
|
+
// then: await client.operations.listOperations(), await client.currentCartV2.addLineItemsToCurrentCart({...})
|
|
51
53
|
```
|
|
52
54
|
The `clientId` is public, not a secret. A mis-wired public env var inlines as `undefined` and 400s every call.
|
|
53
55
|
|
|
@@ -55,7 +57,7 @@ A contract for the **frontend code** that lets a visitor **order** from a Wix Re
|
|
|
55
57
|
|
|
56
58
|
## The shapes you read (field cheat-sheet)
|
|
57
59
|
|
|
58
|
-
These are **SDK read shapes** (`?apiView=SDK`), so entity ids are **`_id`**. Prices/fees are decimal **strings**. The cart-add body (under *Adding a menu item to the cart*) is a separate **write** shape; the `_id` rule applies to read **entities**, not to
|
|
60
|
+
These are **SDK read shapes** (`?apiView=SDK`), so entity ids are **`_id`**. Prices/fees are decimal **strings**. The cart-add body (under *Adding a menu item to the cart*) is a separate **write** shape; the `_id` rule applies to read **entities**, not to request params (note the redirect session's `checkoutId`, which is just the cart's `_id`).
|
|
59
61
|
|
|
60
62
|
```jsonc
|
|
61
63
|
// operations.listOperations() → { operations: [...] } (no arguments)
|
|
@@ -79,11 +81,12 @@ fulfillmentMethod = {
|
|
|
79
81
|
// item (from items.listItems — see how-to-code-restaurants.md)
|
|
80
82
|
item = { _id, name, priceInfo: { price } } // price is a decimal STRING; _id → cart catalogItemId
|
|
81
83
|
|
|
82
|
-
//
|
|
83
|
-
lineItem = {
|
|
84
|
+
// currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: returns { cart } — destructure it
|
|
85
|
+
lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity }, pricing: { unitPrice: { amount } }, attributes: { image } }
|
|
86
|
+
// price → pricing.unitPrice (ConvertedMoney, NO formatted string in V2 — format it yourself; .amount is site currency, .convertedAmount the buyer's display currency); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve)
|
|
84
87
|
|
|
85
|
-
//
|
|
86
|
-
// redirects.createRedirectSession({ ecomCheckout: { checkoutId }, callbacks }) → { redirectSession: { fullUrl } }
|
|
88
|
+
// the cart's _id is the checkout id → pass to the redirect session:
|
|
89
|
+
// redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks }) → { redirectSession: { fullUrl } }
|
|
87
90
|
```
|
|
88
91
|
|
|
89
92
|
---
|
|
@@ -126,8 +129,8 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/onli
|
|
|
126
129
|
This is the one shape that differs materially from a Stores cart. Build a line item whose `catalogReference` names the **Orders app** and carries **`operationId` + `menuId` + `sectionId`** in `options`.
|
|
127
130
|
|
|
128
131
|
```js
|
|
129
|
-
await
|
|
130
|
-
|
|
132
|
+
await currentCartV2.addLineItemsToCurrentCart({
|
|
133
|
+
catalogItems: [{ // the write shape uses `catalogItems`
|
|
131
134
|
quantity,
|
|
132
135
|
catalogReference: {
|
|
133
136
|
catalogItemId: item._id, // the MENU ITEM's _id
|
|
@@ -141,9 +144,9 @@ await currentCart.addToCurrentCart({
|
|
|
141
144
|
}],
|
|
142
145
|
});
|
|
143
146
|
```
|
|
144
|
-
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/add-to-current-cart.md?apiView=SDK>
|
|
147
|
+
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md?apiView=SDK>
|
|
145
148
|
|
|
146
|
-
**⚠️ CRITICAL: `options` MUST carry `operationId`, `menuId`, AND `sectionId` — all three.** A restaurant line item is identified by *where in the menu it was ordered from*, not by a variant. Omitting any of the three makes eCommerce unable to resolve the item against the Restaurants catalog — the add
|
|
149
|
+
**⚠️ CRITICAL: `options` MUST carry `operationId`, `menuId`, AND `sectionId` — all three.** A restaurant line item is identified by *where in the menu it was ordered from*, not by a variant. Omitting any of the three makes eCommerce unable to resolve the item against the Restaurants catalog — Cart V2 rejects the add with an explicit error rather than accepting an invalid line. This is the restaurant analog of the store's mandatory `variantId`.
|
|
147
150
|
|
|
148
151
|
**⚠️ CRITICAL: there is NO `variantId` here.** `options.variantId` is a **Stores** concept — menu items have no variants in this flow. Don't copy the store recipe's `variantId` resolution; a restaurant line uses `operationId`/`menuId`/`sectionId` instead. (Menu item *modifiers* — "extra cheese" — are a separate concern and out of scope for the basic order flow.)
|
|
149
152
|
|
|
@@ -151,21 +154,21 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purch
|
|
|
151
154
|
|
|
152
155
|
Optional: `options.onlineOrderingPageUrl` (e.g. `"/online-ordering"`) lets the buyer click the cart line to return to the item — include it only if your site has such a page.
|
|
153
156
|
|
|
154
|
-
### Checkout
|
|
157
|
+
### Checkout — redirect to the hosted checkout page
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
The cart's `_id` **is** the checkout id — pass it into the redirect session's `ecomCheckout.checkoutId`. Read the current cart, then hand its id to a redirect session (identical to the storefront flow — restaurant orders ride on the same eCommerce checkout).
|
|
160
|
+
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md?apiView=SDK>
|
|
158
161
|
|
|
159
162
|
```js
|
|
160
|
-
const
|
|
163
|
+
const { cart } = await currentCartV2.getCurrentCart(); // NOTE: returns { cart } — destructure it
|
|
161
164
|
const session = await redirects.createRedirectSession({
|
|
162
|
-
ecomCheckout: { checkoutId:
|
|
165
|
+
ecomCheckout: { checkoutId: cart._id }, // the cart's _id IS the checkout id
|
|
163
166
|
callbacks: { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` },
|
|
164
167
|
});
|
|
165
168
|
window.location.href = session.redirectSession.fullUrl; // the hosted-checkout URL (fulfillment + time slot chosen here)
|
|
166
169
|
```
|
|
167
170
|
|
|
168
|
-
**⚠️ `
|
|
171
|
+
**⚠️ The cart's `_id` is the checkout id.** Pass `cart._id` into the redirect session's `ecomCheckout.checkoutId`. And `getCurrentCart()` returns **`{ cart }`** — destructure it, or `cart` is `undefined` and `cart._id` throws *"Cannot read properties of undefined (reading '_id')"*.
|
|
169
172
|
|
|
170
173
|
**⚠️ CRITICAL: `origin` for `postFlowUrl`/`thankYouPageUrl` MUST be the `https://` published host — derive it from `window.location.origin`, NEVER `new URL(request.url).origin`.** The Headless redirect allowlist registers the site's **`https://`** host and treats `http://<same host>` as a different, unlisted origin. If you build the redirect session in a **server route** (`src/pages/api/*`), `new URL(request.url).origin` resolves to `http://` behind Wix's TLS-terminating proxy → the buyer's return redirect **403s** with *"… isn't listed as an allowed redirect domain."* Pass `window.location.origin` from the client into the route (or force the scheme to `https`). Doc: <https://dev.wix.com/docs/go-headless/getting-started/setup/manage-urls/add-allowed-redirect-domains>.
|
|
171
174
|
|
|
@@ -173,16 +176,16 @@ window.location.href = session.redirectSession.fullUrl; // the hosted-checkout U
|
|
|
173
176
|
|
|
174
177
|
### Reading the price and the cart
|
|
175
178
|
|
|
176
|
-
Menu item price is **`item.priceInfo.price`** — a decimal **string** with no currency symbol (see `how-to-code-restaurants.md`; the SDK `PriceInfo` type has only `price`, no `formattedPrice`). A cart line's price is **`lineItem.
|
|
179
|
+
Menu item price is **`item.priceInfo.price`** — a decimal **string** with no currency symbol (see `how-to-code-restaurants.md`; the SDK `PriceInfo` type has only `price`, no `formattedPrice`). A cart line's price is **`lineItem.pricing.unitPrice`** / **`pricing.totalPrice`** — a `ConvertedMoney` `{ amount, convertedAmount }`; Cart V2 line items carry no preformatted price, so format it yourself. The currency lives on the cart, not the money object — format from `cart.customerInfo?.currencyCode ?? cart.businessInfo?.currencyCode` (see the *Formatting cart prices* section of `how-to-code-a-store.md` for the exact `Intl.NumberFormat` helper), e.g. `new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(Number(pricing.totalPrice.convertedAmount ?? pricing.totalPrice.amount))`. Cart line images are `wix:image://` identifiers — resolve them with `media.getScaledToFillImageUrl(id, w, h, {})` (4 args), never raw (see the store/menu recipes' image callout).
|
|
177
180
|
|
|
178
181
|
---
|
|
179
182
|
|
|
180
183
|
## Conclusion
|
|
181
184
|
A correct Restaurants ordering frontend:
|
|
182
|
-
- imports **`operations` / `fulfillmentMethods` from `@wix/restaurants`** (plus `menus`/`sections`/`items` for display) and **`
|
|
185
|
+
- imports **`operations` / `fulfillmentMethods` from `@wix/restaurants`** (plus `menus`/`sections`/`items` for display) and **`currentCartV2` / `redirects`** from `@wix/ecom` / `@wix/redirects`;
|
|
183
186
|
- uses **`_id`** (never `id`) for the operation, menu, section, and item;
|
|
184
187
|
- reads the **`operationId`** once from `listOperations()` (the `ENABLED`/`default` operation) and reuses it;
|
|
185
188
|
- adds menu items with the **Orders app id `9a5d83fd-…`** (never the Stores id) and `options` carrying **`operationId` + `menuId` + `sectionId`** (all three) — **no `variantId`**;
|
|
186
189
|
- threads `menuId`/`sectionId` from the render context, not a re-lookup;
|
|
187
|
-
- checks out
|
|
190
|
+
- checks out by feeding the **current cart's `_id`** (the checkout id) to **`redirects`**, with an **`https://` `window.location.origin`** for the callbacks; the hosted checkout collects fulfillment + payment (which needs a premium plan + payment method).
|
|
188
191
|
</content>
|
|
@@ -4,6 +4,11 @@ For a **managed** project (hosted on Wix infrastructure), every Wix call uses a
|
|
|
4
4
|
|
|
5
5
|
## 1 · Ensure an authenticated CLI session
|
|
6
6
|
|
|
7
|
+
### Secret boundary — mandatory
|
|
8
|
+
|
|
9
|
+
Never read authentication material or tokens into agent context.
|
|
10
|
+
Never emit them to stdout, stderr, logs, prompts, or tool results.
|
|
11
|
+
|
|
7
12
|
```bash
|
|
8
13
|
npx @wix/cli@latest whoami # exits 0 when logged in; non-zero when logged out
|
|
9
14
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wix-manage
|
|
3
|
-
description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, ricos rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, dashboard-navigation."
|
|
3
|
+
description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, ricos rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, seo, dashboard-navigation."
|
|
4
4
|
compatibility: Requires Wix REST API access (API key or OAuth).
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -34,6 +34,16 @@ These recipes do NOT cover frontend development or SDK usage for displaying data
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
+
## SEO
|
|
38
|
+
|
|
39
|
+
### [Manage a Wix Site's SEO Tags](references/seo/manage-seo-tags.md)
|
|
40
|
+
**Technical:** Reads and writes SEO tags (titles, descriptions, social tags, structured data, indexing) at three levels: site-wide, page-type patterns, and individual items. Always read before write — every Set replaces in full.
|
|
41
|
+
|
|
42
|
+
### [Manage URL Redirects on a Wix Site](references/seo/manage-url-redirects.md)
|
|
43
|
+
**Technical:** Reads, creates, and deletes URL redirects through the public SEO Redirects API. Distinguishes exact from group redirects, handles language-scoped redirects on multilingual sites, reads per-item results from bulk responses, and confirms before writes, because creating a redirect can permanently delete an existing one.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
37
47
|
## Accessibility
|
|
38
48
|
|
|
39
49
|
### [Scan a Wix Site for Accessibility Issues](references/accessibility/scan-site-accessibility.md)
|