@jay-framework/wix-cart 0.18.3 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @jay-framework/wix-cart
2
+
3
+ Shared cart and checkout package for Jay Framework Wix integrations. Provides headless cart components with server-side rendering and client-side interactivity, plus checkout redirect to Wix's hosted checkout.
4
+
5
+ ## Features
6
+
7
+ - **Cart page** — Full cart with line items, quantity controls, coupon codes, and estimated totals
8
+ - **Cart indicator** — Reactive badge showing item count (updates automatically on add/remove)
9
+ - **Mini cart** — Compact cart drawer/modal
10
+ - **Checkout redirect** — Creates a checkout from the current cart and redirects to Wix's hosted checkout page
11
+ - **Three-phase rendering** — Slow (build), Fast (request/SSR), Interactive (client)
12
+ - **Shared across stores packages** — Used by both `@jay-framework/wix-stores` and `@jay-framework/wix-stores-v1`
13
+
14
+ ## Configuration
15
+
16
+ ### `config/.wix-cart.yaml`
17
+
18
+ Optional configuration file. Created automatically by `jay-stack-cli setup` if missing.
19
+
20
+ ```yaml
21
+ # Checkout callback URLs
22
+ urls:
23
+ thankYou: '/thank-you' # Where Wix redirects after successful checkout (default: /thank-you)
24
+ ```
25
+
26
+ ## Headless Components
27
+
28
+ ### Cart Page (`cart-page`)
29
+
30
+ Full shopping cart with line item management and checkout.
31
+
32
+ - **Slow phase**: Cart ID, empty cart message
33
+ - **Fast phase**: Loading state
34
+ - **Interactive**: Line items (quantity, remove), coupon input, checkout button
35
+
36
+ Key interactions:
37
+
38
+ - `checkoutButton` — Creates a Wix checkout and redirects to hosted checkout
39
+ - `continueShoppingLink` — Link back to products
40
+ - `clearCartButton` — Remove all items
41
+ - Quantity increment/decrement per line item
42
+ - Coupon apply/remove
43
+
44
+ ### Cart Indicator (`cart-indicator`)
45
+
46
+ Header badge showing cart item count.
47
+
48
+ - **Fast+Interactive**: `itemCount`, `hasItems`, `isLoading`, `justAdded`
49
+ - Reactive — updates automatically when items are added/removed from any page
50
+
51
+ ### Mini Cart (`mini-cart`)
52
+
53
+ Compact cart for drawer/modal display. Same data as cart-page in a condensed format.
54
+
55
+ ## Checkout Flow
56
+
57
+ The checkout button triggers a redirect to Wix's hosted checkout:
58
+
59
+ 1. `createCheckoutFromCurrentCart()` — creates a Wix checkout from the current cart
60
+ 2. `createRedirectSession({ ecomCheckout: { checkoutId } })` — gets a redirect URL
61
+ 3. Browser navigates to Wix checkout — payment, shipping, order review
62
+ 4. After completion, Wix redirects to the configured `thankYou` URL
63
+
64
+ No PCI compliance or payment integration needed — Wix handles everything.
65
+
66
+ ## Services & Contexts
67
+
68
+ ### `WIX_CART_SERVICE` (server)
69
+
70
+ Server-side cart operations using API key authentication.
71
+
72
+ ```typescript
73
+ import { WIX_CART_SERVICE } from '@jay-framework/wix-cart';
74
+
75
+ // Available in .withServices(WIX_CART_SERVICE)
76
+ interface WixCartService {
77
+ cart: CurrentCartClient; // @wix/ecom currentCart
78
+ redirects: RedirectsClient; // @wix/redirects
79
+ urls: { thankYou: string }; // Configured URLs
80
+ }
81
+ ```
82
+
83
+ ### `WIX_CART_CONTEXT` (client)
84
+
85
+ Client-side cart operations using OAuth authentication.
86
+
87
+ ```typescript
88
+ import { WIX_CART_CONTEXT } from '@jay-framework/wix-cart';
89
+
90
+ // Available in useContext(WIX_CART_CONTEXT)
91
+ interface WixCartContext {
92
+ cartIndicator: { itemCount: Getter<number>; hasItems: Getter<boolean> };
93
+ refreshCartIndicator(): Promise<void>;
94
+ getEstimatedCart(): Promise<CartState>;
95
+ addToCart(
96
+ productId: string,
97
+ quantity?: number,
98
+ options?: AddToCartOptions,
99
+ ): Promise<CartOperationResult>;
100
+ removeLineItems(lineItemIds: string[]): Promise<CartOperationResult>;
101
+ updateLineItemQuantity(lineItemId: string, quantity: number): Promise<CartOperationResult>;
102
+ clearCart(): Promise<void>;
103
+ applyCoupon(couponCode: string): Promise<CartOperationResult>;
104
+ removeCoupon(): Promise<CartOperationResult>;
105
+ checkout(): Promise<string>; // Returns Wix checkout redirect URL
106
+ }
107
+ ```
108
+
109
+ ## Dependencies
110
+
111
+ - `@wix/ecom` — Cart operations (currentCart)
112
+ - `@wix/redirects` — Checkout redirect sessions
113
+ - `@jay-framework/wix-server-client` — Wix SDK client and authentication
@@ -74,6 +74,7 @@ tags:
74
74
  type: data
75
75
  dataType: string
76
76
  description: Image URL
77
+ meta: {mediaType: wix-image}
77
78
 
78
79
  - tag: altText
79
80
  type: data
@@ -7,6 +7,7 @@ import { BuildDescriptors } from '@wix/sdk-types';
7
7
  import * as _jay_framework_fullstack_component from '@jay-framework/fullstack-component';
8
8
  import { Signals, PageProps } from '@jay-framework/fullstack-component';
9
9
  import * as _jay_framework_component from '@jay-framework/component';
10
+ import { redirects } from '@wix/redirects';
10
11
 
11
12
  /**
12
13
  * Cart Helper Types and Functions
@@ -138,6 +139,8 @@ declare function mapEstimateTotalsToState(estimate: EstimateTotalsResult | null)
138
139
  interface WixCartInitData {
139
140
  /** Enable client-side cart operations */
140
141
  enableClientCart: boolean;
142
+ /** URL path for post-checkout redirect (default: /thank-you) */
143
+ thankYouUrl: string;
141
144
  }
142
145
  /**
143
146
  * Reactive cart indicator state.
@@ -239,6 +242,11 @@ interface WixCartContext {
239
242
  * @returns Updated cart state
240
243
  */
241
244
  removeCoupon(): Promise<CartOperationResult>;
245
+ /**
246
+ * Create a Wix checkout redirect session and return the URL.
247
+ * Redirects the user to Wix's hosted checkout page.
248
+ */
249
+ checkout(): Promise<string>;
242
250
  onItemAddedToCart: EventEmitter<void, any>;
243
251
  }
