@replohq/sdk 1.9.1 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_vendor/schemas/generated/storefrontCountryCode.d.ts +10 -0
- package/_vendor/schemas/routing/locale.d.ts +1 -1
- package/cart/buy-now-action.d.ts +2 -0
- package/cart/buy-now-action.js +1 -0
- package/cart/cart-actions.d.ts +5 -1
- package/cart/cart-actions.js +21 -5
- package/cart/cart-country-context.d.ts +2 -0
- package/cart/cart-country-context.js +6 -0
- package/cart/cart-loader.d.ts +4 -1
- package/cart/cart-loader.js +5 -2
- package/cart/cart-provider.d.ts +3 -1
- package/cart/cart-provider.js +34 -14
- package/cart/cart-schema.d.ts +3 -0
- package/cart/cart-schema.js +2 -1
- package/cart/cart-types.d.ts +12 -0
- package/cart/gateways/cart-gateway.d.ts +10 -2
- package/cart/gateways/cookie-cart-gateway.d.ts +10 -2
- package/cart/gateways/cookie-cart-gateway.js +8 -1
- package/cart/gateways/resolver-cart-gateway.js +25 -2
- package/cart/hooks/use-buy-now.js +4 -1
- package/gateways/canopy-api.d.ts +6 -0
- package/gateways/canopy-api.js +8 -1
- package/lib/buildMetadata.js +3 -3
- package/loaders/collection-loader-args.d.ts +2 -1
- package/loaders/collection-products-loader-args.d.ts +2 -1
- package/loaders/markets.d.ts +1 -0
- package/loaders/markets.js +0 -0
- package/loaders/product-loader-args.d.ts +2 -1
- package/package.json +8 -4
- package/providers/replo-provider.d.ts +5 -1
- package/providers/replo-provider.js +5 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type StorefrontCountryCode = "AC" | "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AN" | "AO" | "AR" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GS" | "GT" | "GW" | "GY" | "HK" | "HM" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MK" | "ML" | "MM" | "MN" | "MO" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PS" | "PT" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TA" | "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "UM" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VN" | "VU" | "WF" | "WS" | "XK" | "YE" | "YT" | "ZA" | "ZM" | "ZW" | "ZZ";
|
|
2
|
+
export type ShopifyMarket = {
|
|
3
|
+
country?: StorefrontCountryCode;
|
|
4
|
+
};
|
|
5
|
+
export type Markets = {
|
|
6
|
+
shopify?: Partial<ShopifyMarket>;
|
|
7
|
+
};
|
|
8
|
+
export type CartBuyerIdentity = {
|
|
9
|
+
country: StorefrontCountryCode;
|
|
10
|
+
};
|
|
@@ -21,8 +21,8 @@ export declare const localeRoutingConfigSchema: z.ZodObject<{
|
|
|
21
21
|
locales: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
|
|
22
22
|
defaultLocale: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
23
23
|
detection: z.ZodDefault<z.ZodEnum<{
|
|
24
|
-
language: "language";
|
|
25
24
|
country: "country";
|
|
25
|
+
language: "language";
|
|
26
26
|
}>>;
|
|
27
27
|
urlMode: z.ZodDefault<z.ZodEnum<{
|
|
28
28
|
prefixed: "prefixed";
|
package/cart/buy-now-action.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
1
2
|
import type { CartLineAttribute } from "./cart-types";
|
|
2
3
|
/**
|
|
3
4
|
* Server action that handles the full buy-now flow:
|
|
@@ -19,6 +20,7 @@ export declare function buyNowAction(input: Parameters<typeof buyNowWithRecovery
|
|
|
19
20
|
}>;
|
|
20
21
|
export declare function buyNowWithRecoveryAction(input: {
|
|
21
22
|
checkoutAttemptId?: string;
|
|
23
|
+
country?: StorefrontCountryCode;
|
|
22
24
|
lineItems: {
|
|
23
25
|
variantId: string;
|
|
24
26
|
quantity: number;
|
package/cart/buy-now-action.js
CHANGED
|
@@ -114,6 +114,7 @@ async function buyNowWithRecoveryAction(input) {
|
|
|
114
114
|
const cart = await createCartAction({
|
|
115
115
|
lines: cartLines,
|
|
116
116
|
skipStoringCart: true,
|
|
117
|
+
country: input.country,
|
|
117
118
|
discountCodes: discountCodes.length > 0 ? discountCodes : void 0
|
|
118
119
|
});
|
|
119
120
|
if (!cart?.checkoutUrl) {
|
package/cart/cart-actions.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
1
2
|
import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartLineItemPayload } from "./cart-types";
|
|
2
3
|
/**
|
|
3
4
|
* Gets the existing cart or creates a new one if none exists. This is necessary
|
|
@@ -7,7 +8,9 @@ import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, Up
|
|
|
7
8
|
*
|
|
8
9
|
* @returns The existing cart or a newly created cart
|
|
9
10
|
*/
|
|
10
|
-
export declare function getOrCreateCartAction(
|
|
11
|
+
export declare function getOrCreateCartAction(options?: {
|
|
12
|
+
country?: StorefrontCountryCode;
|
|
13
|
+
}): Promise<Cart | null>;
|
|
11
14
|
/**
|
|
12
15
|
* Retrieves the current cart from cookies. Returns null if no cart exists or
|
|
13
16
|
* if the cart has expired/been deleted.
|
|
@@ -28,6 +31,7 @@ export declare function createCartAction(options?: {
|
|
|
28
31
|
lines?: CartLinePayload[];
|
|
29
32
|
skipStoringCart?: boolean;
|
|
30
33
|
discountCodes?: string[];
|
|
34
|
+
country?: StorefrontCountryCode;
|
|
31
35
|
}): Promise<Cart | null>;
|
|
32
36
|
/**
|
|
33
37
|
* Adds line items to an existing cart.
|
package/cart/cart-actions.js
CHANGED
|
@@ -11,13 +11,20 @@ import {
|
|
|
11
11
|
saveCartToCookie
|
|
12
12
|
} from "./utils/cookie-cart-persistence";
|
|
13
13
|
const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
|
|
14
|
-
async function getOrCreateCartAction() {
|
|
14
|
+
async function getOrCreateCartAction(options) {
|
|
15
15
|
const existingCart = await getCartAction();
|
|
16
16
|
if (existingCart) {
|
|
17
17
|
const cartGateway = await getCartGateway();
|
|
18
|
-
|
|
18
|
+
const reconciledCart = await cartGateway.reconcileStoredCart(existingCart);
|
|
19
|
+
if (options?.country && reconciledCart.buyerIdentity?.country !== options.country) {
|
|
20
|
+
return await cartGateway.updateCartBuyerIdentity({
|
|
21
|
+
cartId: reconciledCart.id,
|
|
22
|
+
buyerIdentity: { country: options.country }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return reconciledCart;
|
|
19
26
|
}
|
|
20
|
-
return await createCartAction();
|
|
27
|
+
return await createCartAction({ country: options?.country });
|
|
21
28
|
}
|
|
22
29
|
async function getCartAction() {
|
|
23
30
|
const checkoutProvider = await getCheckoutProvider();
|
|
@@ -44,10 +51,19 @@ async function getCartAction() {
|
|
|
44
51
|
return cart;
|
|
45
52
|
}
|
|
46
53
|
async function createCartAction(options) {
|
|
47
|
-
const {
|
|
54
|
+
const {
|
|
55
|
+
lines,
|
|
56
|
+
skipStoringCart = false,
|
|
57
|
+
discountCodes,
|
|
58
|
+
country
|
|
59
|
+
} = options ?? {};
|
|
48
60
|
const checkoutProvider = await getCheckoutProvider();
|
|
49
61
|
const cartGateway = await getCartGateway();
|
|
50
|
-
const cart = await cartGateway.createCart(
|
|
62
|
+
const cart = await cartGateway.createCart({
|
|
63
|
+
lines,
|
|
64
|
+
discountCodes,
|
|
65
|
+
buyerIdentity: country ? { country } : void 0
|
|
66
|
+
});
|
|
51
67
|
if (cart && !skipStoringCart) {
|
|
52
68
|
try {
|
|
53
69
|
if (checkoutProvider === "stripe") {
|
package/cart/cart-loader.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
1
2
|
/**
|
|
2
3
|
* Server component that loads the cart and provides it to child components
|
|
3
4
|
* through the CartProvider context. In development mode, skips initial
|
|
@@ -6,4 +7,6 @@
|
|
|
6
7
|
* @param children - Child components that will have access to the cart
|
|
7
8
|
* @returns A CartProvider component wrapping the children with cart data
|
|
8
9
|
*/
|
|
9
|
-
export declare function CartLoader({ children }: React.PropsWithChildren
|
|
10
|
+
export declare function CartLoader({ children, country, }: React.PropsWithChildren<{
|
|
11
|
+
country?: StorefrontCountryCode;
|
|
12
|
+
}>): Promise<import("react/jsx-runtime").JSX.Element>;
|
package/cart/cart-loader.js
CHANGED
|
@@ -2,13 +2,16 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { unstable_rethrow } from "next/navigation";
|
|
3
3
|
import { getCartAction } from "./cart-actions";
|
|
4
4
|
import { CartProvider } from "./cart-provider";
|
|
5
|
-
async function CartLoader({
|
|
5
|
+
async function CartLoader({
|
|
6
|
+
children,
|
|
7
|
+
country
|
|
8
|
+
}) {
|
|
6
9
|
const cart = process.env.NODE_ENV === "development" ? null : await getCartAction().catch((error) => {
|
|
7
10
|
unstable_rethrow(error);
|
|
8
11
|
console.warn("[Replo] Failed to load cart:", error);
|
|
9
12
|
return null;
|
|
10
13
|
});
|
|
11
|
-
return /* @__PURE__ */ jsx(CartProvider, { cart, children });
|
|
14
|
+
return /* @__PURE__ */ jsx(CartProvider, { cart, country, children });
|
|
12
15
|
}
|
|
13
16
|
export {
|
|
14
17
|
CartLoader
|
package/cart/cart-provider.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
1
2
|
import type { Cart, CartLine, CartLineAdjustment, CartLinePayload } from "./cart-types";
|
|
2
3
|
import React from "react";
|
|
3
4
|
export type CartOperation = "add" | "update" | "remove" | "discount";
|
|
@@ -93,9 +94,10 @@ export interface CartContextType {
|
|
|
93
94
|
*
|
|
94
95
|
* @returns A context provider wrapping the children with cart functionality
|
|
95
96
|
*/
|
|
96
|
-
export declare function CartProvider({ children, cart, }: React.PropsWithChildren<{
|
|
97
|
+
export declare function CartProvider({ children, cart, country, }: React.PropsWithChildren<{
|
|
97
98
|
/** Initial cart data from the server (can be null) */
|
|
98
99
|
cart: Cart | null;
|
|
100
|
+
country?: StorefrontCountryCode;
|
|
99
101
|
}>): import("react/jsx-runtime").JSX.Element;
|
|
100
102
|
/**
|
|
101
103
|
* @deprecated For internal use only. Use useAddToCart or useBuyNow
|
package/cart/cart-provider.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
updateCartDiscountCodesAction,
|
|
13
13
|
updateCartLineItemAction
|
|
14
14
|
} from "./cart-actions";
|
|
15
|
+
import { CartCountryContext } from "./cart-country-context";
|
|
15
16
|
import {
|
|
16
17
|
deriveAddToCartAdjustments,
|
|
17
18
|
deriveUpdateCartAdjustments
|
|
@@ -24,18 +25,27 @@ import { CartContextError, toCartError } from "./utils/cart-error";
|
|
|
24
25
|
import { recalculateCartCost } from "./utils/cart-utils";
|
|
25
26
|
import { createOptimisticSellingPlanAllocation } from "./utils/variant-to-cart-line";
|
|
26
27
|
let cartCreationPromise = null;
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
let cartCreationCountry;
|
|
29
|
+
function getOrCreateSharedCart(country) {
|
|
30
|
+
if (cartCreationPromise && cartCreationCountry === country) {
|
|
31
|
+
return cartCreationPromise;
|
|
32
|
+
}
|
|
33
|
+
const previousCreation = cartCreationPromise;
|
|
34
|
+
const creation = (async () => {
|
|
35
|
+
if (previousCreation) await previousCreation.catch(() => null);
|
|
36
|
+
const cart = await getOrCreateCartAction({ country });
|
|
29
37
|
if (!cart) {
|
|
30
38
|
throw new CartContextError({
|
|
31
39
|
message: "Your cart could not be loaded. Please try again."
|
|
32
40
|
});
|
|
33
41
|
}
|
|
34
42
|
return cart;
|
|
35
|
-
}).finally(() => {
|
|
36
|
-
cartCreationPromise = null;
|
|
43
|
+
})().finally(() => {
|
|
44
|
+
if (cartCreationPromise === creation) cartCreationPromise = null;
|
|
37
45
|
});
|
|
38
|
-
|
|
46
|
+
cartCreationCountry = country;
|
|
47
|
+
cartCreationPromise = creation;
|
|
48
|
+
return creation;
|
|
39
49
|
}
|
|
40
50
|
function addLineToCart({
|
|
41
51
|
cart,
|
|
@@ -84,7 +94,8 @@ const CartContext = React.createContext(
|
|
|
84
94
|
);
|
|
85
95
|
function CartProvider({
|
|
86
96
|
children,
|
|
87
|
-
cart
|
|
97
|
+
cart,
|
|
98
|
+
country
|
|
88
99
|
}) {
|
|
89
100
|
const [cartData, setCartData] = React.useState(cart);
|
|
90
101
|
const optimisticCart = React.useRef(cart);
|
|
@@ -101,6 +112,10 @@ function CartProvider({
|
|
|
101
112
|
const [error, setError] = React.useState(null);
|
|
102
113
|
const clearError = React.useCallback(() => setError(null), []);
|
|
103
114
|
const isMounted = React.useRef(true);
|
|
115
|
+
const marketSelection = React.useRef({ country });
|
|
116
|
+
if (marketSelection.current.country !== country) {
|
|
117
|
+
marketSelection.current = { country };
|
|
118
|
+
}
|
|
104
119
|
const runWithErrorState = async (operation, mutate) => {
|
|
105
120
|
setError(null);
|
|
106
121
|
try {
|
|
@@ -175,18 +190,22 @@ function CartProvider({
|
|
|
175
190
|
});
|
|
176
191
|
};
|
|
177
192
|
const initializeCart = React.useCallback(async () => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
const selection = marketSelection.current;
|
|
194
|
+
const loadedCart = await getOrCreateSharedCart(country);
|
|
195
|
+
if (!isMounted.current || marketSelection.current !== selection) {
|
|
196
|
+
throw new DOMException(
|
|
197
|
+
"Cart initialization was superseded",
|
|
198
|
+
"AbortError"
|
|
199
|
+
);
|
|
181
200
|
}
|
|
182
201
|
serverCart.current = loadedCart;
|
|
183
202
|
if (pendingMutations.current === 0) updateCartData(loadedCart);
|
|
184
203
|
return loadedCart;
|
|
185
|
-
}, [updateCartData]);
|
|
204
|
+
}, [country, updateCartData]);
|
|
186
205
|
React.useEffect(() => {
|
|
187
206
|
isMounted.current = true;
|
|
188
207
|
void initializeCart().catch((error2) => {
|
|
189
|
-
if (isMounted.current) {
|
|
208
|
+
if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
|
|
190
209
|
console.error("[Replo] Error getting or creating cart:", error2);
|
|
191
210
|
}
|
|
192
211
|
});
|
|
@@ -400,7 +419,8 @@ function CartProvider({
|
|
|
400
419
|
const cart2 = await createCartAction({
|
|
401
420
|
lines,
|
|
402
421
|
skipStoringCart: true,
|
|
403
|
-
discountCodes
|
|
422
|
+
discountCodes,
|
|
423
|
+
country
|
|
404
424
|
});
|
|
405
425
|
if (cart2) {
|
|
406
426
|
return cart2;
|
|
@@ -412,7 +432,7 @@ function CartProvider({
|
|
|
412
432
|
}
|
|
413
433
|
};
|
|
414
434
|
const checkoutUrl = cartData?.checkoutUrl ?? "/";
|
|
415
|
-
return /* @__PURE__ */ jsx(
|
|
435
|
+
return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(
|
|
416
436
|
CartContext.Provider,
|
|
417
437
|
{
|
|
418
438
|
value: {
|
|
@@ -436,7 +456,7 @@ function CartProvider({
|
|
|
436
456
|
},
|
|
437
457
|
children
|
|
438
458
|
}
|
|
439
|
-
);
|
|
459
|
+
) });
|
|
440
460
|
}
|
|
441
461
|
function useCartInternal() {
|
|
442
462
|
const context = React.useContext(CartContext);
|
package/cart/cart-schema.d.ts
CHANGED
|
@@ -74,5 +74,8 @@ export declare const cartSchema: z.ZodObject<{
|
|
|
74
74
|
discountedAmount: z.ZodNumber;
|
|
75
75
|
title: z.ZodNullable<z.ZodString>;
|
|
76
76
|
}, z.core.$strip>>>;
|
|
77
|
+
buyerIdentity: z.ZodOptional<z.ZodObject<{
|
|
78
|
+
country: z.ZodNullable<z.ZodString>;
|
|
79
|
+
}, z.core.$strip>>;
|
|
77
80
|
}, z.core.$strip>;
|
|
78
81
|
export type Cart = z.infer<typeof cartSchema>;
|
package/cart/cart-schema.js
CHANGED
|
@@ -66,7 +66,8 @@ const cartSchema = z.object({
|
|
|
66
66
|
cost: cartCostSchema,
|
|
67
67
|
checkoutUrl: z.string().optional(),
|
|
68
68
|
discountCodes: z.array(cartDiscountCodeSchema).optional(),
|
|
69
|
-
discountAllocations: z.array(cartDiscountAllocationSchema).optional()
|
|
69
|
+
discountAllocations: z.array(cartDiscountAllocationSchema).optional(),
|
|
70
|
+
buyerIdentity: z.object({ country: z.string().nullable() }).optional()
|
|
70
71
|
});
|
|
71
72
|
export {
|
|
72
73
|
cartSchema
|
package/cart/cart-types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
import type { SellingPlanGroup } from "../loaders/loader-schemas";
|
|
6
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
6
7
|
export interface Image {
|
|
7
8
|
url: string;
|
|
8
9
|
altText: string | null;
|
|
@@ -118,6 +119,14 @@ export interface Cart {
|
|
|
118
119
|
checkoutUrl?: string;
|
|
119
120
|
discountCodes?: CartDiscountCode[];
|
|
120
121
|
discountAllocations?: CartDiscountAllocation[];
|
|
122
|
+
/**
|
|
123
|
+
* The market Shopify has the cart in. Shopify prices `cost` and the checkout
|
|
124
|
+
* from this, not from the catalog country the product loaders pass, so it is
|
|
125
|
+
* what tells a market site whether the cart already matches the page.
|
|
126
|
+
*/
|
|
127
|
+
buyerIdentity?: {
|
|
128
|
+
country: string | null;
|
|
129
|
+
};
|
|
121
130
|
}
|
|
122
131
|
/**
|
|
123
132
|
* How a cart write differed from what the shopper asked for, derived by
|
|
@@ -157,3 +166,6 @@ export interface UpdateCartDiscountCodesPayload {
|
|
|
157
166
|
cartId: string;
|
|
158
167
|
discountCodes: string[];
|
|
159
168
|
}
|
|
169
|
+
export interface CartBuyerIdentity {
|
|
170
|
+
country: StorefrontCountryCode;
|
|
171
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
1
|
+
import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
2
2
|
export interface CartGateway {
|
|
3
3
|
getCart(cartId: string): Promise<Cart | null>;
|
|
4
4
|
/**
|
|
@@ -8,8 +8,16 @@ export interface CartGateway {
|
|
|
8
8
|
* cart unchanged.
|
|
9
9
|
*/
|
|
10
10
|
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
11
|
-
createCart(
|
|
11
|
+
createCart(options?: {
|
|
12
|
+
lines?: CartLinePayload[];
|
|
13
|
+
discountCodes?: string[];
|
|
14
|
+
buyerIdentity?: CartBuyerIdentity;
|
|
15
|
+
}): Promise<Cart | null>;
|
|
12
16
|
addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
|
|
17
|
+
updateCartBuyerIdentity(payload: {
|
|
18
|
+
cartId: string;
|
|
19
|
+
buyerIdentity: CartBuyerIdentity;
|
|
20
|
+
}): Promise<Cart | null>;
|
|
13
21
|
updateCartLineItem(cartPayload: UpdateCartLineItemPayload): Promise<Cart | null>;
|
|
14
22
|
removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;
|
|
15
23
|
updateCartDiscountCodes(cartPayload: UpdateCartDiscountCodesPayload): Promise<Cart | null>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReconciledCartLine } from "../../gateways/canopy-api";
|
|
2
|
-
import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
2
|
+
import type { AddToCartPayload, Cart, CartBuyerIdentity, CartLinePayload, RemoveFromCartPayload, UpdateCartDiscountCodesPayload, UpdateCartLineItemPayload } from "../cart-types";
|
|
3
3
|
import type { LineMergeFn } from "../utils/cookie-cart-persistence";
|
|
4
4
|
import type { CartGateway } from "./cart-gateway";
|
|
5
5
|
/**
|
|
@@ -28,10 +28,18 @@ export declare abstract class CookieBasedCartGateway implements CartGateway {
|
|
|
28
28
|
private reconcileForWrite;
|
|
29
29
|
reconcileStoredCart(cart: Cart): Promise<Cart>;
|
|
30
30
|
getCart(cartId: string): Promise<Cart | null>;
|
|
31
|
-
createCart(lines
|
|
31
|
+
createCart({ lines, }?: {
|
|
32
|
+
lines?: CartLinePayload[];
|
|
33
|
+
discountCodes?: string[];
|
|
34
|
+
buyerIdentity?: CartBuyerIdentity;
|
|
35
|
+
}): Promise<Cart | null>;
|
|
32
36
|
addToCart(cartPayload: AddToCartPayload): Promise<Cart | null>;
|
|
33
37
|
updateCartLineItem(cartPayload: UpdateCartLineItemPayload): Promise<Cart | null>;
|
|
34
38
|
removeCartLineItems(cartPayload: RemoveFromCartPayload): Promise<Cart | null>;
|
|
35
39
|
updateCartDiscountCodes(cartPayload: UpdateCartDiscountCodesPayload): Promise<Cart | null>;
|
|
40
|
+
updateCartBuyerIdentity({ cartId, }: {
|
|
41
|
+
cartId: string;
|
|
42
|
+
buyerIdentity: CartBuyerIdentity;
|
|
43
|
+
}): Promise<Cart | null>;
|
|
36
44
|
}
|
|
37
45
|
export {};
|
|
@@ -62,7 +62,9 @@ class CookieBasedCartGateway {
|
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
async createCart(
|
|
65
|
+
async createCart({
|
|
66
|
+
lines
|
|
67
|
+
} = {}) {
|
|
66
68
|
try {
|
|
67
69
|
const cart = await createCartWithPersistence({
|
|
68
70
|
cookieName: this.config.cookieName,
|
|
@@ -149,6 +151,11 @@ class CookieBasedCartGateway {
|
|
|
149
151
|
return null;
|
|
150
152
|
}
|
|
151
153
|
}
|
|
154
|
+
async updateCartBuyerIdentity({
|
|
155
|
+
cartId
|
|
156
|
+
}) {
|
|
157
|
+
return this.getCart(cartId);
|
|
158
|
+
}
|
|
152
159
|
}
|
|
153
160
|
export {
|
|
154
161
|
CookieBasedCartGateway
|
|
@@ -8,7 +8,11 @@ class ResolverCartGateway {
|
|
|
8
8
|
const gateway = await getCanopyApiGateway();
|
|
9
9
|
return await gateway.cartGet({ cartId });
|
|
10
10
|
}
|
|
11
|
-
async createCart(
|
|
11
|
+
async createCart({
|
|
12
|
+
lines,
|
|
13
|
+
discountCodes,
|
|
14
|
+
buyerIdentity
|
|
15
|
+
} = {}) {
|
|
12
16
|
try {
|
|
13
17
|
const gateway = await getCanopyApiGateway();
|
|
14
18
|
return await gateway.cartCreate({
|
|
@@ -21,7 +25,8 @@ class ResolverCartGateway {
|
|
|
21
25
|
})),
|
|
22
26
|
sellingPlanId: line.sellingPlanId
|
|
23
27
|
})),
|
|
24
|
-
discountCodes
|
|
28
|
+
discountCodes,
|
|
29
|
+
country: buyerIdentity?.country
|
|
25
30
|
});
|
|
26
31
|
} catch (error) {
|
|
27
32
|
console.warn("Failed to create cart via canopy-api:", error);
|
|
@@ -94,6 +99,24 @@ class ResolverCartGateway {
|
|
|
94
99
|
return null;
|
|
95
100
|
}
|
|
96
101
|
}
|
|
102
|
+
async updateCartBuyerIdentity({
|
|
103
|
+
cartId,
|
|
104
|
+
buyerIdentity
|
|
105
|
+
}) {
|
|
106
|
+
try {
|
|
107
|
+
const gateway = await getCanopyApiGateway();
|
|
108
|
+
return await gateway.cartUpdateBuyerIdentity({
|
|
109
|
+
cartId,
|
|
110
|
+
country: buyerIdentity.country
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn(
|
|
114
|
+
"Failed to update cart buyer identity via canopy-api:",
|
|
115
|
+
error
|
|
116
|
+
);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
97
120
|
}
|
|
98
121
|
async function createResolverCartGateway() {
|
|
99
122
|
return new ResolverCartGateway();
|
|
@@ -4,6 +4,7 @@ import { useClientValue } from "../../hooks/use-client-value";
|
|
|
4
4
|
import { CanopyError } from "../../lib/canopy-error";
|
|
5
5
|
import { navigateExternal } from "../../lib/external-navigation";
|
|
6
6
|
import { buyNowWithRecoveryAction } from "../buy-now-action";
|
|
7
|
+
import { CartCountryContext } from "../cart-country-context";
|
|
7
8
|
const REPLO_LOCAL_STORAGE_SESSION_KEY = "_replo_sid";
|
|
8
9
|
const REPLO_SESSION_ID_KEY = "rsid";
|
|
9
10
|
const REPLO_CLIENT_ID_KEY = "rclid";
|
|
@@ -13,6 +14,7 @@ class CheckoutAttemptError extends CanopyError {
|
|
|
13
14
|
}
|
|
14
15
|
const checkoutAttempts = /* @__PURE__ */ new Map();
|
|
15
16
|
function useBuyNow() {
|
|
17
|
+
const country = React.useContext(CartCountryContext);
|
|
16
18
|
const reploSessionString = useClientValue(
|
|
17
19
|
() => localStorage.getItem(REPLO_LOCAL_STORAGE_SESSION_KEY),
|
|
18
20
|
null
|
|
@@ -25,6 +27,7 @@ function useBuyNow() {
|
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
29
|
const checkoutInput = {
|
|
30
|
+
country,
|
|
28
31
|
lineItems: variants.map((variant) => ({
|
|
29
32
|
variantId: variant.variantId,
|
|
30
33
|
quantity: variant.quantity ?? 1,
|
|
@@ -82,7 +85,7 @@ function useBuyNow() {
|
|
|
82
85
|
navigateExternal(url.toString());
|
|
83
86
|
return { result: "redirected" };
|
|
84
87
|
},
|
|
85
|
-
[reploSessionString]
|
|
88
|
+
[reploSessionString, country]
|
|
86
89
|
);
|
|
87
90
|
return { buyNow };
|
|
88
91
|
}
|
package/gateways/canopy-api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Cart } from "../cart/cart-schema";
|
|
2
|
+
import type { StorefrontCountryCode } from "../loaders/markets";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { CanopyError } from "../lib/canopy-error";
|
|
4
5
|
type CanopyApiGatewayErrorConfig = {
|
|
@@ -71,6 +72,7 @@ declare class CanopyApiGateway {
|
|
|
71
72
|
sellingPlanId?: string | null;
|
|
72
73
|
}[];
|
|
73
74
|
discountCodes?: string[];
|
|
75
|
+
country?: StorefrontCountryCode;
|
|
74
76
|
}): Promise<Cart>;
|
|
75
77
|
cartAddLines({ cartId, lines, }: {
|
|
76
78
|
cartId: string;
|
|
@@ -104,6 +106,10 @@ declare class CanopyApiGateway {
|
|
|
104
106
|
cartId: string;
|
|
105
107
|
discountCodes: string[];
|
|
106
108
|
}): Promise<Cart>;
|
|
109
|
+
cartUpdateBuyerIdentity({ cartId, country, }: {
|
|
110
|
+
cartId: string;
|
|
111
|
+
country: StorefrontCountryCode;
|
|
112
|
+
}): Promise<Cart>;
|
|
107
113
|
}
|
|
108
114
|
export declare function getCanopyApiGateway(): Promise<CanopyApiGateway>;
|
|
109
115
|
export {};
|
package/gateways/canopy-api.js
CHANGED
|
@@ -194,7 +194,8 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
|
|
|
194
194
|
async cartCreate(options) {
|
|
195
195
|
return this._cartPost("/create", {
|
|
196
196
|
lines: options?.lines,
|
|
197
|
-
discountCodes: options?.discountCodes
|
|
197
|
+
discountCodes: options?.discountCodes,
|
|
198
|
+
country: options?.country
|
|
198
199
|
});
|
|
199
200
|
}
|
|
200
201
|
async cartAddLines({
|
|
@@ -221,6 +222,12 @@ If you're running locally, make sure the canopy-api cloudflare worker is running
|
|
|
221
222
|
}) {
|
|
222
223
|
return this._cartPost("/update-discount-codes", { cartId, discountCodes });
|
|
223
224
|
}
|
|
225
|
+
async cartUpdateBuyerIdentity({
|
|
226
|
+
cartId,
|
|
227
|
+
country
|
|
228
|
+
}) {
|
|
229
|
+
return this._cartPost("/update-buyer-identity", { cartId, country });
|
|
230
|
+
}
|
|
224
231
|
}
|
|
225
232
|
async function getCanopyApiGateway() {
|
|
226
233
|
return CanopyApiGateway.setup();
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-09-17T18:
|
|
5
|
+
"commit": "b00423087f2b01a9cc8fb4700d453161b9ec846c",
|
|
6
|
+
"builtAt": "2026-09-17T18:32:43.736Z"
|
|
7
7
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "./markets";
|
|
1
2
|
export type CollectionLoaderArgs = {
|
|
2
3
|
handle?: string;
|
|
3
4
|
collectionGid?: string;
|
|
4
5
|
language?: string;
|
|
5
|
-
country?:
|
|
6
|
+
country?: StorefrontCountryCode;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
8
9
|
* Builds the single object used as BOTH the React Query key and the loader
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "./markets";
|
|
1
2
|
export type CollectionProductsLoaderArgs = {
|
|
2
3
|
collectionGid: string;
|
|
3
4
|
first?: number;
|
|
4
5
|
after?: string;
|
|
5
6
|
language?: string;
|
|
6
|
-
country?:
|
|
7
|
+
country?: StorefrontCountryCode;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* Builds the single object used as BOTH the React Query key and the loader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Markets, ShopifyMarket, StorefrontCountryCode, } from "../_vendor/schemas/generated/storefrontCountryCode";
|
|
File without changes
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StorefrontCountryCode } from "./markets";
|
|
1
2
|
export type MetafieldIdentifier = {
|
|
2
3
|
namespace: string;
|
|
3
4
|
key: string;
|
|
@@ -8,7 +9,7 @@ export type ProductLoaderArgs = {
|
|
|
8
9
|
productMetafieldIdentifiers?: MetafieldIdentifier[];
|
|
9
10
|
variantMetafieldIdentifiers?: MetafieldIdentifier[];
|
|
10
11
|
language?: string;
|
|
11
|
-
country?:
|
|
12
|
+
country?: StorefrontCountryCode;
|
|
12
13
|
};
|
|
13
14
|
/**
|
|
14
15
|
* Builds the single object used as BOTH the React Query key and the loader
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.9.
|
|
6
|
+
"version": "1.9.2",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-09-17T18:
|
|
8
|
+
"commit": "b00423087f2b01a9cc8fb4700d453161b9ec846c",
|
|
9
|
+
"builtAt": "2026-09-17T18:32:43.736Z"
|
|
10
10
|
},
|
|
11
11
|
"description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
|
|
12
12
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -213,6 +213,10 @@
|
|
|
213
213
|
"types": "./loaders/collection-products-loader-args.d.ts",
|
|
214
214
|
"default": "./loaders/collection-products-loader-args.js"
|
|
215
215
|
},
|
|
216
|
+
"./loaders/markets": {
|
|
217
|
+
"types": "./loaders/markets.d.ts",
|
|
218
|
+
"default": "./loaders/markets.js"
|
|
219
|
+
},
|
|
216
220
|
"./orders/order-status": {
|
|
217
221
|
"types": "./orders/order-status.d.ts",
|
|
218
222
|
"default": "./orders/order-status.js"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Markets } from "../loaders/markets";
|
|
1
2
|
/**
|
|
2
3
|
* Root provider for a Replo site. Mount it once in `app/layout.tsx`, wrapping
|
|
3
4
|
* the whole app. It sets up the React Query client every data loader reads
|
|
@@ -17,4 +18,7 @@
|
|
|
17
18
|
* }
|
|
18
19
|
* ```
|
|
19
20
|
*/
|
|
20
|
-
export declare function ReploProvider({ children }: React.PropsWithChildren
|
|
21
|
+
export declare function ReploProvider({ children, markets, }: React.PropsWithChildren<{
|
|
22
|
+
/** Nested so Shopify and later Replo-product markets do not share one `country`. */
|
|
23
|
+
markets?: Markets;
|
|
24
|
+
}>): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
@@ -6,7 +6,10 @@ import { getCheckoutProvider } from "../lib/integration-utils";
|
|
|
6
6
|
import { LoaderErrorDetailsProvider } from "../loaders/loader-error-boundary";
|
|
7
7
|
import { shouldShowLoaderErrorDetails } from "../loaders/loader-error-details";
|
|
8
8
|
import { QueryProvider } from "./query-provider";
|
|
9
|
-
async function ReploProvider({
|
|
9
|
+
async function ReploProvider({
|
|
10
|
+
children,
|
|
11
|
+
markets
|
|
12
|
+
}) {
|
|
10
13
|
let checkoutProvider = "none";
|
|
11
14
|
try {
|
|
12
15
|
checkoutProvider = await getCheckoutProvider();
|
|
@@ -15,7 +18,7 @@ async function ReploProvider({ children }) {
|
|
|
15
18
|
}
|
|
16
19
|
return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
|
|
17
20
|
/* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
|
|
18
|
-
/* @__PURE__ */ jsx(CartLoader, { children })
|
|
21
|
+
/* @__PURE__ */ jsx(CartLoader, { country: markets?.shopify?.country, children })
|
|
19
22
|
] }) }) });
|
|
20
23
|
}
|
|
21
24
|
export {
|