@cimplify/sdk 0.8.0 → 0.8.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/dist/react.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useState, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react';
1
+ import React2, { createContext, useContext, useState, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react';
2
2
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
3
3
 
4
4
  // src/react/index.tsx
@@ -14,6 +14,7 @@ var MESSAGE_TYPES = {
14
14
  // Parent → Iframe
15
15
  INIT: "init",
16
16
  SET_TOKEN: "set_token",
17
+ SET_CART: "set_cart",
17
18
  GET_DATA: "get_data",
18
19
  PROCESS_CHECKOUT: "process_checkout",
19
20
  ABORT_CHECKOUT: "abort_checkout",
@@ -31,7 +32,8 @@ var MESSAGE_TYPES = {
31
32
  CONTACT_PROVIDED: "contact_provided",
32
33
  CHECKOUT_STATUS: "checkout_status",
33
34
  CHECKOUT_COMPLETE: "checkout_complete",
34
- ORDER_TYPE_CHANGED: "order_type_changed"
35
+ ORDER_TYPE_CHANGED: "order_type_changed",
36
+ REQUEST_SUBMIT: "request_submit"
35
37
  };
36
38
  var EVENT_TYPES = {
37
39
  READY: "ready",
@@ -39,7 +41,8 @@ var EVENT_TYPES = {
39
41
  REQUIRES_OTP: "requires_otp",
40
42
  ERROR: "error",
41
43
  CHANGE: "change",
42
- ORDER_TYPE_CHANGED: "order_type_changed"
44
+ ORDER_TYPE_CHANGED: "order_type_changed",
45
+ REQUEST_SUBMIT: "request_submit"
43
46
  };
44
47
 
45
48
  // src/ads/identity.ts
@@ -341,7 +344,62 @@ function Ad({
341
344
  }
342
345
  );
343
346
  }
344
- var SPACE = { sm: 8, md: 12, lg: 16, xl: 24 };
347
+
348
+ // src/utils/cart-transform.ts
349
+ function getSelections(item) {
350
+ if (item.bundle_resolved?.selections?.length) {
351
+ return item.bundle_resolved.selections.map((s) => ({
352
+ name: s.product_name || s.component_id,
353
+ quantity: s.quantity,
354
+ variant_name: s.variant_name
355
+ }));
356
+ }
357
+ if (item.composite_resolved?.selections?.length) {
358
+ return item.composite_resolved.selections.map((s) => ({
359
+ name: s.component_name || s.component_id,
360
+ quantity: s.quantity
361
+ }));
362
+ }
363
+ return void 0;
364
+ }
365
+ function mapItem(item) {
366
+ const result = {
367
+ name: item.name,
368
+ quantity: item.quantity,
369
+ unit_price: String(item.base_price),
370
+ total_price: String(item.total_price),
371
+ line_type: item.line_type
372
+ };
373
+ if (item.image_url) result.image_url = item.image_url;
374
+ const variantName = item.variant_info?.name || item.variant_name || void 0;
375
+ if (variantName) result.variant_name = variantName;
376
+ if (item.scheduled_start) result.scheduled_start = item.scheduled_start;
377
+ if (item.scheduled_end) result.scheduled_end = item.scheduled_end;
378
+ const selections = getSelections(item);
379
+ if (selections) result.selections = selections;
380
+ if (item.add_on_options?.length) {
381
+ result.add_ons = item.add_on_options.map((opt) => ({
382
+ name: opt.name,
383
+ price: String(opt.price ?? "0")
384
+ }));
385
+ }
386
+ if (item.special_instructions) {
387
+ result.special_instructions = item.special_instructions;
388
+ }
389
+ return result;
390
+ }
391
+ function transformToCheckoutCart(cart) {
392
+ return {
393
+ items: cart.items.map(mapItem),
394
+ subtotal: String(cart.pricing.subtotal),
395
+ tax_amount: String(cart.pricing.tax_amount),
396
+ total_discounts: String(cart.pricing.total_discounts),
397
+ service_charge: String(cart.pricing.service_charge),
398
+ total: String(cart.pricing.total_price),
399
+ currency: cart.pricing.currency
400
+ };
401
+ }
402
+ var SPACE = { sm: 8};
345
403
  function shellColors(isDark, primaryColor) {
346
404
  return {
347
405
  text: isDark ? "#f4f4f5" : "#1a1a1a",
@@ -407,6 +465,7 @@ function CimplifyCheckout({
407
465
  const [errorMessage, setErrorMessage] = useState(null);
408
466
  const [resolvedBusinessId, setResolvedBusinessId] = useState(businessId ?? null);
409
467
  const [resolvedCartId, setResolvedCartId] = useState(cartId ?? null);
468
+ const [resolvedCart, setResolvedCart] = useState(null);
410
469
  const checkoutMountRef = useRef(null);
411
470
  const elementsRef = useRef(null);
412
471
  const activeCheckoutRef = useRef(null);
@@ -414,23 +473,21 @@ function CimplifyCheckout({
414
473
  const hasWarnedInlineAppearanceRef = useRef(false);
415
474
  const isMountedRef = useRef(true);
416
475
  const demoRunRef = useRef(0);
417
- const onCompleteRef = useRef(onComplete);
418
- const onErrorRef = useRef(onError);
419
- const onStatusChangeRef = useRef(onStatusChange);
420
- onCompleteRef.current = onComplete;
421
- onErrorRef.current = onError;
422
- onStatusChangeRef.current = onStatusChange;
423
476
  const isDemoCheckout = demoMode ?? client.getPublicKey().trim().length === 0;
424
477
  const isTestMode = client.isTestMode();
425
478
  const primaryColor = appearance?.variables?.primaryColor || "#0a2540";
426
479
  const isDark = appearance?.theme === "dark";
427
- const emitStatus = useCallback(
480
+ const emitStatus = React2.useEffectEvent(
428
481
  (nextStatus, context = {}) => {
429
482
  setStatus(nextStatus);
430
483
  setStatusText(context.display_text || "");
431
- onStatusChangeRef.current?.(nextStatus, context);
432
- },
433
- []
484
+ onStatusChange?.(nextStatus, context);
485
+ }
486
+ );
487
+ const fireError = React2.useEffectEvent(
488
+ (error) => {
489
+ onError?.(error);
490
+ }
434
491
  );
435
492
  useEffect(() => {
436
493
  if (!resolvedOrderTypes.includes(orderType)) {
@@ -466,6 +523,12 @@ function CimplifyCheckout({
466
523
  setIsInitializing(false);
467
524
  setErrorMessage(null);
468
525
  }
526
+ client.cart.get().then((cartResult) => {
527
+ if (!cancelled && cartResult.ok && cartResult.value) {
528
+ setResolvedCart(cartResult.value);
529
+ }
530
+ }).catch(() => {
531
+ });
469
532
  return;
470
533
  }
471
534
  if (!cancelled) {
@@ -483,7 +546,7 @@ function CimplifyCheckout({
483
546
  setResolvedCartId(null);
484
547
  setErrorMessage(message);
485
548
  setIsInitializing(false);
486
- onErrorRef.current?.({ code: "BUSINESS_ID_REQUIRED", message });
549
+ fireError({ code: "BUSINESS_ID_REQUIRED", message });
487
550
  }
488
551
  return;
489
552
  }
@@ -498,11 +561,14 @@ function CimplifyCheckout({
498
561
  setResolvedCartId(null);
499
562
  setErrorMessage(message);
500
563
  setIsInitializing(false);
501
- onErrorRef.current?.({ code: "CART_EMPTY", message });
564
+ fireError({ code: "CART_EMPTY", message });
502
565
  }
503
566
  return;
504
567
  }
505
568
  nextCartId = cartResult.value.id;
569
+ if (!cancelled) {
570
+ setResolvedCart(cartResult.value);
571
+ }
506
572
  }
507
573
  if (!cancelled) {
508
574
  setResolvedBusinessId(nextBusinessId);
@@ -524,42 +590,12 @@ function CimplifyCheckout({
524
590
  activeCheckoutRef.current = null;
525
591
  };
526
592
  }, []);
527
- useEffect(() => {
528
- if (isDemoCheckout || !resolvedBusinessId) {
529
- elementsRef.current = null;
530
- return;
531
- }
532
- const elements = client.elements(resolvedBusinessId, {
533
- appearance: initialAppearanceRef.current,
534
- linkUrl
535
- });
536
- elementsRef.current = elements;
537
- const checkout = elements.create("checkout", {
538
- orderTypes: resolvedOrderTypes,
539
- defaultOrderType: resolvedOrderTypes[0]
540
- });
541
- if (checkoutMountRef.current) {
542
- checkout.mount(checkoutMountRef.current);
543
- }
544
- checkout.on("order_type_changed", (data) => {
545
- const typed = data;
546
- if (typed.orderType) {
547
- setOrderType(typed.orderType);
548
- }
549
- });
550
- return () => {
551
- activeCheckoutRef.current?.abort();
552
- activeCheckoutRef.current = null;
553
- elements.destroy();
554
- elementsRef.current = null;
555
- };
556
- }, [client, resolvedBusinessId, isDemoCheckout]);
557
- const handleSubmit = useCallback(async () => {
593
+ const handleSubmit = React2.useEffectEvent(async () => {
558
594
  if (isSubmitting || isInitializing || !resolvedCartId) {
559
595
  if (!resolvedCartId && !isInitializing) {
560
596
  const message = "Your cart is empty. Add items before checkout.";
561
597
  setErrorMessage(message);
562
- onErrorRef.current?.({ code: "CART_EMPTY", message });
598
+ fireError({ code: "CART_EMPTY", message });
563
599
  }
564
600
  return;
565
601
  }
@@ -594,7 +630,7 @@ function CimplifyCheckout({
594
630
  order_number: result.order?.order_number,
595
631
  display_text: statusToLabel("success")
596
632
  });
597
- onCompleteRef.current(result);
633
+ onComplete(result);
598
634
  } finally {
599
635
  if (isMountedRef.current && runId === demoRunRef.current) {
600
636
  setIsSubmitting(false);
@@ -605,7 +641,7 @@ function CimplifyCheckout({
605
641
  if (!elementsRef.current) {
606
642
  const message = "Checkout is still initializing. Please try again.";
607
643
  setErrorMessage(message);
608
- onErrorRef.current?.({ code: "CHECKOUT_NOT_READY", message });
644
+ fireError({ code: "CHECKOUT_NOT_READY", message });
609
645
  setIsSubmitting(false);
610
646
  return;
611
647
  }
@@ -620,30 +656,65 @@ function CimplifyCheckout({
620
656
  try {
621
657
  const result = await checkout;
622
658
  if (result.success) {
623
- onCompleteRef.current(result);
659
+ onComplete(result);
624
660
  return;
625
661
  }
626
662
  const code = result.error?.code || "CHECKOUT_FAILED";
627
663
  const message = result.error?.message || "Payment failed.";
628
664
  setErrorMessage(message);
629
- onErrorRef.current?.({ code, message });
665
+ fireError({ code, message });
630
666
  } finally {
631
667
  if (isMountedRef.current) {
632
668
  activeCheckoutRef.current = null;
633
669
  setIsSubmitting(false);
634
670
  }
635
671
  }
636
- }, [
637
- resolvedCartId,
638
- client,
639
- enrollInLink,
640
- emitStatus,
641
- isDemoCheckout,
642
- isInitializing,
643
- isSubmitting,
644
- locationId,
645
- orderType
646
- ]);
672
+ });
673
+ useEffect(() => {
674
+ if (isDemoCheckout || !resolvedBusinessId) {
675
+ elementsRef.current = null;
676
+ return;
677
+ }
678
+ const elements = client.elements(resolvedBusinessId, {
679
+ appearance: initialAppearanceRef.current,
680
+ linkUrl
681
+ });
682
+ elementsRef.current = elements;
683
+ const checkout = elements.create("checkout", {
684
+ orderTypes: resolvedOrderTypes,
685
+ defaultOrderType: resolvedOrderTypes[0]
686
+ });
687
+ if (checkoutMountRef.current) {
688
+ checkout.mount(checkoutMountRef.current);
689
+ }
690
+ checkout.on("ready", () => {
691
+ if (resolvedCart) {
692
+ checkout.setCart(transformToCheckoutCart(resolvedCart));
693
+ }
694
+ });
695
+ checkout.on("order_type_changed", (data) => {
696
+ const typed = data;
697
+ if (typed.orderType) {
698
+ setOrderType(typed.orderType);
699
+ }
700
+ });
701
+ checkout.on("request_submit", () => {
702
+ void handleSubmit();
703
+ });
704
+ return () => {
705
+ activeCheckoutRef.current?.abort();
706
+ activeCheckoutRef.current = null;
707
+ elements.destroy();
708
+ elementsRef.current = null;
709
+ };
710
+ }, [client, resolvedBusinessId, isDemoCheckout]);
711
+ useEffect(() => {
712
+ if (!resolvedCart || !elementsRef.current) return;
713
+ const checkoutElement = elementsRef.current.getElement("checkout");
714
+ if (checkoutElement) {
715
+ checkoutElement.setCart(transformToCheckoutCart(resolvedCart));
716
+ }
717
+ }, [resolvedCart]);
647
718
  const colors = shellColors(isDark ?? false, primaryColor);
648
719
  if (isInitializing) {
649
720
  return /* @__PURE__ */ jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { fontSize: 13, color: colors.textSecondary }, children: "Preparing checkout..." }) });
@@ -666,28 +737,6 @@ function CimplifyCheckout({
666
737
  }
667
738
  ),
668
739
  /* @__PURE__ */ jsx("div", { "data-cimplify-section": "checkout", children: /* @__PURE__ */ jsx("div", { ref: isDemoCheckout ? void 0 : checkoutMountRef }) }),
669
- /* @__PURE__ */ jsx("div", { style: { marginTop: SPACE.xl }, children: /* @__PURE__ */ jsx(
670
- "button",
671
- {
672
- type: "button",
673
- onClick: handleSubmit,
674
- disabled: isSubmitting,
675
- style: {
676
- width: "100%",
677
- padding: `${SPACE.md}px ${SPACE.lg}px`,
678
- borderRadius: 8,
679
- border: "none",
680
- background: isSubmitting ? colors.textMuted : primaryColor,
681
- color: "#ffffff",
682
- cursor: isSubmitting ? "not-allowed" : "pointer",
683
- fontWeight: 600,
684
- fontSize: 16,
685
- transition: "all 150ms ease",
686
- WebkitTapHighlightColor: "transparent"
687
- },
688
- children: isSubmitting ? "Processing..." : "Complete Order"
689
- }
690
- ) }),
691
740
  status && /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.textSecondary }, children: statusText || statusToLabel(status) }),
692
741
  errorMessage && /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.error }, children: errorMessage })
693
742
  ] });
@@ -1297,6 +1346,13 @@ var CartOperations = class {
1297
1346
  }
1298
1347
  };
1299
1348
 
1349
+ // src/constants.ts
1350
+ var MOBILE_MONEY_PROVIDER = {
1351
+ MTN: "mtn",
1352
+ VODAFONE: "vodafone",
1353
+ AIRTEL: "airtel"
1354
+ };
1355
+
1300
1356
  // src/utils/price.ts
1301
1357
  function parsePrice(value) {
1302
1358
  if (value === void 0 || value === null) {
@@ -1535,6 +1591,10 @@ async function openCardPopup(provider, checkoutResult, email, currency, signal)
1535
1591
  }
1536
1592
  return { success: false, error: "PROVIDER_UNAVAILABLE" };
1537
1593
  }
1594
+ var VALID_MOBILE_MONEY_PROVIDERS = new Set(Object.values(MOBILE_MONEY_PROVIDER));
1595
+ function isValidMobileMoneyProvider(value) {
1596
+ return VALID_MOBILE_MONEY_PROVIDERS.has(value);
1597
+ }
1538
1598
  function normalizeAuthorizationType(value) {
1539
1599
  return value === "otp" || value === "pin" ? value : void 0;
1540
1600
  }
@@ -1767,6 +1827,16 @@ var CheckoutResolver = class {
1767
1827
  }
1768
1828
  await this.wait(this.pollIntervalMs);
1769
1829
  }
1830
+ try {
1831
+ const finalResult = await this.client.checkout.pollPaymentStatus(input.orderId);
1832
+ if (finalResult.ok) {
1833
+ const normalized = normalizeStatusResponse(finalResult.value);
1834
+ if (normalized.paid || isPaymentStatusSuccess(normalized.status)) {
1835
+ return this.finalizeSuccess(latestCheckoutResult);
1836
+ }
1837
+ }
1838
+ } catch {
1839
+ }
1770
1840
  return this.fail(
1771
1841
  "PAYMENT_TIMEOUT",
1772
1842
  "Payment confirmation timed out. Please retry checkout.",
@@ -1897,7 +1967,7 @@ var CheckoutResolver = class {
1897
1967
  };
1898
1968
  }
1899
1969
  getEnrollmentMobileMoney() {
1900
- if (this.paymentData?.type === "mobile_money" && this.paymentData.phone_number && this.paymentData.provider) {
1970
+ if (this.paymentData?.type === "mobile_money" && this.paymentData.phone_number && this.paymentData.provider && isValidMobileMoneyProvider(this.paymentData.provider)) {
1901
1971
  return {
1902
1972
  phone_number: this.paymentData.phone_number,
1903
1973
  provider: this.paymentData.provider,
@@ -2971,8 +3041,10 @@ var CimplifyElements = class {
2971
3041
  false
2972
3042
  );
2973
3043
  }
3044
+ this.checkoutInProgress = true;
2974
3045
  if (!options.cart_id) {
2975
3046
  console.debug("[cimplify:checkout] BLOCKED: no cart_id");
3047
+ this.checkoutInProgress = false;
2976
3048
  return toCheckoutError(
2977
3049
  "INVALID_CART",
2978
3050
  "A valid cart is required before checkout can start.",
@@ -2981,6 +3053,7 @@ var CimplifyElements = class {
2981
3053
  }
2982
3054
  if (!options.order_type) {
2983
3055
  console.debug("[cimplify:checkout] BLOCKED: no order_type");
3056
+ this.checkoutInProgress = false;
2984
3057
  return toCheckoutError(
2985
3058
  "ORDER_TYPE_REQUIRED",
2986
3059
  "Order type is required before checkout can start.",
@@ -2990,6 +3063,7 @@ var CimplifyElements = class {
2990
3063
  const checkoutElement = this.elements.get(ELEMENT_TYPES.CHECKOUT) || this.elements.get(ELEMENT_TYPES.PAYMENT);
2991
3064
  if (!checkoutElement) {
2992
3065
  console.debug("[cimplify:checkout] BLOCKED: no checkout element");
3066
+ this.checkoutInProgress = false;
2993
3067
  return toCheckoutError(
2994
3068
  "NO_PAYMENT_ELEMENT",
2995
3069
  "Checkout element must be mounted before checkout.",
@@ -2998,6 +3072,7 @@ var CimplifyElements = class {
2998
3072
  }
2999
3073
  if (!checkoutElement.isMounted()) {
3000
3074
  console.debug("[cimplify:checkout] BLOCKED: checkout element not mounted");
3075
+ this.checkoutInProgress = false;
3001
3076
  return toCheckoutError(
3002
3077
  "PAYMENT_NOT_MOUNTED",
3003
3078
  "Checkout element must be mounted before checkout.",
@@ -3009,6 +3084,7 @@ var CimplifyElements = class {
3009
3084
  const authElement = this.elements.get(ELEMENT_TYPES.AUTH);
3010
3085
  if (authElement && !this.accessToken) {
3011
3086
  console.debug("[cimplify:checkout] BLOCKED: auth incomplete");
3087
+ this.checkoutInProgress = false;
3012
3088
  return toCheckoutError(
3013
3089
  "AUTH_INCOMPLETE",
3014
3090
  "Authentication must complete before checkout can start.",
@@ -3046,7 +3122,6 @@ var CimplifyElements = class {
3046
3122
  };
3047
3123
  const timeoutMs = options.timeout_ms ?? 18e4;
3048
3124
  const paymentWindow = checkoutElement.getContentWindow();
3049
- this.checkoutInProgress = true;
3050
3125
  return new Promise((resolve) => {
3051
3126
  let settled = false;
3052
3127
  const cleanup = () => {
@@ -3341,6 +3416,9 @@ var CimplifyElement = class {
3341
3416
  this.sendMessage({ type: MESSAGE_TYPES.GET_DATA });
3342
3417
  });
3343
3418
  }
3419
+ setCart(cart) {
3420
+ this.sendMessage({ type: MESSAGE_TYPES.SET_CART, cart });
3421
+ }
3344
3422
  sendMessage(message) {
3345
3423
  if (this.iframe?.contentWindow) {
3346
3424
  this.iframe.contentWindow.postMessage(message, this.linkUrl);
@@ -3396,7 +3474,8 @@ var CimplifyElement = class {
3396
3474
  prefillEmail: this.options.prefillEmail,
3397
3475
  appearance: this.parent.getAppearance(),
3398
3476
  orderTypes: this.options.orderTypes,
3399
- defaultOrderType: this.options.defaultOrderType
3477
+ defaultOrderType: this.options.defaultOrderType,
3478
+ renderSubmitButton: true
3400
3479
  });
3401
3480
  const token = this.parent.getAccessToken();
3402
3481
  if (token && this.type !== ELEMENT_TYPES.AUTH) {
@@ -3463,6 +3542,9 @@ var CimplifyElement = class {
3463
3542
  case MESSAGE_TYPES.ORDER_TYPE_CHANGED:
3464
3543
  this.emit(EVENT_TYPES.ORDER_TYPE_CHANGED, { orderType: message.orderType });
3465
3544
  break;
3545
+ case MESSAGE_TYPES.REQUEST_SUBMIT:
3546
+ this.emit(EVENT_TYPES.REQUEST_SUBMIT, {});
3547
+ break;
3466
3548
  }
3467
3549
  }
3468
3550
  emit(event, data) {
@@ -3485,6 +3567,7 @@ var ACCESS_TOKEN_STORAGE_KEY = "cimplify_access_token";
3485
3567
  var SESSION_TOKEN_STORAGE_KEY = "cimplify_session_token";
3486
3568
  var ORDER_TOKEN_PREFIX = "cimplify_ot_";
3487
3569
  var SESSION_TOKEN_HEADER = "x-session-token";
3570
+ var ORDER_TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
3488
3571
  var DEFAULT_TIMEOUT_MS = 3e4;
3489
3572
  var DEFAULT_MAX_RETRIES = 3;
3490
3573
  var DEFAULT_RETRY_DELAY_MS = 1e3;
@@ -3634,12 +3717,27 @@ var CimplifyClient = class {
3634
3717
  }
3635
3718
  setOrderToken(orderId, token) {
3636
3719
  if (typeof window !== "undefined" && window.localStorage) {
3637
- localStorage.setItem(`${ORDER_TOKEN_PREFIX}${orderId}`, token);
3720
+ try {
3721
+ const entry = JSON.stringify({ token, storedAt: Date.now() });
3722
+ localStorage.setItem(`${ORDER_TOKEN_PREFIX}${orderId}`, entry);
3723
+ } catch {
3724
+ }
3638
3725
  }
3639
3726
  }
3640
3727
  getOrderToken(orderId) {
3641
3728
  if (typeof window !== "undefined" && window.localStorage) {
3642
- return localStorage.getItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
3729
+ try {
3730
+ const raw = localStorage.getItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
3731
+ if (!raw) return null;
3732
+ const entry = JSON.parse(raw);
3733
+ if (Date.now() - entry.storedAt > ORDER_TOKEN_TTL_MS) {
3734
+ localStorage.removeItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
3735
+ return null;
3736
+ }
3737
+ return entry.token;
3738
+ } catch {
3739
+ return null;
3740
+ }
3643
3741
  }
3644
3742
  return null;
3645
3743
  }
@@ -3715,10 +3813,13 @@ var CimplifyClient = class {
3715
3813
  }
3716
3814
  saveAccessToken(token) {
3717
3815
  if (typeof window !== "undefined" && window.localStorage) {
3718
- if (token) {
3719
- localStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, token);
3720
- } else {
3721
- localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
3816
+ try {
3817
+ if (token) {
3818
+ localStorage.setItem(ACCESS_TOKEN_STORAGE_KEY, token);
3819
+ } else {
3820
+ localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
3821
+ }
3822
+ } catch {
3722
3823
  }
3723
3824
  }
3724
3825
  }
@@ -3791,6 +3892,19 @@ var CimplifyClient = class {
3791
3892
  });
3792
3893
  return response;
3793
3894
  }
3895
+ if (response.status === 429 && attempt < this.maxRetries) {
3896
+ retryCount++;
3897
+ const retryAfter = response.headers.get("Retry-After");
3898
+ const delay = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3, 3e4) || this.retryDelay * Math.pow(2, attempt) : this.retryDelay * Math.pow(2, attempt);
3899
+ this.hooks.onRetry?.({
3900
+ ...context,
3901
+ attempt: retryCount,
3902
+ delayMs: delay,
3903
+ error: new Error(`Rate limited: ${response.status}`)
3904
+ });
3905
+ await sleep(delay);
3906
+ continue;
3907
+ }
3794
3908
  if (response.status >= 400 && response.status < 500) {
3795
3909
  this.hooks.onRequestError?.({
3796
3910
  ...context,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",