@replohq/sdk 1.5.0 → 1.6.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/_vendor/replo-utils/lib/url.d.ts +5 -1
- package/cart/cart-actions.js +2 -1
- package/cart/cart-actions.js.map +2 -2
- package/cart/cart-provider.d.ts +15 -3
- package/cart/cart-provider.js +49 -6
- package/cart/cart-provider.js.map +2 -2
- package/cart/cart-types.d.ts +15 -0
- package/cart/cart-types.js.map +1 -1
- package/cart/gateways/cart-gateway.d.ts +7 -0
- package/cart/gateways/cart-gateway.js.map +2 -2
- package/cart/gateways/cookie-cart-gateway.d.ts +9 -0
- package/cart/gateways/cookie-cart-gateway.js +54 -3
- package/cart/gateways/cookie-cart-gateway.js.map +2 -2
- package/cart/gateways/resolver-cart-gateway.js +4 -0
- package/cart/gateways/resolver-cart-gateway.js.map +2 -2
- package/cart/gateways/stripe-cart-gateway.d.ts +14 -0
- package/cart/gateways/stripe-cart-gateway.js +6 -1
- package/cart/gateways/stripe-cart-gateway.js.map +2 -2
- package/cart/hooks/use-add-to-cart.d.ts +3 -1
- package/cart/hooks/use-add-to-cart.js +1 -1
- package/cart/hooks/use-add-to-cart.js.map +2 -2
- package/cart/utils/cart-adjustments.d.ts +29 -0
- package/cart/utils/cart-adjustments.js +81 -0
- package/cart/utils/cart-adjustments.js.map +7 -0
- package/cart/utils/cart-utils.d.ts +14 -0
- package/cart/utils/cart-utils.js +43 -0
- package/cart/utils/cart-utils.js.map +2 -2
- package/cart/utils/cookie-cart-persistence.d.ts +4 -2
- package/cart/utils/cookie-cart-persistence.js +35 -21
- package/cart/utils/cookie-cart-persistence.js.map +3 -3
- package/gateways/canopy-api.d.ts +25 -0
- package/gateways/canopy-api.js +40 -0
- package/gateways/canopy-api.js.map +2 -2
- package/lib/buildMetadata.js +3 -3
- package/loaders/loader-schemas.d.ts +5 -0
- package/loaders/loader-schemas.js +2 -0
- package/loaders/loader-schemas.js.map +2 -2
- package/loaders/rebuy-recommendations-loader.d.ts +1 -0
- package/next/config.d.ts +18 -0
- package/next/config.js.map +2 -2
- package/package.json +4 -4
- package/providers/replo-provider.d.ts +19 -0
- package/providers/replo-provider.js.map +2 -2
|
@@ -64,6 +64,10 @@ export declare function fileExtensionFromUrl(url: string): string;
|
|
|
64
64
|
* Registration inputs are attacker-typeable, and the pipeline fetches them
|
|
65
65
|
* server-side — so only plain public https(+http) origins pass: no
|
|
66
66
|
* credentials, no ports, no IP literals, no localhost/RFC-6762 style names.
|
|
67
|
+
* Upgrades http to https and strips the fragment by default; `preserveProtocol`
|
|
68
|
+
* keeps the requested scheme.
|
|
67
69
|
*/
|
|
68
|
-
export declare function sanitizePublicHttpUrl(value: string | null
|
|
70
|
+
export declare function sanitizePublicHttpUrl(value: string | null, { preserveProtocol }?: {
|
|
71
|
+
preserveProtocol?: boolean;
|
|
72
|
+
}): string | null;
|
|
69
73
|
export declare function safeHost(url: string): string | null;
|
package/cart/cart-actions.js
CHANGED
|
@@ -14,7 +14,8 @@ const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
|
|
|
14
14
|
async function getOrCreateCartAction() {
|
|
15
15
|
const existingCart = await getCartAction();
|
|
16
16
|
if (existingCart) {
|
|
17
|
-
|
|
17
|
+
const cartGateway = await getCartGateway();
|
|
18
|
+
return await cartGateway.reconcileStoredCart(existingCart);
|
|
18
19
|
}
|
|
19
20
|
return await createCartAction();
|
|
20
21
|
}
|
package/cart/cart-actions.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/cart-actions.ts"],
|
|
4
|
-
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"./cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport {\n getCartCookieOptions,\n REPLO_CART_COOKIE_NAME,\n} from \"./gateways/cart-constants\";\nimport { getCartGateway } from \"./gateways/cart-gateway\";\nimport {\n loadCartFromCookie,\n saveCartToCookie,\n} from \"./utils/cookie-cart-persistence\";\n\nconst SHOPIFY_CART_COOKIE_NAME = \"replo_cart_id\";\n\n/**\n * Gets the existing cart or creates a new one if none exists. This is necessary\n * because we may not have cookies in the server context during initial load.\n *\n * See: https://nextjs.org/docs/app/api-reference/functions/cookies#understanding-cookie-behavior-in-server-components\n *\n * @returns The existing cart or a newly created cart\n */\nexport async function getOrCreateCartAction(): Promise<Cart | null> {\n const existingCart = await getCartAction();\n if (existingCart) {\n return existingCart;\n }\n return await createCartAction();\n}\n\n/**\n * Retrieves the current cart from cookies. Returns null if no cart exists or\n * if the cart has expired/been deleted.\n *\n * @returns The current cart or null if none exists\n */\nexport async function getCartAction(): Promise<Cart | null> {\n const checkoutProvider = await getCheckoutProvider();\n\n if (checkoutProvider === \"stripe\") {\n return await loadCartFromCookie(REPLO_CART_COOKIE_NAME);\n }\n\n const cookieStore = await cookies();\n const existingCartId = cookieStore.get(SHOPIFY_CART_COOKIE_NAME)?.value;\n\n if (!existingCartId) {\n return null;\n }\n\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.getCart(existingCartId);\n\n // If we had a cart ID in the cookie but couldn't fetch the cart,\n // it means the cart is invalid/expired - clear the cookie\n if (!cart) {\n try {\n cookieStore.set(SHOPIFY_CART_COOKIE_NAME, \"\", {\n ...getCartCookieOptions(),\n maxAge: 0,\n });\n } catch (error) {\n console.warn(\"Failed to delete cart cookie:\", error);\n }\n }\n\n return cart;\n}\n\n/**\n * Creates a new cart with optional initial line items. The cart ID is stored\n * in a cookie for future retrieval.\n *\n * @param options.lines - Optional initial cart line items\n * @param options.skipStoringCart - If true, skips storing the cart in cookies (used for buy-now flows)\n * @param options.discountCodes - Optional discount codes to apply on cart creation\n * @returns The newly created cart\n */\nexport async function createCartAction(options?: {\n lines?: CartLinePayload[];\n skipStoringCart?: boolean;\n discountCodes?: string[];\n}): Promise<Cart | null> {\n const { lines, skipStoringCart = false, discountCodes } = options ?? {};\n const checkoutProvider = await getCheckoutProvider();\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.createCart(lines, discountCodes);\n\n if (cart && !skipStoringCart) {\n // Set the cart ID in a cookie - this works in Server Action context\n try {\n if (checkoutProvider === \"stripe\") {\n await saveCartToCookie(cart, REPLO_CART_COOKIE_NAME);\n } else {\n const cookieStore = await cookies();\n cookieStore.set(\n SHOPIFY_CART_COOKIE_NAME,\n cart.id,\n getCartCookieOptions(),\n );\n }\n } catch (cookieError) {\n console.warn(\"Failed to set cart cookie:\", cookieError);\n }\n }\n\n return cart;\n}\n\n/**\n * Adds line items to an existing cart.\n *\n * @param lines - Array of cart line items to add\n * @param cartId - The ID of the cart to add items to\n * @returns The updated cart\n */\nexport async function addToCartAction(\n lines: AddToCartPayload[\"lines\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.addToCart({ lines, cartId });\n}\n\n/**\n * Updates a cart line item (typically quantity).\n *\n * @param line - The cart line item with updated values\n * @param cartId - The ID of the cart containing the line item\n * @returns The updated cart\n */\nexport async function updateCartLineItemAction(\n line: UpdateCartLineItemPayload[\"line\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartLineItem({ line, cartId });\n}\n\n/**\n * Removes one or more line items from the cart.\n *\n * @param lineIds - Array of line item IDs to remove\n * @param cartId - The ID of the cart to remove items from\n * @returns The updated cart\n */\nexport async function removeCartLineItemsAction(\n lineIds: RemoveFromCartPayload[\"lineIds\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.removeCartLineItems({ lineIds, cartId });\n}\n\n/**\n * Updates discount codes for a cart.\n *\n * @param discountCodes - Array of discount code strings to apply\n * @param cartId - The ID of the cart to update\n * @returns The updated cart\n */\nexport async function updateCartDiscountCodesAction(\n discountCodes: string[],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartDiscountCodes({ discountCodes, cartId });\n}\n"],
|
|
5
|
-
"mappings": ";AAUA,SAAS,eAAe;AAExB,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,2BAA2B;AAUjC,eAAsB,wBAA8C;AAClE,QAAM,eAAe,MAAM,cAAc;AACzC,MAAI,cAAc;
|
|
4
|
+
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"./cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport {\n getCartCookieOptions,\n REPLO_CART_COOKIE_NAME,\n} from \"./gateways/cart-constants\";\nimport { getCartGateway } from \"./gateways/cart-gateway\";\nimport {\n loadCartFromCookie,\n saveCartToCookie,\n} from \"./utils/cookie-cart-persistence\";\n\nconst SHOPIFY_CART_COOKIE_NAME = \"replo_cart_id\";\n\n/**\n * Gets the existing cart or creates a new one if none exists. This is necessary\n * because we may not have cookies in the server context during initial load.\n *\n * See: https://nextjs.org/docs/app/api-reference/functions/cookies#understanding-cookie-behavior-in-server-components\n *\n * @returns The existing cart or a newly created cart\n */\nexport async function getOrCreateCartAction(): Promise<Cart | null> {\n const existingCart = await getCartAction();\n if (existingCart) {\n // The cookie cart snapshots catalog facts at add-to-cart time; a cart\n // that outlives a price change or a product removal corrects itself here,\n // on the mount round-trip, instead of lying until checkout.\n const cartGateway = await getCartGateway();\n return await cartGateway.reconcileStoredCart(existingCart);\n }\n return await createCartAction();\n}\n\n/**\n * Retrieves the current cart from cookies. Returns null if no cart exists or\n * if the cart has expired/been deleted.\n *\n * @returns The current cart or null if none exists\n */\nexport async function getCartAction(): Promise<Cart | null> {\n const checkoutProvider = await getCheckoutProvider();\n\n if (checkoutProvider === \"stripe\") {\n return await loadCartFromCookie(REPLO_CART_COOKIE_NAME);\n }\n\n const cookieStore = await cookies();\n const existingCartId = cookieStore.get(SHOPIFY_CART_COOKIE_NAME)?.value;\n\n if (!existingCartId) {\n return null;\n }\n\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.getCart(existingCartId);\n\n // If we had a cart ID in the cookie but couldn't fetch the cart,\n // it means the cart is invalid/expired - clear the cookie\n if (!cart) {\n try {\n cookieStore.set(SHOPIFY_CART_COOKIE_NAME, \"\", {\n ...getCartCookieOptions(),\n maxAge: 0,\n });\n } catch (error) {\n console.warn(\"Failed to delete cart cookie:\", error);\n }\n }\n\n return cart;\n}\n\n/**\n * Creates a new cart with optional initial line items. The cart ID is stored\n * in a cookie for future retrieval.\n *\n * @param options.lines - Optional initial cart line items\n * @param options.skipStoringCart - If true, skips storing the cart in cookies (used for buy-now flows)\n * @param options.discountCodes - Optional discount codes to apply on cart creation\n * @returns The newly created cart\n */\nexport async function createCartAction(options?: {\n lines?: CartLinePayload[];\n skipStoringCart?: boolean;\n discountCodes?: string[];\n}): Promise<Cart | null> {\n const { lines, skipStoringCart = false, discountCodes } = options ?? {};\n const checkoutProvider = await getCheckoutProvider();\n const cartGateway = await getCartGateway();\n const cart = await cartGateway.createCart(lines, discountCodes);\n\n if (cart && !skipStoringCart) {\n // Set the cart ID in a cookie - this works in Server Action context\n try {\n if (checkoutProvider === \"stripe\") {\n await saveCartToCookie(cart, REPLO_CART_COOKIE_NAME);\n } else {\n const cookieStore = await cookies();\n cookieStore.set(\n SHOPIFY_CART_COOKIE_NAME,\n cart.id,\n getCartCookieOptions(),\n );\n }\n } catch (cookieError) {\n console.warn(\"Failed to set cart cookie:\", cookieError);\n }\n }\n\n return cart;\n}\n\n/**\n * Adds line items to an existing cart.\n *\n * @param lines - Array of cart line items to add\n * @param cartId - The ID of the cart to add items to\n * @returns The updated cart\n */\nexport async function addToCartAction(\n lines: AddToCartPayload[\"lines\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.addToCart({ lines, cartId });\n}\n\n/**\n * Updates a cart line item (typically quantity).\n *\n * @param line - The cart line item with updated values\n * @param cartId - The ID of the cart containing the line item\n * @returns The updated cart\n */\nexport async function updateCartLineItemAction(\n line: UpdateCartLineItemPayload[\"line\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartLineItem({ line, cartId });\n}\n\n/**\n * Removes one or more line items from the cart.\n *\n * @param lineIds - Array of line item IDs to remove\n * @param cartId - The ID of the cart to remove items from\n * @returns The updated cart\n */\nexport async function removeCartLineItemsAction(\n lineIds: RemoveFromCartPayload[\"lineIds\"],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.removeCartLineItems({ lineIds, cartId });\n}\n\n/**\n * Updates discount codes for a cart.\n *\n * @param discountCodes - Array of discount code strings to apply\n * @param cartId - The ID of the cart to update\n * @returns The updated cart\n */\nexport async function updateCartDiscountCodesAction(\n discountCodes: string[],\n cartId: string,\n): Promise<Cart | null> {\n const cartGateway = await getCartGateway();\n return await cartGateway.updateCartDiscountCodes({ discountCodes, cartId });\n}\n"],
|
|
5
|
+
"mappings": ";AAUA,SAAS,eAAe;AAExB,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,2BAA2B;AAUjC,eAAsB,wBAA8C;AAClE,QAAM,eAAe,MAAM,cAAc;AACzC,MAAI,cAAc;AAIhB,UAAM,cAAc,MAAM,eAAe;AACzC,WAAO,MAAM,YAAY,oBAAoB,YAAY;AAAA,EAC3D;AACA,SAAO,MAAM,iBAAiB;AAChC;AAQA,eAAsB,gBAAsC;AAC1D,QAAM,mBAAmB,MAAM,oBAAoB;AAEnD,MAAI,qBAAqB,UAAU;AACjC,WAAO,MAAM,mBAAmB,sBAAsB;AAAA,EACxD;AAEA,QAAM,cAAc,MAAM,QAAQ;AAClC,QAAM,iBAAiB,YAAY,IAAI,wBAAwB,GAAG;AAElE,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,YAAY,QAAQ,cAAc;AAIrD,MAAI,CAAC,MAAM;AACT,QAAI;AACF,kBAAY,IAAI,0BAA0B,IAAI;AAAA,QAC5C,GAAG,qBAAqB;AAAA,QACxB,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,SAAS,OAAO;AACd,cAAQ,KAAK,iCAAiC,KAAK;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AACT;AAWA,eAAsB,iBAAiB,SAId;AACvB,QAAM,EAAE,OAAO,kBAAkB,OAAO,cAAc,IAAI,WAAW,CAAC;AACtE,QAAM,mBAAmB,MAAM,oBAAoB;AACnD,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,OAAO,MAAM,YAAY,WAAW,OAAO,aAAa;AAE9D,MAAI,QAAQ,CAAC,iBAAiB;AAE5B,QAAI;AACF,UAAI,qBAAqB,UAAU;AACjC,cAAM,iBAAiB,MAAM,sBAAsB;AAAA,MACrD,OAAO;AACL,cAAM,cAAc,MAAM,QAAQ;AAClC,oBAAY;AAAA,UACV;AAAA,UACA,KAAK;AAAA,UACL,qBAAqB;AAAA,QACvB;AAAA,MACF;AAAA,IACF,SAAS,aAAa;AACpB,cAAQ,KAAK,8BAA8B,WAAW;AAAA,IACxD;AAAA,EACF;AAEA,SAAO;AACT;AASA,eAAsB,gBACpB,OACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,UAAU,EAAE,OAAO,OAAO,CAAC;AACtD;AASA,eAAsB,yBACpB,MACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAC9D;AASA,eAAsB,0BACpB,SACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,oBAAoB,EAAE,SAAS,OAAO,CAAC;AAClE;AASA,eAAsB,8BACpB,eACA,QACsB;AACtB,QAAM,cAAc,MAAM,eAAe;AACzC,SAAO,MAAM,YAAY,wBAAwB,EAAE,eAAe,OAAO,CAAC;AAC5E;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/cart/cart-provider.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Cart, CartLine, CartLinePayload } from "./cart-types";
|
|
1
|
+
import type { Cart, CartLine, CartLineAdjustment, CartLinePayload } from "./cart-types";
|
|
2
2
|
import React from "react";
|
|
3
3
|
/**
|
|
4
4
|
* Internal cart context type with full functionality.
|
|
@@ -16,13 +16,19 @@ export interface CartContextTypeInternal {
|
|
|
16
16
|
/** Currency code for the cart */
|
|
17
17
|
currencyCode: string;
|
|
18
18
|
/** Function to add items to cart */
|
|
19
|
-
addToCart: (lines: CartLinePayload[], openCartAfterAdd?: boolean) => Promise<
|
|
19
|
+
addToCart: (lines: CartLinePayload[], openCartAfterAdd?: boolean) => Promise<{
|
|
20
|
+
adjustments: CartLineAdjustment[];
|
|
21
|
+
}>;
|
|
20
22
|
/** Function to update cart item quantity */
|
|
21
|
-
updateCartItem: (line: CartLinePayload) => Promise<
|
|
23
|
+
updateCartItem: (line: CartLinePayload) => Promise<{
|
|
24
|
+
adjustments: CartLineAdjustment[];
|
|
25
|
+
}>;
|
|
22
26
|
/** Function to remove item from cart */
|
|
23
27
|
removeCartItem: (lineId: string) => Promise<void>;
|
|
24
28
|
/** Function to update discount codes on cart */
|
|
25
29
|
updateDiscountCodes: (discountCodes: string[]) => Promise<void>;
|
|
30
|
+
/** Re-read the persisted cart and adopt it if no mutations are in flight */
|
|
31
|
+
refreshCart: () => Promise<void>;
|
|
26
32
|
/** Function to create cart and checkout immediately */
|
|
27
33
|
buyNow: (lines: CartLinePayload[], discountCodes?: string[]) => Promise<Cart | null>;
|
|
28
34
|
/** URL to redirect to checkout */
|
|
@@ -50,6 +56,12 @@ export interface CartContextType {
|
|
|
50
56
|
closeCart: () => void;
|
|
51
57
|
/** Function to update discount codes on cart */
|
|
52
58
|
updateDiscountCodes: (discountCodes: string[]) => Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Re-read the persisted cart and adopt it if no mutations are in flight.
|
|
61
|
+
* Call after something outside the provider rewrote the cart — e.g. the
|
|
62
|
+
* checkout route clamped it and bounced back with `?checkout=adjusted`.
|
|
63
|
+
*/
|
|
64
|
+
refreshCart: () => Promise<void>;
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
55
67
|
* Provider component that manages global cart state and operations. Handles both
|
package/cart/cart-provider.js
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
updateCartDiscountCodesAction,
|
|
14
14
|
updateCartLineItemAction
|
|
15
15
|
} from "./cart-actions";
|
|
16
|
+
import {
|
|
17
|
+
deriveAddToCartAdjustments,
|
|
18
|
+
deriveUpdateCartAdjustments
|
|
19
|
+
} from "./utils/cart-adjustments";
|
|
16
20
|
import {
|
|
17
21
|
buildCartLineAnalyticsData,
|
|
18
22
|
buildCartViewAnalyticsData
|
|
@@ -208,6 +212,14 @@ function CartProvider({
|
|
|
208
212
|
const closeCart = React.useCallback(() => {
|
|
209
213
|
setIsCartOpen(false);
|
|
210
214
|
}, []);
|
|
215
|
+
const refreshCart = React.useCallback(async () => {
|
|
216
|
+
const refreshedCart = await getCartAction();
|
|
217
|
+
if (!isMounted.current || !refreshedCart || pendingMutations.current !== 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
serverCart.current = refreshedCart;
|
|
221
|
+
updateCartData(refreshedCart);
|
|
222
|
+
}, [updateCartData]);
|
|
211
223
|
const reportedLineQuantities = React.useRef(/* @__PURE__ */ new Map());
|
|
212
224
|
const addToCart = async (lines, openCartAfterAdd = true) => {
|
|
213
225
|
const readyCart = cartData ?? serverCart.current ?? await initializeCart();
|
|
@@ -245,16 +257,28 @@ function CartProvider({
|
|
|
245
257
|
setIsCartOpen(true);
|
|
246
258
|
emitCartView(updatedCart);
|
|
247
259
|
}
|
|
248
|
-
|
|
249
|
-
|
|
260
|
+
const adjustments = [];
|
|
261
|
+
await enqueueMutation(async (currentCart) => {
|
|
262
|
+
const savedCart = await addToCartAction(lines, currentCart.id);
|
|
263
|
+
if (savedCart) {
|
|
264
|
+
adjustments.push(
|
|
265
|
+
...deriveAddToCartAdjustments({
|
|
266
|
+
cartBeforeAdd: currentCart,
|
|
267
|
+
requestedLines: lines,
|
|
268
|
+
savedCart
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
return savedCart;
|
|
250
273
|
});
|
|
274
|
+
return { adjustments };
|
|
251
275
|
};
|
|
252
276
|
const updateCartItem = async (line) => {
|
|
253
277
|
if (!cartData) {
|
|
254
278
|
console.warn(
|
|
255
279
|
"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
|
|
256
280
|
);
|
|
257
|
-
return;
|
|
281
|
+
return { adjustments: [] };
|
|
258
282
|
}
|
|
259
283
|
const currentLine = cartData.lines.find((item) => item.id === line.id);
|
|
260
284
|
const merchandise = currentLine?.merchandise ?? line.merchandise;
|
|
@@ -274,17 +298,34 @@ function CartProvider({
|
|
|
274
298
|
}
|
|
275
299
|
return updateLineInCart(currentCart, line);
|
|
276
300
|
});
|
|
301
|
+
const adjustments = [];
|
|
277
302
|
await enqueueMutation(async (currentCart) => {
|
|
278
303
|
const savedLine = findLine({
|
|
279
304
|
currentCart,
|
|
280
305
|
lineId: line.id,
|
|
281
306
|
originalLine: currentLine
|
|
282
307
|
});
|
|
283
|
-
|
|
308
|
+
if (!savedLine) {
|
|
309
|
+
return currentCart;
|
|
310
|
+
}
|
|
311
|
+
const savedCart = await updateCartLineItemAction(
|
|
284
312
|
{ ...line, id: savedLine.id },
|
|
285
313
|
currentCart.id
|
|
286
|
-
)
|
|
314
|
+
);
|
|
315
|
+
if (savedCart) {
|
|
316
|
+
adjustments.push(
|
|
317
|
+
...deriveUpdateCartAdjustments({
|
|
318
|
+
cartBeforeUpdate: currentCart,
|
|
319
|
+
editedLine: savedLine,
|
|
320
|
+
requestedQuantity: line.quantity,
|
|
321
|
+
requestedSellingPlanId: line.sellingPlanId,
|
|
322
|
+
savedCart
|
|
323
|
+
})
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return savedCart;
|
|
287
327
|
});
|
|
328
|
+
return { adjustments };
|
|
288
329
|
};
|
|
289
330
|
const removeCartItem = async (lineId) => {
|
|
290
331
|
if (!cartData) {
|
|
@@ -363,6 +404,7 @@ function CartProvider({
|
|
|
363
404
|
updateCartItem,
|
|
364
405
|
removeCartItem,
|
|
365
406
|
updateDiscountCodes,
|
|
407
|
+
refreshCart,
|
|
366
408
|
buyNow,
|
|
367
409
|
checkoutUrl,
|
|
368
410
|
isCartOpen,
|
|
@@ -394,7 +436,8 @@ function useCart() {
|
|
|
394
436
|
isCartOpen: context.isCartOpen,
|
|
395
437
|
openCart: context.openCart,
|
|
396
438
|
closeCart: context.closeCart,
|
|
397
|
-
updateDiscountCodes: context.updateDiscountCodes
|
|
439
|
+
updateDiscountCodes: context.updateDiscountCodes,
|
|
440
|
+
refreshCart: context.refreshCart
|
|
398
441
|
};
|
|
399
442
|
}
|
|
400
443
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/cart-provider.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type { Cart, CartLine, CartLinePayload } from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport {\n buildCartLineAnalyticsData,\n buildCartViewAnalyticsData,\n} from \"./utils/cart-analytics\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart> | null = null;\n\nfunction getOrCreateSharedCart() {\n cartCreationPromise ??= getOrCreateCartAction()\n .then((cart) => {\n if (!cart) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n return cart;\n })\n .finally(() => {\n cartCreationPromise = null;\n });\n return cartCreationPromise;\n}\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart({\n cart,\n line,\n lineId,\n}: {\n cart: Cart;\n line: CartLinePayload;\n lineId: string;\n}): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: lineId,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<void>;\n /** Function to update cart item quantity */\n updateCartItem: (line: CartLinePayload) => Promise<void>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const optimisticCart = React.useRef(cart);\n const updateCartData = React.useCallback(\n (update: React.SetStateAction<Cart | null>) => {\n const nextCart =\n typeof update === \"function\" ? update(optimisticCart.current) : update;\n // Analytics and overlapping mutations need the snapshot before React renders it.\n optimisticCart.current = nextCart;\n setCartData(nextCart);\n return nextCart;\n },\n [],\n );\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const isMounted = React.useRef(true);\n const analytics = useAnalyticsOptional();\n const serverCart = React.useRef(cart);\n const needsRefresh = React.useRef(false);\n const mutationQueue = React.useRef(Promise.resolve());\n const pendingMutations = React.useRef(0);\n const optimisticLines = React.useRef(new Map<string, CartLinePayload>());\n\n const enqueueMutation = async (\n save: (currentCart: Cart) => Promise<Cart | null>,\n ) => {\n pendingMutations.current += 1;\n const request = mutationQueue.current.then(async () => {\n // An existing server cart remains usable if background initialization fails.\n await cartCreationPromise?.catch(() => null);\n if (needsRefresh.current) {\n const restoredCart = await getCartAction();\n if (!restoredCart)\n throw new CartContextError({\n message:\n \"Your cart could not be refreshed. Please reload before changing it.\",\n });\n serverCart.current = restoredCart;\n needsRefresh.current = false;\n }\n if (!serverCart.current) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n try {\n const savedCart = await save(serverCart.current);\n if (!savedCart)\n throw new CartContextError({\n message: \"Your cart could not be saved. Please try again.\",\n });\n serverCart.current = savedCart;\n } catch (error) {\n // A lost response may still have persisted the add that the next removal targets.\n const restoredCart = await getCartAction().catch(() => null);\n needsRefresh.current = !restoredCart;\n if (restoredCart) serverCart.current = restoredCart;\n throw error;\n }\n });\n // Cookie writes must finish in shopper order, including after a failed request.\n mutationQueue.current = request\n .catch(() => {})\n .then(() => {\n pendingMutations.current -= 1;\n if (pendingMutations.current === 0) {\n reportedLineQuantities.current.clear();\n optimisticLines.current.clear();\n updateCartData(serverCart.current);\n }\n });\n await request;\n };\n\n const findLine = ({\n currentCart,\n lineId,\n originalLine,\n }: {\n currentCart: Cart;\n lineId: string;\n originalLine?: CartLine;\n }) => {\n const existingLine = currentCart.lines.find((line) => line.id === lineId);\n if (existingLine) return existingLine;\n const optimisticLine = optimisticLines.current.get(lineId);\n const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;\n const sellingPlanId =\n optimisticLine?.sellingPlanId ??\n originalLine?.sellingPlanAllocation?.sellingPlan.id ??\n null;\n return currentCart.lines.find((line) => {\n return (\n line.merchandise?.id === merchandiseId &&\n (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId\n );\n });\n };\n\n const initializeCart = React.useCallback(async () => {\n const loadedCart = await getOrCreateSharedCart();\n if (!isMounted.current) {\n throw new DOMException(\"Cart provider was unmounted\", \"AbortError\");\n }\n serverCart.current = loadedCart;\n if (pendingMutations.current === 0) updateCartData(loadedCart);\n return loadedCart;\n }, [updateCartData]);\n\n // eslint-disable-next-line replo/no-use-effect -- Initializes the browser cart on mount.\n React.useEffect(() => {\n isMounted.current = true;\n void initializeCart().catch((error) => {\n if (isMounted.current) {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n }\n });\n return () => {\n isMounted.current = false;\n };\n }, [initializeCart]);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const emitCartView = React.useCallback(\n (cart: Cart | null) => {\n if (analytics) {\n void analytics.viewCart({ data: buildCartViewAnalyticsData(cart) });\n }\n },\n [analytics],\n );\n\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n emitCartView(cartData);\n }, [cartData, emitCartView]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n // Last quantity reported to analytics per line with an update in flight, so\n // overlapping stepper clicks diff against it instead of the same render-time\n // quantity. Dropped once the server confirms or another mutation replaces\n // the line set.\n const reportedLineQuantities = React.useRef(new Map<string, number>());\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // React callbacks retain their render's cart, even after initialization resolves.\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n reportedLineQuantities.current.clear();\n const additions = lines.map((line) => {\n const lineId = `temp-${uuidv4()}`;\n optimisticLines.current.set(lineId, line);\n return { line, lineId };\n });\n const applyLines = (cart: Cart) => {\n let updatedCart = { ...cart };\n for (const { line, lineId } of additions) {\n updatedCart = addLineToCart({ cart: updatedCart, line, lineId });\n }\n return updatedCart;\n };\n\n const updatedCart = updateCartData((currentCart) => {\n return applyLines(currentCart ?? readyCart);\n });\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n void analytics.addToCart({\n data: buildCartLineAnalyticsData({\n merchandise: line.merchandise,\n quantity: line.quantity,\n currencyCode: readyCart.cost.currencyCode,\n }),\n });\n }\n }\n\n if (openCartAfterAdd) {\n setIsCartOpen(true);\n emitCartView(updatedCart);\n }\n\n await enqueueMutation((currentCart) => {\n return addToCartAction(lines, currentCart.id);\n });\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n // Ad platforms have no \"quantity changed\" event: an increase is an add of\n // the difference and a decrease is a remove of the difference, matching\n // how Shopify themes report their cart steppers.\n const currentLine = cartData.lines.find((item) => item.id === line.id);\n const merchandise = currentLine?.merchandise ?? line.merchandise;\n const quantityDelta =\n line.quantity -\n (reportedLineQuantities.current.get(line.id) ??\n currentLine?.quantity ??\n 0);\n reportedLineQuantities.current.set(line.id, line.quantity);\n if (analytics && merchandise && quantityDelta !== 0) {\n const data = buildCartLineAnalyticsData({\n merchandise,\n quantity: Math.abs(quantityDelta),\n currencyCode: cartData.cost.currencyCode,\n });\n void (quantityDelta > 0\n ? analytics.addToCart({ data })\n : analytics.removeFromCart({ data }));\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId: line.id,\n originalLine: currentLine,\n });\n return savedLine\n ? await updateCartLineItemAction(\n { ...line, id: savedLine.id },\n currentCart.id,\n )\n : currentCart;\n });\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n reportedLineQuantities.current.delete(lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n void analytics.removeFromCart({\n data: buildCartLineAnalyticsData({\n merchandise: itemToRemove.merchandise,\n quantity: itemToRemove.quantity,\n currencyCode: cartData.cost.currencyCode,\n }),\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId,\n originalLine: itemToRemove,\n });\n return savedLine\n ? await removeCartLineItemsAction([savedLine.id], currentCart.id)\n : currentCart;\n });\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n updateCartData((currentCart) => {\n return {\n ...(currentCart ?? readyCart),\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n await enqueueMutation((currentCart) => {\n return updateCartDiscountCodesAction(discountCodes, currentCart.id);\n });\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n };\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["/**\n * Use useCart() hook to access cart UI state (itemsCount, isCartOpen, openCart, closeCart). For adding products to cart, use useAddToCart. For buy now functionality, use useBuyNow.\n * @module\n */\n\"use client\";\n\nimport type {\n Cart,\n CartLine,\n CartLineAdjustment,\n CartLinePayload,\n} from \"./cart-types\";\n\nimport React from \"react\";\n\nimport { v4 as uuidv4 } from \"uuid\";\n\nimport { useAnalyticsOptional } from \"../analytics/analytics-provider\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport {\n addToCartAction,\n createCartAction,\n getCartAction,\n getOrCreateCartAction,\n removeCartLineItemsAction,\n updateCartDiscountCodesAction,\n updateCartLineItemAction,\n} from \"./cart-actions\";\nimport {\n deriveAddToCartAdjustments,\n deriveUpdateCartAdjustments,\n} from \"./utils/cart-adjustments\";\nimport {\n buildCartLineAnalyticsData,\n buildCartViewAnalyticsData,\n} from \"./utils/cart-analytics\";\nimport { recalculateCartCost } from \"./utils/cart-utils\";\nimport { createOptimisticSellingPlanAllocation } from \"./utils/variant-to-cart-line\";\n\nclass CartContextError extends CanopyError {}\n\n// Global promise to prevent multiple cart creation attempts\nlet cartCreationPromise: Promise<Cart> | null = null;\n\nfunction getOrCreateSharedCart() {\n cartCreationPromise ??= getOrCreateCartAction()\n .then((cart) => {\n if (!cart) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n return cart;\n })\n .finally(() => {\n cartCreationPromise = null;\n });\n return cartCreationPromise;\n}\n\n/**\n * Client-side cart operation to add a line item. Used in editor mode.\n * Merges with existing lines if they match by merchandise ID and selling plan.\n *\n * @param cart - The cart to add the line to\n * @param line - The cart line payload to add\n * @returns The updated cart\n */\nfunction addLineToCart({\n cart,\n line,\n lineId,\n}: {\n cart: Cart;\n line: CartLinePayload;\n lineId: string;\n}): Cart {\n const existingLineIndex = cart.lines.findIndex((existingLine) => {\n return (\n existingLine.merchandise?.id === line.id &&\n // Distinguish by selling plan to avoid merging subscription with one-time\n (line.sellingPlanId ?? null) ===\n (existingLine.sellingPlanAllocation?.sellingPlan.id ?? null)\n );\n });\n\n if (existingLineIndex >= 0) {\n // Update existing line\n const updatedLines = [...cart.lines];\n updatedLines[existingLineIndex] = {\n ...updatedLines[existingLineIndex]!,\n quantity: updatedLines[existingLineIndex]!.quantity + line.quantity,\n };\n return recalculateCartCost({ ...cart, lines: updatedLines });\n } else {\n const optimisticSellingPlanAllocation =\n line.sellingPlanId && line.merchandise\n ? createOptimisticSellingPlanAllocation({\n merchandise: line.merchandise,\n sellingPlanId: line.sellingPlanId,\n currencyCode: cart.cost?.currencyCode ?? \"USD\",\n })\n : null;\n\n const newLine: CartLine = {\n id: lineId,\n quantity: line.quantity,\n merchandise: line.merchandise ?? null,\n attributes: line.properties ?? [],\n sellingPlanAllocation: optimisticSellingPlanAllocation,\n };\n return recalculateCartCost({ ...cart, lines: [newLine, ...cart.lines] });\n }\n}\n\n/**\n * Client-side cart operation to update a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to update\n * @param line - The cart line payload with updated values\n * @returns The updated cart\n */\nfunction updateLineInCart(cart: Cart, line: CartLinePayload): Cart {\n const updatedLines = cart.lines.map((existingLine) => {\n return existingLine.id === line.id\n ? { ...existingLine, quantity: line.quantity }\n : existingLine;\n });\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Client-side cart operation to remove a line item. Used in editor mode.\n *\n * @param cart - The cart containing the line to remove\n * @param lineId - The ID of the line to remove\n * @returns The updated cart\n */\nfunction removeLineFromCart(cart: Cart, lineId: string): Cart {\n const updatedLines = cart.lines.filter((line) => line.id !== lineId);\n return recalculateCartCost({ ...cart, lines: updatedLines });\n}\n\n/**\n * Internal cart context type with full functionality.\n * This is for internal use only - external code should use the limited CartContextType.\n */\nexport interface CartContextTypeInternal {\n /** The current cart (can be null) */\n cartData: Cart | null;\n /** Array of cart line items */\n lineItems: CartLine[];\n /** Total price of all items */\n subtotal: number;\n /** Total number of items in cart */\n itemsCount: number;\n /** Currency code for the cart */\n currencyCode: string;\n /** Function to add items to cart */\n addToCart: (\n lines: CartLinePayload[],\n openCartAfterAdd?: boolean,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to update cart item quantity */\n updateCartItem: (\n line: CartLinePayload,\n ) => Promise<{ adjustments: CartLineAdjustment[] }>;\n /** Function to remove item from cart */\n removeCartItem: (lineId: string) => Promise<void>;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /** Re-read the persisted cart and adopt it if no mutations are in flight */\n refreshCart: () => Promise<void>;\n /** Function to create cart and checkout immediately */\n buyNow: (\n lines: CartLinePayload[],\n discountCodes?: string[],\n ) => Promise<Cart | null>;\n /** URL to redirect to checkout */\n checkoutUrl: string;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n}\n\n// NOTE (Gabe, 2026-01-08): We intentionally duplicate properties from CartContextTypeInternal\n// rather than using Pick<> so that TypeDoc generates expanded documentation for each property.\n/**\n * Public cart context type for LLM use.\n * For adding products to cart, use useAddToCart.\n * For buy now functionality, use useBuyNow.\n */\nexport interface CartContextType {\n /** Total number of items in cart */\n itemsCount: number;\n /** Whether the cart UI is open */\n isCartOpen: boolean;\n /** Function to open the cart UI */\n openCart: () => void;\n /** Function to close the cart UI */\n closeCart: () => void;\n /** Function to update discount codes on cart */\n updateDiscountCodes: (discountCodes: string[]) => Promise<void>;\n /**\n * Re-read the persisted cart and adopt it if no mutations are in flight.\n * Call after something outside the provider rewrote the cart \u2014 e.g. the\n * checkout route clamped it and bounced back with `?checkout=adjusted`.\n */\n refreshCart: () => Promise<void>;\n}\n\nconst CartContext = React.createContext<CartContextTypeInternal | undefined>(\n undefined,\n);\n\n/**\n * Provider component that manages global cart state and operations. Handles both\n * server-side cart synchronization and client-side optimistic updates.\n *\n * @returns A context provider wrapping the children with cart functionality\n */\nexport function CartProvider({\n children,\n cart,\n}: React.PropsWithChildren<{\n /** Initial cart data from the server (can be null) */\n cart: Cart | null;\n}>) {\n const [cartData, setCartData] = React.useState<Cart | null>(cart);\n const optimisticCart = React.useRef(cart);\n const updateCartData = React.useCallback(\n (update: React.SetStateAction<Cart | null>) => {\n const nextCart =\n typeof update === \"function\" ? update(optimisticCart.current) : update;\n // Analytics and overlapping mutations need the snapshot before React renders it.\n optimisticCart.current = nextCart;\n setCartData(nextCart);\n return nextCart;\n },\n [],\n );\n const [isCartOpen, setIsCartOpen] = React.useState(false);\n const isMounted = React.useRef(true);\n const analytics = useAnalyticsOptional();\n const serverCart = React.useRef(cart);\n const needsRefresh = React.useRef(false);\n const mutationQueue = React.useRef(Promise.resolve());\n const pendingMutations = React.useRef(0);\n const optimisticLines = React.useRef(new Map<string, CartLinePayload>());\n\n const enqueueMutation = async (\n save: (currentCart: Cart) => Promise<Cart | null>,\n ) => {\n pendingMutations.current += 1;\n const request = mutationQueue.current.then(async () => {\n // An existing server cart remains usable if background initialization fails.\n await cartCreationPromise?.catch(() => null);\n if (needsRefresh.current) {\n const restoredCart = await getCartAction();\n if (!restoredCart)\n throw new CartContextError({\n message:\n \"Your cart could not be refreshed. Please reload before changing it.\",\n });\n serverCart.current = restoredCart;\n needsRefresh.current = false;\n }\n if (!serverCart.current) {\n throw new CartContextError({\n message: \"Your cart could not be loaded. Please try again.\",\n });\n }\n try {\n const savedCart = await save(serverCart.current);\n if (!savedCart)\n throw new CartContextError({\n message: \"Your cart could not be saved. Please try again.\",\n });\n serverCart.current = savedCart;\n } catch (error) {\n // A lost response may still have persisted the add that the next removal targets.\n const restoredCart = await getCartAction().catch(() => null);\n needsRefresh.current = !restoredCart;\n if (restoredCart) serverCart.current = restoredCart;\n throw error;\n }\n });\n // Cookie writes must finish in shopper order, including after a failed request.\n mutationQueue.current = request\n .catch(() => {})\n .then(() => {\n pendingMutations.current -= 1;\n if (pendingMutations.current === 0) {\n reportedLineQuantities.current.clear();\n optimisticLines.current.clear();\n updateCartData(serverCart.current);\n }\n });\n await request;\n };\n\n const findLine = ({\n currentCart,\n lineId,\n originalLine,\n }: {\n currentCart: Cart;\n lineId: string;\n originalLine?: CartLine;\n }) => {\n const existingLine = currentCart.lines.find((line) => line.id === lineId);\n if (existingLine) return existingLine;\n const optimisticLine = optimisticLines.current.get(lineId);\n const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;\n const sellingPlanId =\n optimisticLine?.sellingPlanId ??\n originalLine?.sellingPlanAllocation?.sellingPlan.id ??\n null;\n return currentCart.lines.find((line) => {\n return (\n line.merchandise?.id === merchandiseId &&\n (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId\n );\n });\n };\n\n const initializeCart = React.useCallback(async () => {\n const loadedCart = await getOrCreateSharedCart();\n if (!isMounted.current) {\n throw new DOMException(\"Cart provider was unmounted\", \"AbortError\");\n }\n serverCart.current = loadedCart;\n if (pendingMutations.current === 0) updateCartData(loadedCart);\n return loadedCart;\n }, [updateCartData]);\n\n // eslint-disable-next-line replo/no-use-effect -- Initializes the browser cart on mount.\n React.useEffect(() => {\n isMounted.current = true;\n void initializeCart().catch((error) => {\n if (isMounted.current) {\n console.error(\"[Replo] Error getting or creating cart:\", error);\n }\n });\n return () => {\n isMounted.current = false;\n };\n }, [initializeCart]);\n\n // Derived values - handle null cart gracefully\n const lineItems = cartData?.lines ?? [];\n\n const subtotal =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0 && lineItem.merchandise) {\n sum += lineItem.merchandise.price * lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n const itemsCount =\n cartData?.lines.reduce((sum, lineItem) => {\n if (lineItem.quantity > 0) {\n sum += lineItem.quantity;\n }\n return sum;\n }, 0) ?? 0;\n\n // Cart UI state functions\n const emitCartView = React.useCallback(\n (cart: Cart | null) => {\n if (analytics) {\n void analytics.viewCart({ data: buildCartViewAnalyticsData(cart) });\n }\n },\n [analytics],\n );\n\n const openCart = React.useCallback(() => {\n setIsCartOpen(true);\n emitCartView(cartData);\n }, [cartData, emitCartView]);\n\n const closeCart = React.useCallback(() => {\n setIsCartOpen(false);\n }, []);\n\n const refreshCart = React.useCallback(async () => {\n const refreshedCart = await getCartAction();\n // In-flight mutations own serverCart; a slow refresh must not clobber\n // their newer result with this older snapshot.\n if (\n !isMounted.current ||\n !refreshedCart ||\n pendingMutations.current !== 0\n ) {\n return;\n }\n serverCart.current = refreshedCart;\n updateCartData(refreshedCart);\n }, [updateCartData]);\n\n // Last quantity reported to analytics per line with an update in flight, so\n // overlapping stepper clicks diff against it instead of the same render-time\n // quantity. Dropped once the server confirms or another mutation replaces\n // the line set.\n const reportedLineQuantities = React.useRef(new Map<string, number>());\n\n const addToCart = async (\n lines: CartLinePayload[],\n openCartAfterAdd: boolean = true,\n ) => {\n // React callbacks retain their render's cart, even after initialization resolves.\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n reportedLineQuantities.current.clear();\n const additions = lines.map((line) => {\n const lineId = `temp-${uuidv4()}`;\n optimisticLines.current.set(lineId, line);\n return { line, lineId };\n });\n const applyLines = (cart: Cart) => {\n let updatedCart = { ...cart };\n for (const { line, lineId } of additions) {\n updatedCart = addLineToCart({ cart: updatedCart, line, lineId });\n }\n return updatedCart;\n };\n\n const updatedCart = updateCartData((currentCart) => {\n return applyLines(currentCart ?? readyCart);\n });\n\n if (analytics) {\n for (const line of lines) {\n if (!line.merchandise) {\n continue;\n }\n void analytics.addToCart({\n data: buildCartLineAnalyticsData({\n merchandise: line.merchandise,\n quantity: line.quantity,\n currencyCode: readyCart.cost.currencyCode,\n }),\n });\n }\n }\n\n if (openCartAfterAdd) {\n setIsCartOpen(true);\n emitCartView(updatedCart);\n }\n\n // The write path reconciles the whole cart, so any line can be clamped\n // or dropped \u2014 not just the ones this call added. Intent comes from the\n // request itself, not the optimistic cart, whose lines can lack\n // merchandise or plan metadata.\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedCart = await addToCartAction(lines, currentCart.id);\n if (savedCart) {\n adjustments.push(\n ...deriveAddToCartAdjustments({\n cartBeforeAdd: currentCart,\n requestedLines: lines,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n };\n\n const updateCartItem = async (line: CartLinePayload) => {\n // If no cart exists, we can't update it\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return { adjustments: [] };\n }\n\n // Ad platforms have no \"quantity changed\" event: an increase is an add of\n // the difference and a decrease is a remove of the difference, matching\n // how Shopify themes report their cart steppers.\n const currentLine = cartData.lines.find((item) => item.id === line.id);\n const merchandise = currentLine?.merchandise ?? line.merchandise;\n const quantityDelta =\n line.quantity -\n (reportedLineQuantities.current.get(line.id) ??\n currentLine?.quantity ??\n 0);\n reportedLineQuantities.current.set(line.id, line.quantity);\n if (analytics && merchandise && quantityDelta !== 0) {\n const data = buildCartLineAnalyticsData({\n merchandise,\n quantity: Math.abs(quantityDelta),\n currencyCode: cartData.cost.currencyCode,\n });\n void (quantityDelta > 0\n ? analytics.addToCart({ data })\n : analytics.removeFromCart({ data }));\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return updateLineInCart(currentCart, line);\n });\n\n const adjustments: CartLineAdjustment[] = [];\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId: line.id,\n originalLine: currentLine,\n });\n if (!savedLine) {\n return currentCart;\n }\n const savedCart = await updateCartLineItemAction(\n { ...line, id: savedLine.id },\n currentCart.id,\n );\n if (savedCart) {\n adjustments.push(\n ...deriveUpdateCartAdjustments({\n cartBeforeUpdate: currentCart,\n editedLine: savedLine,\n requestedQuantity: line.quantity,\n requestedSellingPlanId: line.sellingPlanId,\n savedCart,\n }),\n );\n }\n return savedCart;\n });\n return { adjustments };\n };\n\n const removeCartItem = async (lineId: string) => {\n if (!cartData) {\n console.warn(\n \"[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists.\",\n );\n return;\n }\n\n const itemToRemove = cartData.lines.find((item) => item.id === lineId);\n reportedLineQuantities.current.delete(lineId);\n if (analytics && itemToRemove && itemToRemove.merchandise) {\n void analytics.removeFromCart({\n data: buildCartLineAnalyticsData({\n merchandise: itemToRemove.merchandise,\n quantity: itemToRemove.quantity,\n currencyCode: cartData.cost.currencyCode,\n }),\n });\n }\n\n // Optimistic update - use functional form to avoid stale state\n updateCartData((currentCart) => {\n if (!currentCart) {\n return null;\n }\n return removeLineFromCart(currentCart, lineId);\n });\n\n await enqueueMutation(async (currentCart) => {\n const savedLine = findLine({\n currentCart,\n lineId,\n originalLine: itemToRemove,\n });\n return savedLine\n ? await removeCartLineItemsAction([savedLine.id], currentCart.id)\n : currentCart;\n });\n };\n\n const updateDiscountCodes = async (discountCodes: string[]) => {\n const readyCart =\n cartData ?? serverCart.current ?? (await initializeCart());\n\n updateCartData((currentCart) => {\n return {\n ...(currentCart ?? readyCart),\n discountCodes: discountCodes.map((code) => ({\n code,\n applicable: true,\n })),\n };\n });\n\n await enqueueMutation((currentCart) => {\n return updateCartDiscountCodesAction(discountCodes, currentCart.id);\n });\n };\n\n const buyNow = async (lines: CartLinePayload[], discountCodes?: string[]) => {\n // Create a new cart with the specified lines and discount codes\n const cart = await createCartAction({\n lines,\n skipStoringCart: true,\n discountCodes,\n });\n if (cart) {\n return cart;\n } else {\n console.warn(\n \"[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists.\",\n );\n return null;\n }\n };\n\n const checkoutUrl = cartData?.checkoutUrl ?? \"/\";\n\n return (\n <CartContext.Provider\n value={{\n cartData,\n lineItems,\n subtotal,\n itemsCount,\n currencyCode: cartData?.cost.currencyCode ?? \"USD\",\n addToCart,\n updateCartItem,\n removeCartItem,\n updateDiscountCodes,\n refreshCart,\n buyNow,\n checkoutUrl,\n isCartOpen,\n openCart,\n closeCart,\n }}\n >\n {children}\n </CartContext.Provider>\n );\n}\n\n/**\n * @deprecated For internal use only. Use useAddToCart or useBuyNow\n * for cart operations. Use useCart for cart UI state (itemsCount, openCart, closeCart).\n */\nexport function useCartInternal(): CartContextTypeInternal {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCartInternal must be used within a CartProvider\",\n });\n }\n return context;\n}\n\n/**\n * Hook to access cart UI state and controls. For adding products to cart,\n * use useAddToCart. For buy now functionality, use useBuyNow.\n *\n * @example\n * ```tsx\n * import { useCart } from \"@replohq/sdk/cart/cart-provider\";\n *\n * function MyComponent() {\n * const { itemsCount, openCart } = useCart();\n * ...\n * }\n * ```\n * @throws Error if used outside of a CartProvider\n */\nexport function useCart(): CartContextType {\n const context = React.useContext(CartContext);\n if (context === undefined) {\n throw new CartContextError({\n message: \"useCart must be used within a CartProvider\",\n });\n }\n return {\n itemsCount: context.itemsCount,\n isCartOpen: context.isCartOpen,\n openCart: context.openCart,\n closeCart: context.closeCart,\n updateDiscountCodes: context.updateDiscountCodes,\n refreshCart: context.refreshCart,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["cart", "updatedCart"]
|
|
7
7
|
}
|
package/cart/cart-types.d.ts
CHANGED
|
@@ -119,6 +119,21 @@ export interface Cart {
|
|
|
119
119
|
discountCodes?: CartDiscountCode[];
|
|
120
120
|
discountAllocations?: CartDiscountAllocation[];
|
|
121
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* How a cart write differed from what the shopper asked for, derived by
|
|
124
|
+
* comparing the requested lines against the cart the server saved. Cart UIs
|
|
125
|
+
* render these as Shopify-style notices ("Only 9 in stock — we've updated
|
|
126
|
+
* your cart"); the underlying clamp has already been applied to the cart.
|
|
127
|
+
*/
|
|
128
|
+
export type CartLineAdjustment = {
|
|
129
|
+
type: "quantityClampedToStock";
|
|
130
|
+
variantId: string;
|
|
131
|
+
requested: number;
|
|
132
|
+
applied: number;
|
|
133
|
+
} | {
|
|
134
|
+
type: "unavailableLineRemoved";
|
|
135
|
+
variantId: string;
|
|
136
|
+
};
|
|
122
137
|
export interface CartLinePayload {
|
|
123
138
|
id: string;
|
|
124
139
|
quantity: number;
|
package/cart/cart-types.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/cart-types.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * TypeScript types for cart data structures. Use these types when working with cart data, but don't import from this module directly in components - use the cart hooks instead.\n * @module\n */\n\nimport type { SellingPlanGroup } from \"../loaders/loader-schemas\";\n\nexport interface Image {\n url: string;\n altText: string | null;\n}\n\nexport interface CartLineMerchandise {\n id: string;\n product: {\n id: string;\n title: string;\n sellingPlanGroups?: SellingPlanGroup[];\n };\n title: string;\n /** Integer minor units (Stripe convention): 1999 = $19.99 USD */\n price: number;\n compareAtPrice?: number | null;\n image: Image | null;\n selectedOptions: {\n name: string;\n value: string;\n }[];\n}\n\nexport interface CartLineAttribute {\n key: string;\n value: string | number | boolean;\n}\n\n/** Replo attribution property added to cart/buy-now line items for order attribution in Shopify. */\nexport const REPLO_ATTRIBUTION_PROPERTY: CartLineAttribute = {\n key: \"_replo\",\n value: \"true\",\n};\n\nexport interface SellingPlanPriceAdjustment {\n compareAtPrice: {\n amount: number;\n currencyCode: string;\n };\n perDeliveryPrice: {\n amount: number;\n currencyCode: string;\n };\n price: {\n amount: number;\n currencyCode: string;\n };\n unitPrice: {\n amount: number;\n currencyCode: string;\n } | null;\n}\n\nexport interface SellingPlanAllocation {\n sellingPlan: {\n id: string;\n name: string;\n description: string | null;\n };\n priceAdjustments: SellingPlanPriceAdjustment[];\n checkoutChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n remainingBalanceChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n}\n\nexport interface CartLine {\n id: string;\n quantity: number;\n /**\n * Null when this is an optimistic line whose merchandise couldn't be\n * resolved from the query cache or passed explicitly. Consumers should\n * render a skeleton/loading state in that case; the line is replaced\n * with real merchandise once the server roundtrip lands.\n */\n merchandise: CartLineMerchandise | null;\n attributes: CartLineAttribute[];\n sellingPlanAllocation?: SellingPlanAllocation | null;\n}\n\nexport interface CartCost {\n /** Integer minor units (Stripe convention) in currencyCode */\n subtotalAmount: number;\n totalAmount: number;\n currencyCode: string;\n}\n\n/**\n * Represents a discount code applied to a cart.\n */\nexport interface CartDiscountCode {\n /** The discount code string */\n code: string;\n /**\n * Whether the discount code is applicable to the current cart.\n * A code may not be applicable due to:\n * - Cart contents not meeting product/collection eligibility\n * - Minimum purchase amount not met\n * - Code expired or usage limits reached\n * - Customer restrictions\n * - Conflicting discount rules\n */\n applicable: boolean;\n}\n\n/**\n * One discount Shopify applied to a cart line or to the whole cart. The amount\n * is already subtracted from `cost.totalAmount` (and from `cost.subtotalAmount`\n * for line-level discounts).\n */\nexport interface CartDiscountAllocation {\n /** Integer minor units (Stripe convention) in the cart's currencyCode */\n discountedAmount: number;\n /** Automatic discount title or discount code; null when Shopify has neither */\n title: string | null;\n}\n\nexport interface Cart {\n id: string;\n lines: CartLine[];\n cost: CartCost;\n checkoutUrl?: string;\n discountCodes?: CartDiscountCode[];\n discountAllocations?: CartDiscountAllocation[];\n}\n\nexport interface CartLinePayload {\n id: string;\n quantity: number;\n merchandise?: CartLineMerchandise;\n properties?: CartLineAttribute[];\n sellingPlanId?: string | null;\n}\n\nexport interface AddToCartPayload {\n lines: CartLinePayload[];\n cartId: string;\n}\n\nexport interface UpdateCartLineItemPayload {\n line: CartLinePayload;\n cartId: string;\n}\n\nexport interface RemoveFromCartPayload {\n lineIds: string[];\n cartId: string;\n}\n\nexport interface UpdateCartDiscountCodesPayload {\n cartId: string;\n discountCodes: string[];\n}\n"],
|
|
4
|
+
"sourcesContent": ["/**\n * TypeScript types for cart data structures. Use these types when working with cart data, but don't import from this module directly in components - use the cart hooks instead.\n * @module\n */\n\nimport type { SellingPlanGroup } from \"../loaders/loader-schemas\";\n\nexport interface Image {\n url: string;\n altText: string | null;\n}\n\nexport interface CartLineMerchandise {\n id: string;\n product: {\n id: string;\n title: string;\n sellingPlanGroups?: SellingPlanGroup[];\n };\n title: string;\n /** Integer minor units (Stripe convention): 1999 = $19.99 USD */\n price: number;\n compareAtPrice?: number | null;\n image: Image | null;\n selectedOptions: {\n name: string;\n value: string;\n }[];\n}\n\nexport interface CartLineAttribute {\n key: string;\n value: string | number | boolean;\n}\n\n/** Replo attribution property added to cart/buy-now line items for order attribution in Shopify. */\nexport const REPLO_ATTRIBUTION_PROPERTY: CartLineAttribute = {\n key: \"_replo\",\n value: \"true\",\n};\n\nexport interface SellingPlanPriceAdjustment {\n compareAtPrice: {\n amount: number;\n currencyCode: string;\n };\n perDeliveryPrice: {\n amount: number;\n currencyCode: string;\n };\n price: {\n amount: number;\n currencyCode: string;\n };\n unitPrice: {\n amount: number;\n currencyCode: string;\n } | null;\n}\n\nexport interface SellingPlanAllocation {\n sellingPlan: {\n id: string;\n name: string;\n description: string | null;\n };\n priceAdjustments: SellingPlanPriceAdjustment[];\n checkoutChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n remainingBalanceChargeAmount: {\n amount: number;\n currencyCode: string;\n };\n}\n\nexport interface CartLine {\n id: string;\n quantity: number;\n /**\n * Null when this is an optimistic line whose merchandise couldn't be\n * resolved from the query cache or passed explicitly. Consumers should\n * render a skeleton/loading state in that case; the line is replaced\n * with real merchandise once the server roundtrip lands.\n */\n merchandise: CartLineMerchandise | null;\n attributes: CartLineAttribute[];\n sellingPlanAllocation?: SellingPlanAllocation | null;\n}\n\nexport interface CartCost {\n /** Integer minor units (Stripe convention) in currencyCode */\n subtotalAmount: number;\n totalAmount: number;\n currencyCode: string;\n}\n\n/**\n * Represents a discount code applied to a cart.\n */\nexport interface CartDiscountCode {\n /** The discount code string */\n code: string;\n /**\n * Whether the discount code is applicable to the current cart.\n * A code may not be applicable due to:\n * - Cart contents not meeting product/collection eligibility\n * - Minimum purchase amount not met\n * - Code expired or usage limits reached\n * - Customer restrictions\n * - Conflicting discount rules\n */\n applicable: boolean;\n}\n\n/**\n * One discount Shopify applied to a cart line or to the whole cart. The amount\n * is already subtracted from `cost.totalAmount` (and from `cost.subtotalAmount`\n * for line-level discounts).\n */\nexport interface CartDiscountAllocation {\n /** Integer minor units (Stripe convention) in the cart's currencyCode */\n discountedAmount: number;\n /** Automatic discount title or discount code; null when Shopify has neither */\n title: string | null;\n}\n\nexport interface Cart {\n id: string;\n lines: CartLine[];\n cost: CartCost;\n checkoutUrl?: string;\n discountCodes?: CartDiscountCode[];\n discountAllocations?: CartDiscountAllocation[];\n}\n\n/**\n * How a cart write differed from what the shopper asked for, derived by\n * comparing the requested lines against the cart the server saved. Cart UIs\n * render these as Shopify-style notices (\"Only 9 in stock \u2014 we've updated\n * your cart\"); the underlying clamp has already been applied to the cart.\n */\nexport type CartLineAdjustment =\n | {\n type: \"quantityClampedToStock\";\n variantId: string;\n requested: number;\n applied: number;\n }\n | { type: \"unavailableLineRemoved\"; variantId: string };\n\nexport interface CartLinePayload {\n id: string;\n quantity: number;\n merchandise?: CartLineMerchandise;\n properties?: CartLineAttribute[];\n sellingPlanId?: string | null;\n}\n\nexport interface AddToCartPayload {\n lines: CartLinePayload[];\n cartId: string;\n}\n\nexport interface UpdateCartLineItemPayload {\n line: CartLinePayload;\n cartId: string;\n}\n\nexport interface RemoveFromCartPayload {\n lineIds: string[];\n cartId: string;\n}\n\nexport interface UpdateCartDiscountCodesPayload {\n cartId: string;\n discountCodes: string[];\n}\n"],
|
|
5
5
|
"mappings": "AAoCO,MAAM,6BAAgD;AAAA,EAC3D,KAAK;AAAA,EACL,OAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
2
2
|
export interface CartGateway {
|
|
3
3
|
getCart(cartId: string): Promise<Cart | null>;
|
|
4
|
+
/**
|
|
5
|
+
* Bring a stored cart back in line with its source of truth. Cookie carts
|
|
6
|
+
* reprice against the catalog and drop unavailable lines (persisting the
|
|
7
|
+
* result); provider-backed carts are already server-truth and return the
|
|
8
|
+
* cart unchanged.
|
|
9
|
+
*/
|
|
10
|
+
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
4
11
|
createCart(lines?: CartLinePayload[], discountCodes?: string[]): Promise<Cart | null>;
|
|
5
12
|
addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
|
|
6
13
|
updateCartLineItem(cartPayload: UpdateCartLineItemPayload): Promise<Cart | null>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../cart/gateways/cart-gateway.ts"],
|
|
4
|
-
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartDiscountCodesPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\n\nimport { getCheckoutProvider } from \"../../lib/integration-utils\";\nimport { createDefaultCartGateway } from \"./default-cart-gateway\";\nimport { createResolverCartGateway } from \"./resolver-cart-gateway\";\nimport { createStripeCartGateway } from \"./stripe-cart-gateway\";\n\nexport interface CartGateway {\n getCart(cartId: string): Promise<Cart | null>;\n createCart(\n lines?: CartLinePayload[],\n discountCodes?: string[],\n ): Promise<Cart | null>;\n addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;\n updateCartLineItem(\n cartPayload: UpdateCartLineItemPayload,\n ): Promise<Cart | null>;\n removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;\n updateCartDiscountCodes(\n cartPayload: UpdateCartDiscountCodesPayload,\n ): Promise<Cart | null>;\n}\n\nexport async function getCartGateway(): Promise<CartGateway> {\n const checkoutProvider = await getCheckoutProvider();\n\n if (checkoutProvider === \"stripe\") {\n return await createStripeCartGateway();\n } else if (checkoutProvider === \"shopify\") {\n return await createResolverCartGateway();\n } else {\n return await createDefaultCartGateway();\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAWA,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAC1C,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartDiscountCodesPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\n\nimport { getCheckoutProvider } from \"../../lib/integration-utils\";\nimport { createDefaultCartGateway } from \"./default-cart-gateway\";\nimport { createResolverCartGateway } from \"./resolver-cart-gateway\";\nimport { createStripeCartGateway } from \"./stripe-cart-gateway\";\n\nexport interface CartGateway {\n getCart(cartId: string): Promise<Cart | null>;\n /**\n * Bring a stored cart back in line with its source of truth. Cookie carts\n * reprice against the catalog and drop unavailable lines (persisting the\n * result); provider-backed carts are already server-truth and return the\n * cart unchanged.\n */\n reconcileStoredCart(cart: Cart): Promise<Cart>;\n createCart(\n lines?: CartLinePayload[],\n discountCodes?: string[],\n ): Promise<Cart | null>;\n addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;\n updateCartLineItem(\n cartPayload: UpdateCartLineItemPayload,\n ): Promise<Cart | null>;\n removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;\n updateCartDiscountCodes(\n cartPayload: UpdateCartDiscountCodesPayload,\n ): Promise<Cart | null>;\n}\n\nexport async function getCartGateway(): Promise<CartGateway> {\n const checkoutProvider = await getCheckoutProvider();\n\n if (checkoutProvider === \"stripe\") {\n return await createStripeCartGateway();\n } else if (checkoutProvider === \"shopify\") {\n return await createResolverCartGateway();\n } else {\n return await createDefaultCartGateway();\n }\n}\n"],
|
|
5
|
+
"mappings": ";AAWA,SAAS,2BAA2B;AACpC,SAAS,gCAAgC;AACzC,SAAS,iCAAiC;AAC1C,SAAS,+BAA+B;AAyBxC,eAAsB,iBAAuC;AAC3D,QAAM,mBAAmB,MAAM,oBAAoB;AAEnD,MAAI,qBAAqB,UAAU;AACjC,WAAO,MAAM,wBAAwB;AAAA,EACvC,WAAW,qBAAqB,WAAW;AACzC,WAAO,MAAM,0BAA0B;AAAA,EACzC,OAAO;AACL,WAAO,MAAM,yBAAyB;AAAA,EACxC;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReconciledCartLine } from "../../gateways/canopy-api";
|
|
1
2
|
import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
2
3
|
import type { LineMergeFn } from "../utils/cookie-cart-persistence";
|
|
3
4
|
import type { CartGateway } from "./cart-gateway";
|
|
@@ -10,6 +11,10 @@ interface CookieCartConfig {
|
|
|
10
11
|
lineIdGenerator: () => string;
|
|
11
12
|
cartIdGenerator: () => string;
|
|
12
13
|
gatewayName: string;
|
|
14
|
+
reconcileLines?: (lines: {
|
|
15
|
+
variantId: string;
|
|
16
|
+
quantity: number;
|
|
17
|
+
}[]) => Promise<ReconciledCartLine[]>;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Abstract base class for cookie-based cart gateways.
|
|
@@ -18,6 +23,10 @@ interface CookieCartConfig {
|
|
|
18
23
|
*/
|
|
19
24
|
export declare abstract class CookieBasedCartGateway implements CartGateway {
|
|
20
25
|
protected abstract readonly config: CookieCartConfig;
|
|
26
|
+
private fetchReconciledLines;
|
|
27
|
+
private reconcile;
|
|
28
|
+
private reconcileForWrite;
|
|
29
|
+
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
21
30
|
getCart(cartId: string): Promise<Cart | null>;
|
|
22
31
|
createCart(lines?: CartLinePayload[], _discountCodes?: string[]): Promise<Cart | null>;
|
|
23
32
|
addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
|
|
@@ -1,11 +1,59 @@
|
|
|
1
|
+
import { applyCartLineReconciliation } from "../utils/cart-utils";
|
|
1
2
|
import {
|
|
2
3
|
addToCartWithPersistence,
|
|
3
4
|
createCartWithPersistence,
|
|
4
5
|
loadCartFromCookie,
|
|
5
6
|
removeCartLineItemsWithPersistence,
|
|
7
|
+
saveCartToCookie,
|
|
6
8
|
updateCartLineItemWithPersistence
|
|
7
9
|
} from "../utils/cookie-cart-persistence";
|
|
8
10
|
class CookieBasedCartGateway {
|
|
11
|
+
// Fails open (null) when the catalog can't answer: a canopy blip must not
|
|
12
|
+
// block cart writes — checkout still enforces stock and price.
|
|
13
|
+
async fetchReconciledLines(cart) {
|
|
14
|
+
const reconcileLines = this.config.reconcileLines;
|
|
15
|
+
if (!reconcileLines) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const requestLines = cart.lines.flatMap((line) => {
|
|
19
|
+
return line.merchandise ? [{ variantId: line.merchandise.id, quantity: line.quantity }] : [];
|
|
20
|
+
});
|
|
21
|
+
if (requestLines.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return await reconcileLines(requestLines);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.warn(
|
|
28
|
+
`${this.config.gatewayName} cart reconciliation unavailable, keeping cart as written:`,
|
|
29
|
+
error
|
|
30
|
+
);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Catalog fetch failures fail open inside fetchReconciledLines; a
|
|
35
|
+
// reconciliation mismatch from the apply still throws.
|
|
36
|
+
async reconcile(cart, { clampQuantities }) {
|
|
37
|
+
const reconciledLines = await this.fetchReconciledLines(cart);
|
|
38
|
+
if (!reconciledLines) {
|
|
39
|
+
return cart;
|
|
40
|
+
}
|
|
41
|
+
return applyCartLineReconciliation(cart, reconciledLines, {
|
|
42
|
+
clampQuantities
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
reconcileForWrite = (cart) => {
|
|
46
|
+
return this.reconcile(cart, { clampQuantities: true });
|
|
47
|
+
};
|
|
48
|
+
async reconcileStoredCart(cart) {
|
|
49
|
+
const reconciledCart = await this.reconcile(cart, {
|
|
50
|
+
clampQuantities: false
|
|
51
|
+
});
|
|
52
|
+
if (JSON.stringify(reconciledCart.lines) !== JSON.stringify(cart.lines)) {
|
|
53
|
+
await saveCartToCookie(reconciledCart, this.config.cookieName);
|
|
54
|
+
}
|
|
55
|
+
return reconciledCart;
|
|
56
|
+
}
|
|
9
57
|
async getCart(cartId) {
|
|
10
58
|
try {
|
|
11
59
|
const cart = await loadCartFromCookie(this.config.cookieName);
|
|
@@ -24,7 +72,8 @@ class CookieBasedCartGateway {
|
|
|
24
72
|
cookieName: this.config.cookieName,
|
|
25
73
|
lines,
|
|
26
74
|
lineIdGenerator: this.config.lineIdGenerator,
|
|
27
|
-
cartIdGenerator: this.config.cartIdGenerator
|
|
75
|
+
cartIdGenerator: this.config.cartIdGenerator,
|
|
76
|
+
reconcile: this.reconcileForWrite
|
|
28
77
|
});
|
|
29
78
|
return cart;
|
|
30
79
|
} catch (createCartError) {
|
|
@@ -40,7 +89,8 @@ class CookieBasedCartGateway {
|
|
|
40
89
|
const cartData = await addToCartWithPersistence(cartPayload, {
|
|
41
90
|
cookieName: this.config.cookieName,
|
|
42
91
|
mergeFn: this.config.lineMergeFn,
|
|
43
|
-
lineIdGenerator: this.config.lineIdGenerator
|
|
92
|
+
lineIdGenerator: this.config.lineIdGenerator,
|
|
93
|
+
reconcile: this.reconcileForWrite
|
|
44
94
|
});
|
|
45
95
|
return cartData;
|
|
46
96
|
} catch (error) {
|
|
@@ -56,7 +106,8 @@ class CookieBasedCartGateway {
|
|
|
56
106
|
try {
|
|
57
107
|
const cartData = await updateCartLineItemWithPersistence(
|
|
58
108
|
cartPayload,
|
|
59
|
-
this.config.cookieName
|
|
109
|
+
this.config.cookieName,
|
|
110
|
+
this.reconcileForWrite
|
|
60
111
|
);
|
|
61
112
|
return cartData;
|
|
62
113
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../cart/gateways/cookie-cart-gateway.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartDiscountCodesPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\nimport type { LineMergeFn } from \"../utils/cookie-cart-persistence\";\nimport type { CartGateway } from \"./cart-gateway\";\n\nimport {\n addToCartWithPersistence,\n createCartWithPersistence,\n loadCartFromCookie,\n removeCartLineItemsWithPersistence,\n updateCartLineItemWithPersistence,\n} from \"../utils/cookie-cart-persistence\";\n\n/**\n * Configuration for cookie-based cart gateways\n */\ninterface CookieCartConfig {\n cookieName: string;\n lineMergeFn: LineMergeFn;\n lineIdGenerator: () => string;\n cartIdGenerator: () => string;\n gatewayName: string; // For logging (e.g., \"Stripe\", \"Default\")\n}\n\n/**\n * Abstract base class for cookie-based cart gateways.\n * Provides common error handling, logging, and cart operations.\n * Subclasses must provide gateway-specific configuration.\n */\nexport abstract class CookieBasedCartGateway implements CartGateway {\n protected abstract readonly config: CookieCartConfig;\n\n async getCart(cartId: string): Promise<Cart | null> {\n try {\n const cart = await loadCartFromCookie(this.config.cookieName);\n return cart?.id === cartId ? cart : null;\n } catch (error) {\n console.warn(\n `${this.config.gatewayName} cart ${cartId} not found, could not fetch cart:`,\n error,\n );\n return null;\n }\n }\n\n async createCart(\n lines?: CartLinePayload[],\n _discountCodes?: string[],\n ): Promise<Cart | null> {\n try {\n const cart = await createCartWithPersistence({\n cookieName: this.config.cookieName,\n lines,\n lineIdGenerator: this.config.lineIdGenerator,\n cartIdGenerator: this.config.cartIdGenerator,\n });\n return cart;\n } catch (createCartError) {\n console.warn(\n `Failed to create ${this.config.gatewayName} cart${lines?.length ? \" with lines\" : \"\"}:`,\n createCartError,\n );\n return null;\n }\n }\n\n async addToCart(cartPayload: AddToCartPayload): Promise<Cart | null> {\n try {\n const cartData = await addToCartWithPersistence(cartPayload, {\n cookieName: this.config.cookieName,\n mergeFn: this.config.lineMergeFn,\n lineIdGenerator: this.config.lineIdGenerator,\n });\n return cartData;\n } catch (error) {\n console.warn(`Failed to add items to ${this.config.gatewayName} cart:`, {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n itemCount: cartPayload.lines.length,\n });\n return null;\n }\n }\n\n async updateCartLineItem(\n cartPayload: UpdateCartLineItemPayload,\n ): Promise<Cart | null> {\n try {\n const cartData = await updateCartLineItemWithPersistence(\n cartPayload,\n this.config.cookieName,\n );\n return cartData;\n } catch (error) {\n console.warn(\n `Failed to update ${this.config.gatewayName} cart line item:`,\n {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n lineId: cartPayload.line.id,\n quantity: cartPayload.line.quantity,\n },\n );\n return null;\n }\n }\n\n async removeCartLineItems(\n cartPayload: RemoveFromCartPayload,\n ): Promise<Cart | null> {\n try {\n const cartData = await removeCartLineItemsWithPersistence(\n cartPayload,\n this.config.cookieName,\n );\n return cartData;\n } catch (error) {\n console.warn(\n `Failed to remove ${this.config.gatewayName} cart line items:`,\n {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n lineIds: cartPayload.lineIds,\n },\n );\n return null;\n }\n }\n\n async updateCartDiscountCodes(\n cartPayload: UpdateCartDiscountCodesPayload,\n ): Promise<Cart | null> {\n console.warn(\n `${this.config.gatewayName} cart gateway does not yet support discount codes`,\n );\n // Return existing cart without changes\n try {\n return await this.getCart(cartPayload.cartId);\n } catch (error) {\n console.warn(\"Failed to fetch cart for discount code update:\", error);\n return null;\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { ReconciledCartLine } from \"../../gateways/canopy-api\";\nimport type {\n AddToCartPayload,\n Cart,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartDiscountCodesPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\nimport type { LineMergeFn } from \"../utils/cookie-cart-persistence\";\nimport type { CartGateway } from \"./cart-gateway\";\n\nimport { applyCartLineReconciliation } from \"../utils/cart-utils\";\nimport {\n addToCartWithPersistence,\n createCartWithPersistence,\n loadCartFromCookie,\n removeCartLineItemsWithPersistence,\n saveCartToCookie,\n updateCartLineItemWithPersistence,\n} from \"../utils/cookie-cart-persistence\";\n\n/**\n * Configuration for cookie-based cart gateways\n */\ninterface CookieCartConfig {\n cookieName: string;\n lineMergeFn: LineMergeFn;\n lineIdGenerator: () => string;\n cartIdGenerator: () => string;\n gatewayName: string; // For logging (e.g., \"Stripe\", \"Default\")\n // The catalog's per-line facts for this cart's variants. When present,\n // every cart write reconciles against it (reprice, clamp, drop\n // unavailable); absent means the gateway has no catalog to ask.\n reconcileLines?: (\n lines: { variantId: string; quantity: number }[],\n ) => Promise<ReconciledCartLine[]>;\n}\n\n/**\n * Abstract base class for cookie-based cart gateways.\n * Provides common error handling, logging, and cart operations.\n * Subclasses must provide gateway-specific configuration.\n */\nexport abstract class CookieBasedCartGateway implements CartGateway {\n protected abstract readonly config: CookieCartConfig;\n\n // Fails open (null) when the catalog can't answer: a canopy blip must not\n // block cart writes \u2014 checkout still enforces stock and price.\n private async fetchReconciledLines(\n cart: Cart,\n ): Promise<ReconciledCartLine[] | null> {\n const reconcileLines = this.config.reconcileLines;\n if (!reconcileLines) {\n return null;\n }\n const requestLines = cart.lines.flatMap((line) => {\n return line.merchandise\n ? [{ variantId: line.merchandise.id, quantity: line.quantity }]\n : [];\n });\n if (requestLines.length === 0) {\n return null;\n }\n try {\n return await reconcileLines(requestLines);\n } catch (error) {\n console.warn(\n `${this.config.gatewayName} cart reconciliation unavailable, keeping cart as written:`,\n error,\n );\n return null;\n }\n }\n\n // Catalog fetch failures fail open inside fetchReconciledLines; a\n // reconciliation mismatch from the apply still throws.\n private async reconcile(\n cart: Cart,\n { clampQuantities }: { clampQuantities: boolean },\n ): Promise<Cart> {\n const reconciledLines = await this.fetchReconciledLines(cart);\n if (!reconciledLines) {\n return cart;\n }\n return applyCartLineReconciliation(cart, reconciledLines, {\n clampQuantities,\n });\n }\n\n private reconcileForWrite = (cart: Cart): Promise<Cart> => {\n return this.reconcile(cart, { clampQuantities: true });\n };\n\n async reconcileStoredCart(cart: Cart): Promise<Cart> {\n // Reads reprice and drop unavailable lines but never clamp quantities \u2014\n // silent quantity changes are the write and checkout paths' job, where\n // they come with messaging.\n const reconciledCart = await this.reconcile(cart, {\n clampQuantities: false,\n });\n // Lines are the only thing reconciliation can change; cost derives from them.\n if (JSON.stringify(reconciledCart.lines) !== JSON.stringify(cart.lines)) {\n await saveCartToCookie(reconciledCart, this.config.cookieName);\n }\n return reconciledCart;\n }\n\n async getCart(cartId: string): Promise<Cart | null> {\n try {\n const cart = await loadCartFromCookie(this.config.cookieName);\n return cart?.id === cartId ? cart : null;\n } catch (error) {\n console.warn(\n `${this.config.gatewayName} cart ${cartId} not found, could not fetch cart:`,\n error,\n );\n return null;\n }\n }\n\n async createCart(\n lines?: CartLinePayload[],\n _discountCodes?: string[],\n ): Promise<Cart | null> {\n try {\n const cart = await createCartWithPersistence({\n cookieName: this.config.cookieName,\n lines,\n lineIdGenerator: this.config.lineIdGenerator,\n cartIdGenerator: this.config.cartIdGenerator,\n reconcile: this.reconcileForWrite,\n });\n return cart;\n } catch (createCartError) {\n console.warn(\n `Failed to create ${this.config.gatewayName} cart${lines?.length ? \" with lines\" : \"\"}:`,\n createCartError,\n );\n return null;\n }\n }\n\n async addToCart(cartPayload: AddToCartPayload): Promise<Cart | null> {\n try {\n const cartData = await addToCartWithPersistence(cartPayload, {\n cookieName: this.config.cookieName,\n mergeFn: this.config.lineMergeFn,\n lineIdGenerator: this.config.lineIdGenerator,\n reconcile: this.reconcileForWrite,\n });\n return cartData;\n } catch (error) {\n console.warn(`Failed to add items to ${this.config.gatewayName} cart:`, {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n itemCount: cartPayload.lines.length,\n });\n return null;\n }\n }\n\n async updateCartLineItem(\n cartPayload: UpdateCartLineItemPayload,\n ): Promise<Cart | null> {\n try {\n const cartData = await updateCartLineItemWithPersistence(\n cartPayload,\n this.config.cookieName,\n this.reconcileForWrite,\n );\n return cartData;\n } catch (error) {\n console.warn(\n `Failed to update ${this.config.gatewayName} cart line item:`,\n {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n lineId: cartPayload.line.id,\n quantity: cartPayload.line.quantity,\n },\n );\n return null;\n }\n }\n\n async removeCartLineItems(\n cartPayload: RemoveFromCartPayload,\n ): Promise<Cart | null> {\n try {\n const cartData = await removeCartLineItemsWithPersistence(\n cartPayload,\n this.config.cookieName,\n );\n return cartData;\n } catch (error) {\n console.warn(\n `Failed to remove ${this.config.gatewayName} cart line items:`,\n {\n error: error instanceof Error ? error.message : String(error),\n cartId: cartPayload.cartId,\n lineIds: cartPayload.lineIds,\n },\n );\n return null;\n }\n }\n\n async updateCartDiscountCodes(\n cartPayload: UpdateCartDiscountCodesPayload,\n ): Promise<Cart | null> {\n console.warn(\n `${this.config.gatewayName} cart gateway does not yet support discount codes`,\n );\n // Return existing cart without changes\n try {\n return await this.getCart(cartPayload.cartId);\n } catch (error) {\n console.warn(\"Failed to fetch cart for discount code update:\", error);\n return null;\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|