@replohq/sdk 1.10.1 → 1.10.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.
@@ -11,6 +11,14 @@ import type { AddToCartPayload, Cart, CartLinePayload, RemoveFromCartPayload, Up
11
11
  export declare function getOrCreateCartAction(options?: {
12
12
  country?: StorefrontCountryCode;
13
13
  }): Promise<Cart | null>;
14
+ /**
15
+ * Reconciles an existing persisted cart without creating one when none exists.
16
+ *
17
+ * @returns The reconciled cart, or null when no persisted cart exists
18
+ */
19
+ export declare function reconcileCartAction(options?: {
20
+ country?: StorefrontCountryCode;
21
+ }): Promise<Cart | null>;
14
22
  /**
15
23
  * Retrieves the current cart from cookies. Returns null if no cart exists or
16
24
  * if the cart has expired/been deleted.
@@ -11,21 +11,31 @@ import {
11
11
  saveCartToCookie
12
12
  } from "./utils/cookie-cart-persistence";
13
13
  const SHOPIFY_CART_COOKIE_NAME = "replo_cart_id";
14
+ async function reconcileExistingCart(cart, country) {
15
+ const cartGateway = await getCartGateway();
16
+ const reconciledCart = await cartGateway.reconcileStoredCart(cart);
17
+ if (country && reconciledCart.buyerIdentity?.country !== country) {
18
+ return await cartGateway.updateCartBuyerIdentity({
19
+ cartId: reconciledCart.id,
20
+ buyerIdentity: { country }
21
+ });
22
+ }
23
+ return reconciledCart;
24
+ }
14
25
  async function getOrCreateCartAction(options) {
15
26
  const existingCart = await getCartAction();
16
27
  if (existingCart) {
17
- const cartGateway = await getCartGateway();
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;
28
+ return await reconcileExistingCart(existingCart, options?.country);
26
29
  }
27
30
  return await createCartAction({ country: options?.country });
28
31
  }
32
+ async function reconcileCartAction(options) {
33
+ const existingCart = await getCartAction();
34
+ if (!existingCart) {
35
+ return null;
36
+ }
37
+ return await reconcileExistingCart(existingCart, options?.country);
38
+ }
29
39
  async function getCartAction() {
30
40
  const checkoutProvider = await getCheckoutProvider();
31
41
  if (checkoutProvider === "stripe") {
@@ -103,6 +113,7 @@ export {
103
113
  createCartAction,
104
114
  getCartAction,
105
115
  getOrCreateCartAction,
116
+ reconcileCartAction,
106
117
  removeCartLineItemsAction,
107
118
  updateCartDiscountCodesAction,
108
119
  updateCartLineItemAction
@@ -7,6 +7,7 @@ import type { StorefrontCountryCode } from "../loaders/markets";
7
7
  * @param children - Child components that will have access to the cart
8
8
  * @returns A CartProvider component wrapping the children with cart data
9
9
  */
10
- export declare function CartLoader({ children, country, }: React.PropsWithChildren<{
10
+ export declare function CartLoader({ children, country, shouldReconcileSuppliedCart, }: React.PropsWithChildren<{
11
11
  country?: StorefrontCountryCode;
12
+ shouldReconcileSuppliedCart: boolean;
12
13
  }>): Promise<import("react/jsx-runtime").JSX.Element>;
@@ -2,16 +2,18 @@ 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
+ import { SuppliedCartReconciliationBoundary } from "./supplied-cart-reconciliation";
5
6
  async function CartLoader({
6
7
  children,
7
- country
8
+ country,
9
+ shouldReconcileSuppliedCart
8
10
  }) {
9
11
  const cart = false ? null : await getCartAction().catch((error) => {
10
12
  unstable_rethrow(error);
11
13
  console.warn("[Replo] Failed to load cart:", error);
12
14
  return null;
13
15
  });
14
- return /* @__PURE__ */ jsx(CartProvider, { cart, country, children });
16
+ return /* @__PURE__ */ jsx(SuppliedCartReconciliationBoundary, { enabled: shouldReconcileSuppliedCart, children: /* @__PURE__ */ jsx(CartProvider, { cart, country, children }) });
15
17
  }
16
18
  export {
17
19
  CartLoader
@@ -8,11 +8,13 @@ import {
8
8
  createCartAction,
9
9
  getCartAction,
10
10
  getOrCreateCartAction,
11
+ reconcileCartAction,
11
12
  removeCartLineItemsAction,
12
13
  updateCartDiscountCodesAction,
13
14
  updateCartLineItemAction
14
15
  } from "./cart-actions";
15
16
  import { CartCountryContext } from "./cart-country-context";
17
+ import { useShouldReconcileSuppliedCart } from "./supplied-cart-reconciliation";
16
18
  import {
17
19
  deriveAddToCartAdjustments,
18
20
  deriveUpdateCartAdjustments
@@ -89,6 +91,25 @@ function removeLineFromCart(cart, lineId) {
89
91
  const updatedLines = cart.lines.filter((line) => line.id !== lineId);
90
92
  return recalculateCartCost({ ...cart, lines: updatedLines });
91
93
  }
94
+ async function createBuyNowCart({
95
+ lines,
96
+ discountCodes,
97
+ country
98
+ }) {
99
+ const cart = await createCartAction({
100
+ lines,
101
+ skipStoringCart: true,
102
+ discountCodes,
103
+ country
104
+ });
105
+ if (!cart) {
106
+ console.warn(
107
+ "[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists."
108
+ );
109
+ return null;
110
+ }
111
+ return cart;
112
+ }
92
113
  const CartContext = React.createContext(
93
114
  void 0
94
115
  );
@@ -97,6 +118,9 @@ function CartProvider({
97
118
  cart,
98
119
  country
99
120
  }) {
121
+ const isSuppliedCart = Boolean(cart);
122
+ const isInternalSuppliedCartReconciliationEnabled = useShouldReconcileSuppliedCart();
123
+ const shouldReconcileSuppliedCart = isSuppliedCart && (isInternalSuppliedCartReconciliationEnabled || Boolean(country));
100
124
  const [cartData, setCartData] = React.useState(cart);
101
125
  const optimisticCart = React.useRef(cart);
102
126
  const updateCartData = React.useCallback(
@@ -116,79 +140,110 @@ function CartProvider({
116
140
  if (marketSelection.current.country !== country) {
117
141
  marketSelection.current = { country };
118
142
  }
119
- const runWithErrorState = async (operation, mutate) => {
120
- setError(null);
121
- try {
122
- return await mutate();
123
- } catch (caught) {
124
- console.error(`[Replo] Cart ${operation} failed:`, caught);
125
- setError(toCartError(operation, caught));
126
- throw caught;
127
- }
128
- };
143
+ const runWithErrorState = React.useCallback(
144
+ async (operation, mutate) => {
145
+ setError(null);
146
+ try {
147
+ return await mutate();
148
+ } catch (caught) {
149
+ console.error(`[Replo] Cart ${operation} failed:`, caught);
150
+ setError(toCartError(operation, caught));
151
+ throw caught;
152
+ }
153
+ },
154
+ []
155
+ );
129
156
  const analytics = useAnalyticsOptional();
130
157
  const serverCart = React.useRef(cart);
131
158
  const needsRefresh = React.useRef(false);
132
159
  const mutationQueue = React.useRef(Promise.resolve());
133
160
  const pendingMutations = React.useRef(0);
134
161
  const optimisticLines = React.useRef(/* @__PURE__ */ new Map());
135
- const enqueueMutation = async (save) => {
136
- pendingMutations.current += 1;
137
- const request = mutationQueue.current.then(async () => {
138
- await cartCreationPromise?.catch(() => null);
139
- if (needsRefresh.current) {
140
- const restoredCart = await getCartAction();
141
- if (!restoredCart)
142
- throw new CartContextError({
143
- message: "Your cart could not be refreshed. Please reload before changing it."
144
- });
145
- serverCart.current = restoredCart;
146
- needsRefresh.current = false;
147
- }
148
- if (!serverCart.current) {
149
- throw new CartContextError({
150
- message: "Your cart could not be loaded. Please try again."
151
- });
152
- }
162
+ const lastReconciliationCountry = React.useRef(false);
163
+ const startSuppliedCartReconciliation = React.useCallback(() => {
164
+ if (!shouldReconcileSuppliedCart || lastReconciliationCountry.current === country) {
165
+ return;
166
+ }
167
+ lastReconciliationCountry.current = country;
168
+ mutationQueue.current = mutationQueue.current.then(async () => {
153
169
  try {
154
- const savedCart = await save(serverCart.current);
155
- if (!savedCart)
156
- throw new CartContextError({
157
- message: "Your cart could not be saved. Please try again."
158
- });
159
- serverCart.current = savedCart;
170
+ const reconciledCart = await reconcileCartAction({ country });
171
+ if (!reconciledCart) {
172
+ return;
173
+ }
174
+ serverCart.current = reconciledCart;
175
+ if (isMounted.current && pendingMutations.current === 0) {
176
+ updateCartData(reconciledCart);
177
+ }
160
178
  } catch (error2) {
161
- const restoredCart = await getCartAction().catch(() => null);
162
- needsRefresh.current = !restoredCart;
163
- if (restoredCart) serverCart.current = restoredCart;
164
- throw error2;
179
+ console.warn("[Replo] Failed to reconcile supplied cart:", error2);
165
180
  }
166
181
  });
167
- mutationQueue.current = request.catch(() => {
168
- }).then(() => {
169
- pendingMutations.current -= 1;
170
- if (pendingMutations.current === 0) {
171
- reportedLineQuantities.current.clear();
172
- optimisticLines.current.clear();
173
- updateCartData(serverCart.current);
174
- }
175
- });
176
- await request;
177
- };
178
- const findLine = ({
179
- currentCart,
180
- lineId,
181
- originalLine
182
- }) => {
183
- const existingLine = currentCart.lines.find((line) => line.id === lineId);
184
- if (existingLine) return existingLine;
185
- const optimisticLine = optimisticLines.current.get(lineId);
186
- const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;
187
- const sellingPlanId = optimisticLine?.sellingPlanId ?? originalLine?.sellingPlanAllocation?.sellingPlan.id ?? null;
188
- return currentCart.lines.find((line) => {
189
- return line.merchandise?.id === merchandiseId && (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId;
190
- });
191
- };
182
+ }, [country, shouldReconcileSuppliedCart, updateCartData]);
183
+ const enqueueMutation = React.useCallback(
184
+ async (save) => {
185
+ startSuppliedCartReconciliation();
186
+ pendingMutations.current += 1;
187
+ const request = mutationQueue.current.then(async () => {
188
+ await cartCreationPromise?.catch(() => null);
189
+ if (needsRefresh.current) {
190
+ const restoredCart = await getCartAction();
191
+ if (!restoredCart)
192
+ throw new CartContextError({
193
+ message: "Your cart could not be refreshed. Please reload before changing it."
194
+ });
195
+ serverCart.current = restoredCart;
196
+ needsRefresh.current = false;
197
+ }
198
+ if (!serverCart.current) {
199
+ throw new CartContextError({
200
+ message: "Your cart could not be loaded. Please try again."
201
+ });
202
+ }
203
+ try {
204
+ const savedCart = await save(serverCart.current);
205
+ if (!savedCart)
206
+ throw new CartContextError({
207
+ message: "Your cart could not be saved. Please try again."
208
+ });
209
+ serverCart.current = savedCart;
210
+ } catch (error2) {
211
+ const restoredCart = await getCartAction().catch(() => null);
212
+ needsRefresh.current = !restoredCart;
213
+ if (restoredCart) serverCart.current = restoredCart;
214
+ throw error2;
215
+ }
216
+ });
217
+ mutationQueue.current = request.catch(() => {
218
+ }).then(() => {
219
+ pendingMutations.current -= 1;
220
+ if (pendingMutations.current === 0) {
221
+ reportedLineQuantities.current.clear();
222
+ optimisticLines.current.clear();
223
+ updateCartData(serverCart.current);
224
+ }
225
+ });
226
+ await request;
227
+ },
228
+ [startSuppliedCartReconciliation, updateCartData]
229
+ );
230
+ const findLine = React.useCallback(
231
+ ({
232
+ currentCart,
233
+ lineId,
234
+ originalLine
235
+ }) => {
236
+ const existingLine = currentCart.lines.find((line) => line.id === lineId);
237
+ if (existingLine) return existingLine;
238
+ const optimisticLine = optimisticLines.current.get(lineId);
239
+ const merchandiseId = optimisticLine?.id ?? originalLine?.merchandise?.id;
240
+ const sellingPlanId = optimisticLine?.sellingPlanId ?? originalLine?.sellingPlanAllocation?.sellingPlan.id ?? null;
241
+ return currentCart.lines.find((line) => {
242
+ return line.merchandise?.id === merchandiseId && (line.sellingPlanAllocation?.sellingPlan.id ?? null) === sellingPlanId;
243
+ });
244
+ },
245
+ []
246
+ );
192
247
  const initializeCart = React.useCallback(async () => {
193
248
  const selection = marketSelection.current;
194
249
  const loadedCart = await getOrCreateSharedCart(country);
@@ -204,16 +259,19 @@ function CartProvider({
204
259
  }, [country, updateCartData]);
205
260
  React.useEffect(() => {
206
261
  isMounted.current = true;
207
- void initializeCart().catch((error2) => {
208
- if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
209
- console.error("[Replo] Error getting or creating cart:", error2);
210
- }
211
- });
262
+ if (isSuppliedCart) {
263
+ startSuppliedCartReconciliation();
264
+ } else {
265
+ void initializeCart().catch((error2) => {
266
+ if (isMounted.current && !(error2 instanceof DOMException && error2.name === "AbortError")) {
267
+ console.error("[Replo] Error getting or creating cart:", error2);
268
+ }
269
+ });
270
+ }
212
271
  return () => {
213
272
  isMounted.current = false;
214
273
  };
215
- }, [initializeCart]);
216
- const lineItems = cartData?.lines ?? [];
274
+ }, [initializeCart, isSuppliedCart, startSuppliedCartReconciliation]);
217
275
  const subtotal = cartData?.lines.reduce((sum, lineItem) => {
218
276
  if (lineItem.quantity > 0 && lineItem.merchandise) {
219
277
  sum += lineItem.merchandise.price * lineItem.quantity;
@@ -250,213 +308,255 @@ function CartProvider({
250
308
  updateCartData(refreshedCart);
251
309
  }, [updateCartData]);
252
310
  const reportedLineQuantities = React.useRef(/* @__PURE__ */ new Map());
253
- const addToCart = (lines, openCartAfterAdd = true) => {
254
- return runWithErrorState("add", async () => {
255
- const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
256
- if (openCartAfterAdd) setIsCartOpen(true);
257
- throw caught;
258
- });
259
- reportedLineQuantities.current.clear();
260
- const additions = lines.map((line) => {
261
- const lineId = `temp-${uuidv4()}`;
262
- optimisticLines.current.set(lineId, line);
263
- return { line, lineId };
264
- });
265
- const applyLines = (cart2) => {
266
- let updatedCart2 = { ...cart2 };
267
- for (const { line, lineId } of additions) {
268
- updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
311
+ const addToCart = React.useCallback(
312
+ (lines, openCartAfterAdd = true) => {
313
+ return runWithErrorState("add", async () => {
314
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart().catch((caught) => {
315
+ if (openCartAfterAdd) setIsCartOpen(true);
316
+ throw caught;
317
+ });
318
+ reportedLineQuantities.current.clear();
319
+ const additions = lines.map((line) => {
320
+ const lineId = `temp-${uuidv4()}`;
321
+ optimisticLines.current.set(lineId, line);
322
+ return { line, lineId };
323
+ });
324
+ const applyLines = (cart2) => {
325
+ let updatedCart2 = { ...cart2 };
326
+ for (const { line, lineId } of additions) {
327
+ updatedCart2 = addLineToCart({ cart: updatedCart2, line, lineId });
328
+ }
329
+ return updatedCart2;
330
+ };
331
+ const updatedCart = updateCartData((currentCart) => {
332
+ return applyLines(currentCart ?? readyCart);
333
+ });
334
+ if (analytics) {
335
+ for (const line of lines) {
336
+ if (!line.merchandise) {
337
+ continue;
338
+ }
339
+ void analytics.addToCart({
340
+ data: buildCartLineAnalyticsData({
341
+ merchandise: line.merchandise,
342
+ quantity: line.quantity,
343
+ currencyCode: readyCart.cost.currencyCode
344
+ })
345
+ });
346
+ }
269
347
  }
270
- return updatedCart2;
271
- };
272
- const updatedCart = updateCartData((currentCart) => {
273
- return applyLines(currentCart ?? readyCart);
274
- });
275
- if (analytics) {
276
- for (const line of lines) {
277
- if (!line.merchandise) {
278
- continue;
348
+ if (openCartAfterAdd) {
349
+ setIsCartOpen(true);
350
+ emitCartView(updatedCart);
351
+ }
352
+ const adjustments = [];
353
+ await enqueueMutation(async (currentCart) => {
354
+ const savedCart = await addToCartAction(lines, currentCart.id);
355
+ if (savedCart) {
356
+ adjustments.push(
357
+ ...deriveAddToCartAdjustments({
358
+ cartBeforeAdd: currentCart,
359
+ requestedLines: lines,
360
+ savedCart
361
+ })
362
+ );
279
363
  }
280
- void analytics.addToCart({
281
- data: buildCartLineAnalyticsData({
282
- merchandise: line.merchandise,
283
- quantity: line.quantity,
284
- currencyCode: readyCart.cost.currencyCode
285
- })
364
+ return savedCart;
365
+ });
366
+ return { adjustments };
367
+ });
368
+ },
369
+ [
370
+ analytics,
371
+ cartData,
372
+ emitCartView,
373
+ enqueueMutation,
374
+ initializeCart,
375
+ runWithErrorState,
376
+ updateCartData
377
+ ]
378
+ );
379
+ const updateCartItem = React.useCallback(
380
+ (line) => {
381
+ return runWithErrorState("update", async () => {
382
+ if (!cartData) {
383
+ console.warn(
384
+ "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
385
+ );
386
+ return { adjustments: [] };
387
+ }
388
+ const currentLine = cartData.lines.find((item) => item.id === line.id);
389
+ const merchandise = currentLine?.merchandise ?? line.merchandise;
390
+ const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
391
+ reportedLineQuantities.current.set(line.id, line.quantity);
392
+ if (analytics && merchandise && quantityDelta !== 0) {
393
+ const data = buildCartLineAnalyticsData({
394
+ merchandise,
395
+ quantity: Math.abs(quantityDelta),
396
+ currencyCode: cartData.cost.currencyCode
286
397
  });
398
+ void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
287
399
  }
288
- }
289
- if (openCartAfterAdd) {
290
- setIsCartOpen(true);
291
- emitCartView(updatedCart);
292
- }
293
- const adjustments = [];
294
- await enqueueMutation(async (currentCart) => {
295
- const savedCart = await addToCartAction(lines, currentCart.id);
296
- if (savedCart) {
297
- adjustments.push(
298
- ...deriveAddToCartAdjustments({
299
- cartBeforeAdd: currentCart,
300
- requestedLines: lines,
301
- savedCart
302
- })
400
+ updateCartData((currentCart) => {
401
+ if (!currentCart) {
402
+ return null;
403
+ }
404
+ return updateLineInCart(currentCart, line);
405
+ });
406
+ const adjustments = [];
407
+ await enqueueMutation(async (currentCart) => {
408
+ const savedLine = findLine({
409
+ currentCart,
410
+ lineId: line.id,
411
+ originalLine: currentLine
412
+ });
413
+ if (!savedLine) {
414
+ return currentCart;
415
+ }
416
+ const savedCart = await updateCartLineItemAction(
417
+ { ...line, id: savedLine.id },
418
+ currentCart.id
303
419
  );
304
- }
305
- return savedCart;
306
- });
307
- return { adjustments };
308
- });
309
- };
310
- const updateCartItem = (line) => {
311
- return runWithErrorState("update", async () => {
312
- if (!cartData) {
313
- console.warn(
314
- "[Replo] Cannot update cart item: no cart exists. Reach out to support@replo.app if this persists."
315
- );
316
- return { adjustments: [] };
317
- }
318
- const currentLine = cartData.lines.find((item) => item.id === line.id);
319
- const merchandise = currentLine?.merchandise ?? line.merchandise;
320
- const quantityDelta = line.quantity - (reportedLineQuantities.current.get(line.id) ?? currentLine?.quantity ?? 0);
321
- reportedLineQuantities.current.set(line.id, line.quantity);
322
- if (analytics && merchandise && quantityDelta !== 0) {
323
- const data = buildCartLineAnalyticsData({
324
- merchandise,
325
- quantity: Math.abs(quantityDelta),
326
- currencyCode: cartData.cost.currencyCode
420
+ if (savedCart) {
421
+ adjustments.push(
422
+ ...deriveUpdateCartAdjustments({
423
+ cartBeforeUpdate: currentCart,
424
+ editedLine: savedLine,
425
+ requestedQuantity: line.quantity,
426
+ requestedSellingPlanId: line.sellingPlanId,
427
+ savedCart
428
+ })
429
+ );
430
+ }
431
+ return savedCart;
327
432
  });
328
- void (quantityDelta > 0 ? analytics.addToCart({ data }) : analytics.removeFromCart({ data }));
329
- }
330
- updateCartData((currentCart) => {
331
- if (!currentCart) {
332
- return null;
333
- }
334
- return updateLineInCart(currentCart, line);
433
+ return { adjustments };
335
434
  });
336
- const adjustments = [];
337
- await enqueueMutation(async (currentCart) => {
338
- const savedLine = findLine({
339
- currentCart,
340
- lineId: line.id,
341
- originalLine: currentLine
342
- });
343
- if (!savedLine) {
344
- return currentCart;
435
+ },
436
+ [
437
+ analytics,
438
+ cartData,
439
+ enqueueMutation,
440
+ findLine,
441
+ runWithErrorState,
442
+ updateCartData
443
+ ]
444
+ );
445
+ const removeCartItem = React.useCallback(
446
+ (lineId) => {
447
+ return runWithErrorState("remove", async () => {
448
+ if (!cartData) {
449
+ console.warn(
450
+ "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
451
+ );
452
+ return;
345
453
  }
346
- const savedCart = await updateCartLineItemAction(
347
- { ...line, id: savedLine.id },
348
- currentCart.id
349
- );
350
- if (savedCart) {
351
- adjustments.push(
352
- ...deriveUpdateCartAdjustments({
353
- cartBeforeUpdate: currentCart,
354
- editedLine: savedLine,
355
- requestedQuantity: line.quantity,
356
- requestedSellingPlanId: line.sellingPlanId,
357
- savedCart
454
+ const itemToRemove = cartData.lines.find((item) => item.id === lineId);
455
+ reportedLineQuantities.current.delete(lineId);
456
+ if (analytics && itemToRemove && itemToRemove.merchandise) {
457
+ void analytics.removeFromCart({
458
+ data: buildCartLineAnalyticsData({
459
+ merchandise: itemToRemove.merchandise,
460
+ quantity: itemToRemove.quantity,
461
+ currencyCode: cartData.cost.currencyCode
358
462
  })
359
- );
463
+ });
360
464
  }
361
- return savedCart;
362
- });
363
- return { adjustments };
364
- });
365
- };
366
- const removeCartItem = (lineId) => {
367
- return runWithErrorState("remove", async () => {
368
- if (!cartData) {
369
- console.warn(
370
- "[Replo] Cannot remove cart item: no cart exists. Reach out to support@replo.app if this persists."
371
- );
372
- return;
373
- }
374
- const itemToRemove = cartData.lines.find((item) => item.id === lineId);
375
- reportedLineQuantities.current.delete(lineId);
376
- if (analytics && itemToRemove && itemToRemove.merchandise) {
377
- void analytics.removeFromCart({
378
- data: buildCartLineAnalyticsData({
379
- merchandise: itemToRemove.merchandise,
380
- quantity: itemToRemove.quantity,
381
- currencyCode: cartData.cost.currencyCode
382
- })
465
+ updateCartData((currentCart) => {
466
+ if (!currentCart) {
467
+ return null;
468
+ }
469
+ return removeLineFromCart(currentCart, lineId);
383
470
  });
384
- }
385
- updateCartData((currentCart) => {
386
- if (!currentCart) {
387
- return null;
388
- }
389
- return removeLineFromCart(currentCart, lineId);
390
- });
391
- await enqueueMutation(async (currentCart) => {
392
- const savedLine = findLine({
393
- currentCart,
394
- lineId,
395
- originalLine: itemToRemove
471
+ await enqueueMutation(async (currentCart) => {
472
+ const savedLine = findLine({
473
+ currentCart,
474
+ lineId,
475
+ originalLine: itemToRemove
476
+ });
477
+ return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
396
478
  });
397
- return savedLine ? await removeCartLineItemsAction([savedLine.id], currentCart.id) : currentCart;
398
- });
399
- });
400
- };
401
- const updateDiscountCodes = (discountCodes) => {
402
- return runWithErrorState("discount", async () => {
403
- const readyCart = cartData ?? serverCart.current ?? await initializeCart();
404
- updateCartData((currentCart) => {
405
- return {
406
- ...currentCart ?? readyCart,
407
- discountCodes: discountCodes.map((code) => ({
408
- code,
409
- applicable: true
410
- }))
411
- };
412
479
  });
413
- await enqueueMutation((currentCart) => {
414
- return updateCartDiscountCodesAction(discountCodes, currentCart.id);
480
+ },
481
+ [
482
+ analytics,
483
+ cartData,
484
+ enqueueMutation,
485
+ findLine,
486
+ runWithErrorState,
487
+ updateCartData
488
+ ]
489
+ );
490
+ const updateDiscountCodes = React.useCallback(
491
+ (discountCodes) => {
492
+ return runWithErrorState("discount", async () => {
493
+ const readyCart = cartData ?? serverCart.current ?? await initializeCart();
494
+ updateCartData((currentCart) => {
495
+ return {
496
+ ...currentCart ?? readyCart,
497
+ discountCodes: discountCodes.map((code) => ({
498
+ code,
499
+ applicable: true
500
+ }))
501
+ };
502
+ });
503
+ await enqueueMutation((currentCart) => {
504
+ return updateCartDiscountCodesAction(discountCodes, currentCart.id);
505
+ });
415
506
  });
416
- });
417
- };
418
- const buyNow = async (lines, discountCodes) => {
419
- const cart2 = await createCartAction({
420
- lines,
421
- skipStoringCart: true,
422
- discountCodes,
423
- country
424
- });
425
- if (cart2) {
426
- return cart2;
427
- } else {
428
- console.warn(
429
- "[Replo] Failed to create cart for buy now. Reach out to support@replo.app if this persists."
430
- );
431
- return null;
432
- }
433
- };
434
- const checkoutUrl = cartData?.checkoutUrl ?? "/";
435
- return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(
436
- CartContext.Provider,
437
- {
438
- value: {
439
- cartData,
440
- lineItems,
441
- subtotal,
442
- itemsCount,
443
- currencyCode: cartData?.cost.currencyCode ?? "USD",
444
- addToCart,
445
- updateCartItem,
446
- removeCartItem,
447
- updateDiscountCodes,
448
- refreshCart,
449
- buyNow,
450
- checkoutUrl,
451
- isCartOpen,
452
- openCart,
453
- closeCart,
454
- error,
455
- clearError
456
- },
457
- children
458
- }
459
- ) });
507
+ },
508
+ [
509
+ cartData,
510
+ enqueueMutation,
511
+ initializeCart,
512
+ runWithErrorState,
513
+ updateCartData
514
+ ]
515
+ );
516
+ const buyNow = React.useCallback(
517
+ (lines, discountCodes) => {
518
+ return createBuyNowCart({ lines, discountCodes, country });
519
+ },
520
+ [country]
521
+ );
522
+ const contextValue = React.useMemo(
523
+ () => ({
524
+ cartData,
525
+ lineItems: cartData?.lines ?? [],
526
+ subtotal,
527
+ itemsCount,
528
+ currencyCode: cartData?.cost.currencyCode ?? "USD",
529
+ addToCart,
530
+ updateCartItem,
531
+ removeCartItem,
532
+ updateDiscountCodes,
533
+ refreshCart,
534
+ buyNow,
535
+ checkoutUrl: cartData?.checkoutUrl ?? "/",
536
+ isCartOpen,
537
+ openCart,
538
+ closeCart,
539
+ error,
540
+ clearError
541
+ }),
542
+ [
543
+ addToCart,
544
+ buyNow,
545
+ cartData,
546
+ clearError,
547
+ closeCart,
548
+ error,
549
+ isCartOpen,
550
+ itemsCount,
551
+ openCart,
552
+ refreshCart,
553
+ removeCartItem,
554
+ subtotal,
555
+ updateCartItem,
556
+ updateDiscountCodes
557
+ ]
558
+ );
559
+ return /* @__PURE__ */ jsx(CartCountryContext.Provider, { value: country, children: /* @__PURE__ */ jsx(CartContext.Provider, { value: contextValue, children }) });
460
560
  }
461
561
  function useCartInternal() {
462
562
  const context = React.useContext(CartContext);
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export declare function SuppliedCartReconciliationBoundary({ children, enabled, }: React.PropsWithChildren<{
3
+ enabled: boolean;
4
+ }>): import("react/jsx-runtime").JSX.Element;
5
+ export declare function useShouldReconcileSuppliedCart(): boolean;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import React from "react";
4
+ const ShouldReconcileSuppliedCartContext = React.createContext(true);
5
+ function SuppliedCartReconciliationBoundary({
6
+ children,
7
+ enabled
8
+ }) {
9
+ return /* @__PURE__ */ jsx(ShouldReconcileSuppliedCartContext.Provider, { value: enabled, children });
10
+ }
11
+ function useShouldReconcileSuppliedCart() {
12
+ return React.useContext(ShouldReconcileSuppliedCartContext);
13
+ }
14
+ export {
15
+ SuppliedCartReconciliationBoundary,
16
+ useShouldReconcileSuppliedCart
17
+ };
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.10.1",
3
+ "version": "1.10.2",
4
4
  "branch": "HEAD",
5
- "commit": "bad30733ed6d9f37c3fe49876e818d01dad78e77",
6
- "builtAt": "2026-09-20T21:19:55.438Z"
5
+ "commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
6
+ "builtAt": "2026-09-20T21:55:46.106Z"
7
7
  };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.10.1",
3
+ "version": "1.10.2",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.10.1",
6
+ "version": "1.10.2",
7
7
  "branch": "HEAD",
8
- "commit": "bad30733ed6d9f37c3fe49876e818d01dad78e77",
9
- "builtAt": "2026-09-20T21:19:55.438Z"
8
+ "commit": "5ee9af27c9ec5545655f225b5029ef7572f61344",
9
+ "builtAt": "2026-09-20T21:55:46.106Z"
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",
@@ -18,7 +18,14 @@ async function ReploProvider({
18
18
  }
19
19
  return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(LoaderErrorDetailsProvider, { enabled: shouldShowLoaderErrorDetails(), children: /* @__PURE__ */ jsxs(AnalyticsProvider, { checkoutProvider, children: [
20
20
  /* @__PURE__ */ jsx(ReploFirstPartyPixel, {}),
21
- /* @__PURE__ */ jsx(CartLoader, { country: markets?.shopify?.country, children })
21
+ /* @__PURE__ */ jsx(
22
+ CartLoader,
23
+ {
24
+ country: markets?.shopify?.country,
25
+ shouldReconcileSuppliedCart: checkoutProvider === "stripe",
26
+ children
27
+ }
28
+ )
22
29
  ] }) }) });
23
30
  }
24
31
  export {