244
252
  /**
@@ -253,7 +261,7 @@ declare const WIX_CART_CONTEXT: _jay_framework_runtime.ContextMarker<WixCartCont
253
261
  *
254
262
  * @returns The created context for immediate use (e.g., to call refreshCartIndicator)
255
263
  */
256
- declare function provideWixCartContext(): WixCartContext;
264
+ declare function provideWixCartContext(thankYouUrl?: string): WixCartContext;
257
265
 
258
266
  interface CartIndicatorViewState {
259
267
  itemCount: number,
@@ -276,6 +284,10 @@ interface CartIndicatorRefs {
276
284
 
277
285
  interface WixCartService {
278
286
  cart: BuildDescriptors<typeof currentCart, {}>;
287
+ redirects: BuildDescriptors<typeof redirects, {}>;
288
+ urls: {
289
+ thankYou: string;
290
+ };
279
291
  }
280
292
  /**
281
293
  * Server service marker for Wix Cart.
@@ -1,4 +1,4 @@
1
- export { A as AddToCartOptions, q as CartIndicatorState, n as CartLineItem, C as CartOperationResult, l as CartState, o as CartSummary, R as ReactiveCartIndicator, b as WIX_CART_CONTEXT, c as WixCartContext, r as cartIndicator, s as cartPage, u as init, t as miniCart } from './index.client-DWB0MOQ0.js';
1
+ export { A as AddToCartOptions, q as CartIndicatorState, n as CartLineItem, C as CartOperationResult, l as CartState, o as CartSummary, R as ReactiveCartIndicator, b as WIX_CART_CONTEXT, c as WixCartContext, r as cartIndicator, s as cartPage, u as init, t as miniCart } from './index.client-C7UwcsSn.js';
2
2
  import '@jay-framework/runtime';
3
3
  import '@jay-framework/reactive';
4
4
  import '@wix/ecom';
@@ -6,3 +6,4 @@ import '@wix/auto_sdk_ecom_current-cart';
6
6
  import '@wix/sdk-types';
7
7
  import '@jay-framework/fullstack-component';
8
8
  import '@jay-framework/component';
9
+ import '@wix/redirects';
@@ -11,6 +11,666 @@ function getCurrentCartClient(wixClient) {
11
11
  }
12
12
  return currentCartInstance;
13
13
  }
14
+ const SDKRequestToRESTRequestRenameMap = {
15
+ _id: "id",
16
+ _createdDate: "createdDate",
17
+ _updatedDate: "updatedDate"
18
+ };
19
+ const RESTResponseToSDKResponseRenameMap = {
20
+ id: "_id",
21
+ createdDate: "_createdDate",
22
+ updatedDate: "_updatedDate"
23
+ };
24
+ function renameAllNestedKeys(payload, renameMap, ignorePaths) {
25
+ const isIgnored = (path) => ignorePaths.includes(path);
26
+ const traverse = (obj, path) => {
27
+ if (Array.isArray(obj)) {
28
+ obj.forEach((item) => {
29
+ traverse(item, path);
30
+ });
31
+ } else if (typeof obj === "object" && obj !== null) {
32
+ const objAsRecord = obj;
33
+ Object.keys(objAsRecord).forEach((key) => {
34
+ const newPath = path === "" ? key : `${path}.${key}`;
35
+ if (isIgnored(newPath)) {
36
+ return;
37
+ }
38
+ const transformedKey = renameKey(key, renameMap);
39
+ if (transformedKey !== key && !(transformedKey in objAsRecord)) {
40
+ objAsRecord[transformedKey] = objAsRecord[key];
41
+ delete objAsRecord[key];
42
+ }
43
+ traverse(objAsRecord[transformedKey], newPath);
44
+ });
45
+ }
46
+ };
47
+ traverse(payload, "");
48
+ return payload;
49
+ }
50
+ function renameKey(key, renameMap) {
51
+ let transformedKey;
52
+ if (key.includes(".")) {
53
+ const parts = key.split(".");
54
+ const transformedParts = parts.map((part) => renameMap[part] ?? part);
55
+ transformedKey = transformedParts.join(".");
56
+ } else {
57
+ transformedKey = renameMap[key] ?? key;
58
+ }
59
+ return transformedKey;
60
+ }
61
+ function renameKeysFromSDKRequestToRESTRequest(payload, ignorePaths = []) {
62
+ return renameAllNestedKeys(payload, SDKRequestToRESTRequestRenameMap, ignorePaths);
63
+ }
64
+ function renameKeysFromRESTResponseToSDKResponse(payload, ignorePaths = []) {
65
+ return renameAllNestedKeys(payload, RESTResponseToSDKResponseRenameMap, ignorePaths);
66
+ }
67
+ function transformRESTTimestampToSDKTimestamp(val) {
68
+ return val ? new Date(val) : void 0;
69
+ }
70
+ function transformPath(obj, { path, isRepeated, isMap }, transformFn) {
71
+ const pathParts = path.split(".");
72
+ if (pathParts.length === 1 && path in obj) {
73
+ obj[path] = isRepeated ? obj[path].map(transformFn) : isMap ? Object.fromEntries(Object.entries(obj[path]).map(([key, value]) => [key, transformFn(value)])) : transformFn(obj[path]);
74
+ return obj;
75
+ }
76
+ const [first, ...rest] = pathParts;
77
+ if (first.endsWith("{}")) {
78
+ const cleanPath = first.slice(0, -2);
79
+ obj[cleanPath] = Object.fromEntries(Object.entries(obj[cleanPath]).map(([key, value]) => [
80
+ key,
81
+ transformPath(value, { path: rest.join("."), isRepeated, isMap }, transformFn)
82
+ ]));
83
+ } else if (Array.isArray(obj[first])) {
84
+ obj[first] = obj[first].map((item) => transformPath(item, { path: rest.join("."), isRepeated, isMap }, transformFn));
85
+ } else if (first in obj && typeof obj[first] === "object" && obj[first] !== null) {
86
+ obj[first] = transformPath(obj[first], { path: rest.join("."), isRepeated, isMap }, transformFn);
87
+ } else if (first === "*") {
88
+ Object.keys(obj).reduce((acc, curr) => {
89
+ acc[curr] = transformPath(obj[curr], { path: rest.join("."), isRepeated, isMap }, transformFn);
90
+ return acc;
91
+ }, obj);
92
+ }
93
+ return obj;
94
+ }
95
+ function transformPaths(obj, transformations) {
96
+ return transformations.reduce((acc, { paths, transformFn }) => paths.reduce((transformerAcc, path) => transformPath(transformerAcc, path, transformFn), acc), obj);
97
+ }
98
+ function EventDefinition(type, isDomainEvent = false, transformations = (x) => x) {
99
+ return () => ({
100
+ __type: "event-definition",
101
+ type,
102
+ isDomainEvent,
103
+ transformations
104
+ });
105
+ }
106
+ function constantCase(input) {
107
+ return split(input).map((part) => part.toLocaleUpperCase()).join("_");
108
+ }
109
+ const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu;
110
+ const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu;
111
+ const SPLIT_REPLACE_VALUE = "$1\0$2";
112
+ const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu;
113
+ function split(value) {
114
+ let result = value.trim();
115
+ result = result.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE).replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE);
116
+ result = result.replace(DEFAULT_STRIP_REGEXP, "\0");
117
+ let start = 0;
118
+ let end = result.length;
119
+ while (result.charAt(start) === "\0") {
120
+ start++;
121
+ }
122
+ if (start === end) {
123
+ return [];
124
+ }
125
+ while (result.charAt(end - 1) === "\0") {
126
+ end--;
127
+ }
128
+ return result.slice(start, end).split(/\0/g);
129
+ }
130
+ const isValidationError = (httpClientError) => "validationError" in (httpClientError.response?.data?.details ?? {});
131
+ const isApplicationError = (httpClientError) => "applicationError" in (httpClientError.response?.data?.details ?? {});
132
+ const isClientError = (httpClientError) => (httpClientError.response?.status ?? -1) >= 400 && (httpClientError.response?.status ?? -1) < 500;
133
+ function transformError(httpClientError, pathsToArguments = {
134
+ explicitPathsToArguments: {},
135
+ spreadPathsToArguments: {},
136
+ singleArgumentUnchanged: false
137
+ }, argumentNames = []) {
138
+ if (typeof httpClientError !== "object" || httpClientError === null) {
139
+ throw httpClientError;
140
+ }
141
+ if (isValidationError(httpClientError)) {
142
+ return buildValidationError(httpClientError, pathsToArguments, argumentNames);
143
+ }
144
+ if (isApplicationError(httpClientError)) {
145
+ return buildApplicationError(httpClientError);
146
+ }
147
+ if (isClientError(httpClientError)) {
148
+ const status = httpClientError.response?.status;
149
+ const statusText = httpClientError.response?.statusText ?? "UNKNOWN";
150
+ const message = httpClientError.response?.data?.message ?? statusText;
151
+ const details = {
152
+ applicationError: {
153
+ description: statusText,
154
+ code: constantCase(statusText),
155
+ data: {}
156
+ },
157
+ requestId: httpClientError.requestId
158
+ };
159
+ return wrapError(httpClientError, {
160
+ message: JSON.stringify({
161
+ message,
162
+ details
163
+ }, null, 2),
164
+ extraProperties: {
165
+ details,
166
+ status
167
+ }
168
+ });
169
+ }
170
+ return buildSystemError(httpClientError);
171
+ }
172
+ const buildValidationError = (httpClientError, pathsToArguments, argumentNames) => {
173
+ const validationErrorResponse = httpClientError.response?.data;
174
+ const requestId = httpClientError.requestId;
175
+ const { fieldViolations } = validationErrorResponse.details.validationError;
176
+ const transformedFieldViolations = violationsWithRenamedFields(pathsToArguments, fieldViolations, argumentNames)?.sort((a, b) => a.field < b.field ? -1 : 1);
177
+ const message = `INVALID_ARGUMENT: ${transformedFieldViolations?.map(({ field, description }) => `"${field}" ${description}`)?.join(", ")}`;
178
+ const details = {
179
+ validationError: { fieldViolations: transformedFieldViolations },
180
+ requestId
181
+ };
182
+ return wrapError(httpClientError, {
183
+ message: JSON.stringify({ message, details }, null, 2),
184
+ extraProperties: {
185
+ details,
186
+ status: httpClientError.response?.status,
187
+ requestId
188
+ }
189
+ });
190
+ };
191
+ const wrapError = (baseError, { message, extraProperties }) => {
192
+ return Object.assign(baseError, {
193
+ ...extraProperties,
194
+ message
195
+ });
196
+ };
197
+ const buildApplicationError = (httpClientError) => {
198
+ const status = httpClientError.response?.status;
199
+ const statusText = httpClientError.response?.statusText ?? "UNKNOWN";
200
+ const message = httpClientError.response?.data?.message ?? statusText;
201
+ const description = httpClientError.response?.data?.details?.applicationError?.description ?? statusText;
202
+ const code = httpClientError.response?.data?.details?.applicationError?.code ?? constantCase(statusText);
203
+ const data = httpClientError.response?.data?.details?.applicationError?.data ?? {};
204
+ const combinedMessage = message === description ? message : `${message}: ${description}`;
205
+ const details = {
206
+ applicationError: {
207
+ description,
208
+ code,
209
+ data
210
+ },
211
+ requestId: httpClientError.requestId
212
+ };
213
+ return wrapError(httpClientError, {
214
+ message: JSON.stringify({ message: combinedMessage, details }, null, 2),
215
+ extraProperties: {
216
+ details,
217
+ status,
218
+ requestId: httpClientError.requestId
219
+ }
220
+ });
221
+ };
222
+ const buildSystemError = (httpClientError) => {
223
+ const message = httpClientError.requestId ? `System error occurred, request-id: ${httpClientError.requestId}` : `System error occurred: ${JSON.stringify(httpClientError)}`;
224
+ return wrapError(httpClientError, {
225
+ message,
226
+ extraProperties: {
227
+ requestId: httpClientError.requestId,
228
+ status: httpClientError.response?.status,
229
+ code: constantCase(httpClientError.response?.statusText ?? "UNKNOWN"),
230
+ ...!httpClientError.response && {
231
+ runtimeError: httpClientError
232
+ }
233
+ }
234
+ });
235
+ };
236
+ const violationsWithRenamedFields = ({ spreadPathsToArguments, explicitPathsToArguments, singleArgumentUnchanged }, fieldViolations, argumentNames) => {
237
+ const allPathsToArguments = {
238
+ ...spreadPathsToArguments,
239
+ ...explicitPathsToArguments
240
+ };
241
+ const allPathsToArgumentsKeys = Object.keys(allPathsToArguments);
242
+ return fieldViolations?.filter((fieldViolation) => {
243
+ const containedInAMoreSpecificViolationField = fieldViolations.some((anotherViolation) => anotherViolation.field.length > fieldViolation.field.length && anotherViolation.field.startsWith(fieldViolation.field) && allPathsToArgumentsKeys.includes(anotherViolation.field));
244
+ return !containedInAMoreSpecificViolationField;
245
+ }).map((fieldViolation) => {
246
+ const exactMatchArgumentExpression = explicitPathsToArguments[fieldViolation.field];
247
+ if (exactMatchArgumentExpression) {
248
+ return {
249
+ ...fieldViolation,
250
+ field: withRenamedArgument(exactMatchArgumentExpression, argumentNames)
251
+ };
252
+ }
253
+ const longestPartialPathMatch = allPathsToArgumentsKeys?.sort((a, b) => b.length - a.length)?.find((path) => fieldViolation.field.startsWith(path));
254
+ if (longestPartialPathMatch) {
255
+ const partialMatchArgumentExpression = allPathsToArguments[longestPartialPathMatch];
256
+ if (partialMatchArgumentExpression) {
257
+ return {
258
+ ...fieldViolation,
259
+ field: fieldViolation.field.replace(longestPartialPathMatch, withRenamedArgument(partialMatchArgumentExpression, argumentNames))
260
+ };
261
+ }
262
+ }
263
+ if (singleArgumentUnchanged) {
264
+ return {
265
+ ...fieldViolation,
266
+ field: `${argumentNames[0]}.${fieldViolation.field}`
267
+ };
268
+ }
269
+ return fieldViolation;
270
+ });
271
+ };
272
+ const withRenamedArgument = (fieldValue, argumentNames) => {
273
+ const argIndex = getArgumentIndex(fieldValue);
274
+ if (argIndex !== null && typeof argIndex !== "undefined") {
275
+ return fieldValue.replace(`$[${argIndex}]`, argumentNames[argIndex]);
276
+ }
277
+ return fieldValue;
278
+ };
279
+ const getArgumentIndex = (s) => {
280
+ const match = s.match(/\$\[(?<argIndex>\d+)\]/);
281
+ return match && match.groups && Number(match.groups.argIndex);
282
+ };
283
+ var wixContext = {};
284
+ function resolveContext() {
285
+ const oldContext = typeof $wixContext !== "undefined" && $wixContext.initWixModules ? $wixContext.initWixModules : typeof globalThis.__wix_context__ !== "undefined" && globalThis.__wix_context__.initWixModules ? globalThis.__wix_context__.initWixModules : void 0;
286
+ if (oldContext) {
287
+ return {
288
+ // @ts-expect-error
289
+ initWixModules(modules, elevated) {
290
+ return runWithoutContext(() => oldContext(modules, elevated));
291
+ },
292
+ fetchWithAuth() {
293
+ throw new Error("fetchWithAuth is not available in this context");
294
+ },
295
+ graphql() {
296
+ throw new Error("graphql is not available in this context");
297
+ }
298
+ };
299
+ }
300
+ const contextualClient = typeof $wixContext !== "undefined" ? $wixContext.client : typeof wixContext.client !== "undefined" ? wixContext.client : typeof globalThis.__wix_context__ !== "undefined" ? globalThis.__wix_context__.client : void 0;
301
+ const elevatedClient = typeof $wixContext !== "undefined" ? $wixContext.elevatedClient : typeof wixContext.elevatedClient !== "undefined" ? wixContext.elevatedClient : typeof globalThis.__wix_context__ !== "undefined" ? globalThis.__wix_context__.elevatedClient : void 0;
302
+ if (!contextualClient && !elevatedClient) {
303
+ return;
304
+ }
305
+ return {
306
+ initWixModules(wixModules, elevated) {
307
+ if (elevated) {
308
+ if (!elevatedClient) {
309
+ throw new Error("An elevated client is required to use elevated modules. Make sure to initialize the Wix context with an elevated client before using elevated SDK modules");
310
+ }
311
+ return runWithoutContext(() => elevatedClient.use(wixModules));
312
+ }
313
+ if (!contextualClient) {
314
+ throw new Error("Wix context is not available. Make sure to initialize the Wix context before using SDK modules");
315
+ }
316
+ return runWithoutContext(() => contextualClient.use(wixModules));
317
+ },
318
+ fetchWithAuth: (urlOrRequest, requestInit) => {
319
+ if (!contextualClient) {
320
+ throw new Error("Wix context is not available. Make sure to initialize the Wix context before using SDK modules");
321
+ }
322
+ return contextualClient.fetchWithAuth(urlOrRequest, requestInit);
323
+ },
324
+ getAuth() {
325
+ if (!contextualClient) {
326
+ throw new Error("Wix context is not available. Make sure to initialize the Wix context before using SDK modules");
327
+ }
328
+ return contextualClient.auth;
329
+ },
330
+ async graphql(query, variables, opts) {
331
+ if (!contextualClient) {
332
+ throw new Error("Wix context is not available. Make sure to initialize the Wix context before using SDK modules");
333
+ }
334
+ return contextualClient.graphql(query, variables, opts);
335
+ }
336
+ };
337
+ }
338
+ function runWithoutContext(fn) {
339
+ const globalContext = globalThis.__wix_context__;
340
+ const moduleContext = {
341
+ client: wixContext.client,
342
+ elevatedClient: wixContext.elevatedClient
343
+ };
344
+ let closureContext;
345
+ globalThis.__wix_context__ = void 0;
346
+ wixContext.client = void 0;
347
+ wixContext.elevatedClient = void 0;
348
+ if (typeof $wixContext !== "undefined") {
349
+ closureContext = {
350
+ client: $wixContext?.client,
351
+ elevatedClient: $wixContext?.elevatedClient
352
+ };
353
+ delete $wixContext.client;
354
+ delete $wixContext.elevatedClient;
355
+ }
356
+ try {
357
+ return fn();
358
+ } finally {
359
+ globalThis.__wix_context__ = globalContext;
360
+ wixContext.client = moduleContext.client;
361
+ wixContext.elevatedClient = moduleContext.elevatedClient;
362
+ if (typeof $wixContext !== "undefined") {
363
+ $wixContext.client = closureContext.client;
364
+ $wixContext.elevatedClient = closureContext.elevatedClient;
365
+ }
366
+ }
367
+ }
368
+ function contextualizeRESTModuleV2(restModule, elevated) {
369
+ return (...args) => {
370
+ const context = resolveContext();
371
+ if (!context) {
372
+ return restModule.apply(void 0, args);
373
+ }
374
+ return context.initWixModules(restModule, elevated).apply(void 0, args);
375
+ };
376
+ }
377
+ function contextualizeEventDefinitionModuleV2(eventDefinition) {
378
+ const contextualMethod = (...args) => {
379
+ const context = resolveContext();
380
+ if (!context) {
381
+ return () => {
382
+ return {
383
+ slug: eventDefinition.type
384
+ };
385
+ };
386
+ }
387
+ return context.initWixModules(eventDefinition).apply(void 0, args);
388
+ };
389
+ contextualMethod.__type = eventDefinition.__type;
390
+ contextualMethod.type = eventDefinition.type;
391
+ contextualMethod.isDomainEvent = eventDefinition.isDomainEvent;
392
+ contextualMethod.transformations = eventDefinition.transformations;
393
+ return contextualMethod;
394
+ }
395
+ function createRESTModule(descriptor, elevated = false) {
396
+ return contextualizeRESTModuleV2(descriptor, elevated);
397
+ }
398
+ function resolveUrl(opts) {
399
+ const domain = resolveDomain(opts.host);
400
+ const mappings = resolveMappingsByDomain(domain, opts.domainToMappings);
401
+ const path = injectDataIntoProtoPath(opts.protoPath, opts.data || {});
402
+ return resolvePathFromMappings(path, mappings);
403
+ }
404
+ const DOMAINS = ["wix.com", "editorx.com"];
405
+ const USER_DOMAIN = "_";
406
+ const REGEX_CAPTURE_DOMAINS = new RegExp(`\\.(${DOMAINS.join("|")})$`);
407
+ const WIX_API_DOMAINS = ["42.wixprod.net", "uw2-edt-1.wixprod.net"];
408
+ const DEV_WIX_CODE_DOMAIN = "dev.wix-code.com";
409
+ const REGEX_CAPTURE_PROTO_FIELD = /{(.*)}/;
410
+ const REGEX_CAPTURE_API_DOMAINS = new RegExp(`\\.(${WIX_API_DOMAINS.join("|")})$`);
411
+ const REGEX_CAPTURE_DEV_WIX_CODE_DOMAIN = new RegExp(`.*\\.${DEV_WIX_CODE_DOMAIN}$`);
412
+ function resolveDomain(host) {
413
+ const resolvedHost = fixHostExceptions(host);
414
+ return resolvedHost.replace(REGEX_CAPTURE_DOMAINS, "._base_domain_").replace(REGEX_CAPTURE_API_DOMAINS, "._api_base_domain_").replace(REGEX_CAPTURE_DEV_WIX_CODE_DOMAIN, "*.dev.wix-code.com");
415
+ }
416
+ function fixHostExceptions(host) {
417
+ return host.replace("create.editorx.com", "editor.editorx.com");
418
+ }
419
+ function resolveMappingsByDomain(domain, domainToMappings) {
420
+ const mappings = domainToMappings[domain] || domainToMappings[USER_DOMAIN];
421
+ if (mappings) {
422
+ return mappings;
423
+ }
424
+ const rootDomainMappings = resolveRootDomain(domain, domainToMappings);
425
+ if (!rootDomainMappings) {
426
+ if (isBaseDomain(domain)) {
427
+ return domainToMappings[wwwBaseDomain];
428
+ }
429
+ }
430
+ return rootDomainMappings ?? [];
431
+ }
432
+ function resolveRootDomain(domain, domainToMappings) {
433
+ return Object.entries(domainToMappings).find(([entryDomain]) => {
434
+ const [, ...rooDomainSegments] = domain.split(".");
435
+ return rooDomainSegments.join(".") === entryDomain;
436
+ })?.[1];
437
+ }
438
+ function isBaseDomain(domain) {
439
+ return !!domain.match(/\._base_domain_$/);
440
+ }
441
+ const wwwBaseDomain = "www._base_domain_";
442
+ function injectDataIntoProtoPath(protoPath, data) {
443
+ return protoPath.split("/").map((path) => maybeProtoPathToData(path, data)).join("/");
444
+ }
445
+ function maybeProtoPathToData(protoPath, data) {
446
+ const protoRegExpMatch = protoPath.match(REGEX_CAPTURE_PROTO_FIELD) || [];
447
+ const field = protoRegExpMatch[1];
448
+ if (field) {
449
+ const suffix = protoPath.replace(protoRegExpMatch[0], "");
450
+ return findByPath(data, field, protoPath, suffix);
451
+ }
452
+ return protoPath;
453
+ }
454
+ function findByPath(obj, path, defaultValue, suffix) {
455
+ let result = obj;
456
+ for (const field of path.split(".")) {
457
+ if (!result) {
458
+ return defaultValue;
459
+ }
460
+ result = result[field];
461
+ }
462
+ return `${result}${suffix}`;
463
+ }
464
+ function resolvePathFromMappings(protoPath, mappings) {
465
+ const mapping = mappings?.find((m) => protoPath.startsWith(m.destPath));
466
+ if (!mapping) {
467
+ return protoPath;
468
+ }
469
+ return mapping.srcPath + protoPath.slice(mapping.destPath.length);
470
+ }
471
+ function createEventModule(eventDefinition) {
472
+ return contextualizeEventDefinitionModuleV2(eventDefinition);
473
+ }
474
+ function resolveWixHeadlessV1RedirectSessionServiceUrl(opts) {
475
+ const domainToMappings = {
476
+ "www._base_domain_": [
477
+ {
478
+ srcPath: "/_api/redirects-api",
479
+ destPath: ""
480
+ }
481
+ ],
482
+ "www.wixapis.com": [
483
+ {
484
+ srcPath: "/_api/redirects-api",
485
+ destPath: ""
486
+ },
487
+ {
488
+ srcPath: "/redirect-session",
489
+ destPath: ""
490
+ },
491
+ {
492
+ srcPath: "/headless/v1/redirect-session",
493
+ destPath: "/v1/redirect-session"
494
+ }
495
+ ],
496
+ "*.dev.wix-code.com": [
497
+ {
498
+ srcPath: "/headless/v1/redirect-session",
499
+ destPath: "/v1/redirect-session"
500
+ }
501
+ ],
502
+ _: [
503
+ {
504
+ srcPath: "/headless/v1/redirect-session",
505
+ destPath: "/v1/redirect-session"
506
+ }
507
+ ]
508
+ };
509
+ return resolveUrl(Object.assign(opts, { domainToMappings }));
510
+ }
511
+ var PACKAGE_NAME = "@wix/auto_sdk_redirects_redirects";
512
+ function createRedirectSession(payload) {
513
+ function __createRedirectSession({ host }) {
514
+ const metadata = {
515
+ entityFqdn: "wix.headless.v1.redirect_session",
516
+ method: "POST",
517
+ methodFqn: "wix.headless.v1.RedirectSessionService.CreateRedirectSession",
518
+ packageName: PACKAGE_NAME,
519
+ migrationOptions: {
520
+ optInTransformResponse: true
521
+ },
522
+ url: resolveWixHeadlessV1RedirectSessionServiceUrl({
523
+ protoPath: "/v1/redirect-session",
524
+ data: payload,
525
+ host
526
+ }),
527
+ data: payload
528
+ };
529
+ return metadata;
530
+ }
531
+ return __createRedirectSession;
532
+ }
533
+ var LocationType = /* @__PURE__ */ ((LocationType2) => {
534
+ LocationType2["UNDEFINED"] = "UNDEFINED";
535
+ LocationType2["OWNER_BUSINESS"] = "OWNER_BUSINESS";
536
+ LocationType2["OWNER_CUSTOM"] = "OWNER_CUSTOM";
537
+ LocationType2["CUSTOM"] = "CUSTOM";
538
+ return LocationType2;
539
+ })(LocationType || {});
540
+ var Prompt = /* @__PURE__ */ ((Prompt2) => {
541
+ Prompt2["login"] = "login";
542
+ Prompt2["none"] = "none";
543
+ Prompt2["consent"] = "consent";
544
+ Prompt2["select_account"] = "select_account";
545
+ return Prompt2;
546
+ })(Prompt || {});
547
+ var MembersAccountSection = /* @__PURE__ */ ((MembersAccountSection2) => {
548
+ MembersAccountSection2["ACCOUNT_INFO"] = "ACCOUNT_INFO";
549
+ MembersAccountSection2["BOOKINGS"] = "BOOKINGS";
550
+ MembersAccountSection2["ORDERS"] = "ORDERS";
551
+ MembersAccountSection2["SUBSCRIPTIONS"] = "SUBSCRIPTIONS";
552
+ MembersAccountSection2["EVENTS"] = "EVENTS";
553
+ return MembersAccountSection2;
554
+ })(MembersAccountSection || {});
555
+ var AttachPagesResponseStatus = /* @__PURE__ */ ((AttachPagesResponseStatus2) => {
556
+ AttachPagesResponseStatus2["UNKNOWN"] = "UNKNOWN";
557
+ AttachPagesResponseStatus2["SUCCESS"] = "SUCCESS";
558
+ AttachPagesResponseStatus2["NO_ACTION"] = "NO_ACTION";
559
+ AttachPagesResponseStatus2["ERROR"] = "ERROR";
560
+ return AttachPagesResponseStatus2;
561
+ })(AttachPagesResponseStatus || {});
562
+ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
563
+ CallbackType2["UNKNOWN"] = "UNKNOWN";
564
+ CallbackType2["LOGOUT"] = "LOGOUT";
565
+ CallbackType2["CHECKOUT"] = "CHECKOUT";
566
+ CallbackType2["AUTHORIZE"] = "AUTHORIZE";
567
+ return CallbackType2;
568
+ })(CallbackType || {});
569
+ var Status = /* @__PURE__ */ ((Status2) => {
570
+ Status2["UNKNOWN"] = "UNKNOWN";
571
+ Status2["SUCCESS"] = "SUCCESS";
572
+ Status2["ERROR"] = "ERROR";
573
+ return Status2;
574
+ })(Status || {});
575
+ var WebhookIdentityType = /* @__PURE__ */ ((WebhookIdentityType2) => {
576
+ WebhookIdentityType2["UNKNOWN"] = "UNKNOWN";
577
+ WebhookIdentityType2["ANONYMOUS_VISITOR"] = "ANONYMOUS_VISITOR";
578
+ WebhookIdentityType2["MEMBER"] = "MEMBER";
579
+ WebhookIdentityType2["WIX_USER"] = "WIX_USER";
580
+ WebhookIdentityType2["APP"] = "APP";
581
+ return WebhookIdentityType2;
582
+ })(WebhookIdentityType || {});
583
+ async function createRedirectSession2(options) {
584
+ const { httpClient, sideEffects } = arguments[1];
585
+ const payload = renameKeysFromSDKRequestToRESTRequest({
586
+ bookingsCheckout: options?.bookingsCheckout,
587
+ ecomCheckout: options?.ecomCheckout,
588
+ eventsCheckout: options?.eventsCheckout,
589
+ paidPlansCheckout: options?.paidPlansCheckout,
590
+ login: options?.login,
591
+ logout: options?.logout,
592
+ auth: options?.auth,
593
+ storesProduct: options?.storesProduct,
594
+ bookingsBook: options?.bookingsBook,
595
+ callbacks: options?.callbacks,
596
+ preferences: options?.preferences,
597
+ origin: options?.origin
598
+ });
599
+ const reqOpts = createRedirectSession(payload);
600
+ sideEffects?.onSiteCall?.();
601
+ try {
602
+ const result = await httpClient.request(reqOpts);
603
+ sideEffects?.onSuccess?.(result);
604
+ return renameKeysFromRESTResponseToSDKResponse(result.data);
605
+ } catch (err) {
606
+ const transformedError = transformError(
607
+ err,
608
+ {
609
+ spreadPathsToArguments: {},
610
+ explicitPathsToArguments: {
611
+ bookingsCheckout: "$[0].bookingsCheckout",
612
+ ecomCheckout: "$[0].ecomCheckout",
613
+ eventsCheckout: "$[0].eventsCheckout",
614
+ paidPlansCheckout: "$[0].paidPlansCheckout",
615
+ login: "$[0].login",
616
+ logout: "$[0].logout",
617
+ auth: "$[0].auth",
618
+ storesProduct: "$[0].storesProduct",
619
+ bookingsBook: "$[0].bookingsBook",
620
+ callbacks: "$[0].callbacks",
621
+ preferences: "$[0].preferences",
622
+ origin: "$[0].origin"
623
+ },
624
+ singleArgumentUnchanged: false
625
+ },
626
+ ["options"]
627
+ );
628
+ sideEffects?.onError?.(err);
629
+ throw transformedError;
630
+ }
631
+ }
632
+ function createRedirectSession3(httpClient) {
633
+ return (options) => createRedirectSession2(
634
+ options,
635
+ // @ts-ignore
636
+ { httpClient }
637
+ );
638
+ }
639
+ var onRedirectSessionCreated = EventDefinition(
640
+ "wix.headless.v1.redirect_session_created",
641
+ true,
642
+ (event) => renameKeysFromRESTResponseToSDKResponse(
643
+ transformPaths(event, [
644
+ {
645
+ transformFn: transformRESTTimestampToSDKTimestamp,
646
+ paths: [{ path: "metadata.eventTime" }]
647
+ }
648
+ ])
649
+ )
650
+ )();
651
+ var createRedirectSession4 = /* @__PURE__ */ createRESTModule(createRedirectSession3);
652
+ var onRedirectSessionCreated2 = createEventModule(
653
+ onRedirectSessionCreated
654
+ );
655
+ const redirects = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
656
+ __proto__: null,
657
+ AttachPagesResponseStatus,
658
+ CallbackType,
659
+ LocationType,
660
+ MembersAccountSection,
661
+ Prompt,
662
+ Status,
663
+ WebhookIdentityType,
664
+ createRedirectSession: createRedirectSession4,
665
+ onRedirectSessionCreated: onRedirectSessionCreated2
666
+ }, Symbol.toStringTag, { value: "Module" }));
667
+ let redirectsInstance;
668
+ function getRedirectsClient(wixClient) {
669
+ if (!redirectsInstance) {
670
+ redirectsInstance = wixClient.use(redirects);
671
+ }
672
+ return redirectsInstance;
673
+ }
14
674
  function parseWixMediaUrl(url) {
15
675
  if (!url) return null;
16
676
  const match = url.match(
@@ -252,10 +912,11 @@ function mapEstimateTotalsToState(estimate) {
252
912
  }
253
913
  const WIX_STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
254
914
  const WIX_CART_CONTEXT = createJayContext("wix:cart");
255
- function provideWixCartContext() {
915
+ function provideWixCartContext(thankYouUrl = "/thank-you") {
256
916
  const wixClientContext = useGlobalContext(WIX_CLIENT_CONTEXT);
257
917
  const wixClient = wixClientContext.client;
258
918
  const cartClient = getCurrentCartClient(wixClient);
919
+ const redirectsClient = getRedirectsClient(wixClient);
259
920
  const cartContext = registerReactiveGlobalContext(WIX_CART_CONTEXT, () => {
260
921
  const [itemCount, setItemCount] = createSignal(0);
261
922
  const [hasItems, setHasItems] = createSignal(false);
@@ -335,6 +996,19 @@ function provideWixCartContext() {
335
996
  updateIndicatorFromCart(result.cart ?? null);
336
997
  return { cartState: mapCartToState(result.cart ?? null) };
337
998
  }
999
+ async function checkout() {
1000
+ const { checkoutId } = await cartClient.createCheckoutFromCurrentCart({});
1001
+ if (!checkoutId) throw new Error("Failed to create checkout from cart");
1002
+ const postFlowUrl = window.location.origin + thankYouUrl;
1003
+ const { redirectSession } = await redirectsClient.createRedirectSession({
1004
+ ecomCheckout: { checkoutId },
1005
+ callbacks: { postFlowUrl }
1006
+ });
1007
+ if (!redirectSession?.fullUrl) {
1008
+ throw new Error("Failed to create checkout redirect session");
1009
+ }
1010
+ return redirectSession.fullUrl;
1011
+ }
338
1012
  return {
339
1013
  cartIndicator: {
340
1014
  itemCount,
@@ -348,6 +1022,7 @@ function provideWixCartContext() {
348
1022
  clearCart,
349
1023
  applyCoupon,
350
1024
  removeCoupon,
1025
+ checkout,
351
1026
  onItemAddedToCart
352
1027
  };
353
1028
  });
@@ -507,7 +1182,13 @@ function CartPageInteractive(_props, refs, viewStateSignals, _carryForward, cart
507
1182
  }
508
1183
  async function handleCheckout() {
509
1184
  setIsCheckingOut(true);
510
- window.location.href = "/checkout";
1185
+ try {
1186
+ const checkoutUrl = await cartContext.checkout();
1187
+ window.location.href = checkoutUrl;
1188
+ } catch (e) {
1189
+ console.error("[wix-cart] Checkout redirect failed:", e);
1190
+ setIsCheckingOut(false);
1191
+ }
511
1192
  }
512
1193
  refs.clearCartButton?.onclick(handleClearCart);
513
1194
  refs.checkoutButton?.onclick(handleCheckout);
@@ -567,8 +1248,8 @@ function MiniCartInteractive(_props, refs, viewStateSignals, _carryForward, cart
567
1248
  const miniCart = makeJayStackComponent().withProps().withContexts(WIX_CART_CONTEXT).withInteractive(MiniCartInteractive);
568
1249
  const init = makeJayInit().withClient(async (data) => {
569
1250
  console.log("[wix-cart] Initializing client-side cart context...");
570
- const { enableClientCart } = data;
571
- const cartContext = provideWixCartContext();
1251
+ const { enableClientCart, thankYouUrl } = data;
1252
+ const cartContext = provideWixCartContext(thankYouUrl);
572
1253
  if (enableClientCart) {
573
1254
  cartContext.refreshCartIndicator();
574
1255
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { WixClient } from '@wix/sdk';
2
- import { W as WixCartService } from './index.client-DWB0MOQ0.js';
3
- export { A as AddToCartOptions, q as CartIndicatorState, n as CartLineItem, C as CartOperationResult, l as CartState, o as CartSummary, R as ReactiveCartIndicator, b as WIX_CART_CONTEXT, a as WIX_CART_SERVICE, c as WixCartContext, d as WixCartInitData, r as cartIndicator, s as cartPage, j as estimateCurrentCartTotalsOrNull, i as getCurrentCartOrNull, h as getEmptyCartState, u as init, e as mapCartSummary, g as mapCartToIndicator, f as mapCartToState, k as mapEstimateTotalsToState, m as mapLineItem, t as miniCart, p as provideWixCartContext } from './index.client-DWB0MOQ0.js';
2
+ import { W as WixCartService } from './index.client-C7UwcsSn.js';
3
+ export { A as AddToCartOptions, q as CartIndicatorState, n as CartLineItem, C as CartOperationResult, l as CartState, o as CartSummary, R as ReactiveCartIndicator, b as WIX_CART_CONTEXT, a as WIX_CART_SERVICE, c as WixCartContext, d as WixCartInitData, r as cartIndicator, s as cartPage, j as estimateCurrentCartTotalsOrNull, i as getCurrentCartOrNull, h as getEmptyCartState, u as init, e as mapCartSummary, g as mapCartToIndicator, f as mapCartToState, k as mapEstimateTotalsToState, m as mapLineItem, t as miniCart, p as provideWixCartContext } from './index.client-C7UwcsSn.js';
4
4
  import { currentCart } from '@wix/ecom';
5
5
  import { BuildDescriptors } from '@wix/sdk-types';
6
6
  import '@jay-framework/runtime';
@@ -8,6 +8,7 @@ import '@jay-framework/reactive';
8
8
  import '@wix/auto_sdk_ecom_current-cart';
9
9
  import '@jay-framework/fullstack-component';
10
10
  import '@jay-framework/component';
11
+ import '@wix/redirects';
11
12
 
12
13
  /**
13
14
  * Wix Cart Client Factory
@@ -33,10 +34,15 @@ declare function getCurrentCartClient(wixClient: WixClient): BuildDescriptors<ty
33
34
  * This file contains server-only code (registerService).
34
35
  */
35
36
 
37
+ interface WixCartServiceOptions {
38
+ urls?: {
39
+ thankYou?: string;
40
+ };
41
+ }
36
42
  /**
37
43
  * Creates, registers, and returns a Wix Cart service instance.
38
44
  * Called during server initialization.
39
45
  */
40
- declare function provideWixCartService(wixClient: WixClient): WixCartService;
46
+ declare function provideWixCartService(wixClient: WixClient, options?: WixCartServiceOptions): WixCartService;
41
47
 
42
48
  export { WixCartService, getCurrentCartClient, provideWixCartService };
package/dist/index.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import { registerService, getService } from "@jay-framework/stack-server-runtime";
2
2
  import { currentCart } from "@wix/ecom";
3
+ import { redirects } from "@wix/redirects";
3
4
  import { createJayService, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
4
5
  import { createJayContext, useGlobalContext } from "@jay-framework/runtime";
5
6
  import { registerReactiveGlobalContext, createSignal, useReactive, createEvent } from "@jay-framework/component";
6
7
  import { WIX_CLIENT_CONTEXT, WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import * as yaml from "js-yaml";
7
11
  let currentCartInstance;
8
12
  function getCurrentCartClient(wixClient) {
9
13
  if (!currentCartInstance) {
@@ -11,10 +15,19 @@ function getCurrentCartClient(wixClient) {
11
15
  }
12
16
  return currentCartInstance;
13
17
  }
18
+ let redirectsInstance;
19
+ function getRedirectsClient(wixClient) {
20
+ if (!redirectsInstance) {
21
+ redirectsInstance = wixClient.use(redirects);
22
+ }
23
+ return redirectsInstance;
24
+ }
14
25
  const WIX_CART_SERVICE = createJayService("Wix Cart Service");
15
- function provideWixCartService(wixClient) {
26
+ function provideWixCartService(wixClient, options) {
16
27
  const service = {
17
- cart: getCurrentCartClient(wixClient)
28
+ cart: getCurrentCartClient(wixClient),
29
+ redirects: getRedirectsClient(wixClient),
30
+ urls: { thankYou: options?.urls?.thankYou || "/thank-you" }
18
31
  };
19
32
  registerService(WIX_CART_SERVICE, service);
20
33
  return service;
@@ -260,10 +273,11 @@ function mapEstimateTotalsToState(estimate) {
260
273
  }
261
274
  const WIX_STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
262
275
  const WIX_CART_CONTEXT = createJayContext("wix:cart");
263
- function provideWixCartContext() {
276
+ function provideWixCartContext(thankYouUrl = "/thank-you") {
264
277
  const wixClientContext = useGlobalContext(WIX_CLIENT_CONTEXT);
265
278
  const wixClient = wixClientContext.client;
266
279
  const cartClient = getCurrentCartClient(wixClient);
280
+ const redirectsClient = getRedirectsClient(wixClient);
267
281
  const cartContext = registerReactiveGlobalContext(WIX_CART_CONTEXT, () => {
268
282
  const [itemCount, setItemCount] = createSignal(0);
269
283
  const [hasItems, setHasItems] = createSignal(false);
@@ -343,6 +357,19 @@ function provideWixCartContext() {
343
357
  updateIndicatorFromCart(result.cart ?? null);
344
358
  return { cartState: mapCartToState(result.cart ?? null) };
345
359
  }
360
+ async function checkout() {
361
+ const { checkoutId } = await cartClient.createCheckoutFromCurrentCart({});
362
+ if (!checkoutId) throw new Error("Failed to create checkout from cart");
363
+ const postFlowUrl = window.location.origin + thankYouUrl;
364
+ const { redirectSession } = await redirectsClient.createRedirectSession({
365
+ ecomCheckout: { checkoutId },
366
+ callbacks: { postFlowUrl }
367
+ });
368
+ if (!redirectSession?.fullUrl) {
369
+ throw new Error("Failed to create checkout redirect session");
370
+ }
371
+ return redirectSession.fullUrl;
372
+ }
346
373
  return {
347
374
  cartIndicator: {
348
375
  itemCount,
@@ -356,6 +383,7 @@ function provideWixCartContext() {
356
383
  clearCart,
357
384
  applyCoupon,
358
385
  removeCoupon,
386
+ checkout,
359
387
  onItemAddedToCart
360
388
  };
361
389
  });
@@ -429,13 +457,32 @@ async function renderFastChanging(_props, _wixCart) {
429
457
  }));
430
458
  }
431
459
  const miniCart = makeJayStackComponent().withProps().withServices(WIX_CART_SERVICE).withFastRender(renderFastChanging);
460
+ function loadWixCartConfig() {
461
+ const configPath = path.join(process.cwd(), "config", ".wix-cart.yaml");
462
+ const defaults = {
463
+ urls: { thankYou: "/thank-you" }
464
+ };
465
+ if (!fs.existsSync(configPath)) return defaults;
466
+ try {
467
+ const raw = yaml.load(fs.readFileSync(configPath, "utf-8"));
468
+ return {
469
+ urls: {
470
+ thankYou: raw?.urls?.thankYou || defaults.urls.thankYou
471
+ }
472
+ };
473
+ } catch {
474
+ return defaults;
475
+ }
476
+ }
432
477
  const init = makeJayInit().withServer(async () => {
433
478
  console.log("[wix-cart] Initializing Wix Cart service...");
434
479
  const wixClient = getService(WIX_CLIENT_SERVICE);
435
- provideWixCartService(wixClient);
480
+ const config = loadWixCartConfig();
481
+ provideWixCartService(wixClient, { urls: config.urls });
436
482
  console.log("[wix-cart] Server initialization complete");
437
483
  return {
438
- enableClientCart: true
484
+ enableClientCart: true,
485
+ thankYouUrl: config.urls.thankYou
439
486
  };
440
487
  });
441
488
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/wix-cart",
3
- "version": "0.18.3",
3
+ "version": "0.19.1",
4
4
  "type": "module",
5
5
  "description": "Wix Cart/Ecom shared package for Jay Framework",
6
6
  "license": "Apache-2.0",
@@ -37,16 +37,17 @@
37
37
  "test": ":"
38
38
  },
39
39
  "dependencies": {
40
- "@jay-framework/component": "^0.18.3",
41
- "@jay-framework/fullstack-component": "^0.18.3",
42
- "@jay-framework/reactive": "^0.18.3",
43
- "@jay-framework/runtime": "^0.18.3",
44
- "@jay-framework/secure": "^0.18.3",
45
- "@jay-framework/stack-client-runtime": "^0.18.3",
46
- "@jay-framework/stack-server-runtime": "^0.18.3",
47
- "@jay-framework/wix-server-client": "^0.18.3",
48
- "@jay-framework/wix-utils": "^0.18.3",
40
+ "@jay-framework/component": "^0.19.1",
41
+ "@jay-framework/fullstack-component": "^0.19.1",
42
+ "@jay-framework/reactive": "^0.19.1",
43
+ "@jay-framework/runtime": "^0.19.1",
44
+ "@jay-framework/secure": "^0.19.1",
45
+ "@jay-framework/stack-client-runtime": "^0.19.1",
46
+ "@jay-framework/stack-server-runtime": "^0.19.1",
47
+ "@jay-framework/wix-server-client": "^0.19.1",
48
+ "@jay-framework/wix-utils": "^0.19.1",
49
49
  "@wix/ecom": "^1.0.1996",
50
+ "@wix/redirects": "^1.0.77",
50
51
  "@wix/sdk": "^1.21.5",
51
52
  "@wix/sdk-runtime": "^1.0.11"
52
53
  },
@@ -54,9 +55,9 @@
54
55
  "@babel/core": "^7.23.7",
55
56
  "@babel/preset-env": "^7.23.8",
56
57
  "@babel/preset-typescript": "^7.23.3",
57
- "@jay-framework/compiler-jay-stack": "^0.18.3",
58
- "@jay-framework/jay-cli": "^0.18.3",
59
- "@jay-framework/vite-plugin": "^0.18.3",
58
+ "@jay-framework/compiler-jay-stack": "^0.19.1",
59
+ "@jay-framework/jay-cli": "^0.19.1",
60
+ "@jay-framework/vite-plugin": "^0.19.1",
60
61
  "@jay-framework/wix-dev-environment": "^0.6.12",
61
62
  "nodemon": "^3.0.3",
62
63
  "rimraf": "^5.0.5",