@cimplify/sdk 0.8.7 → 0.8.8

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 React2, { createContext, useContext, useState, useEffect, useRef, useMemo, useCallback, useSyncExternalStore } from 'react';
1
+ import React3, { 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
@@ -362,12 +362,13 @@ function getSelections(item) {
362
362
  }
363
363
  return void 0;
364
364
  }
365
- function mapItem(item) {
365
+ function mapItem(item, fx) {
366
+ const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
366
367
  const result = {
367
368
  name: item.name,
368
369
  quantity: item.quantity,
369
- unit_price: String(item.base_price),
370
- total_price: String(item.total_price),
370
+ unit_price: amt(item.base_price),
371
+ total_price: amt(item.total_price),
371
372
  line_type: item.line_type
372
373
  };
373
374
  if (item.image_url) result.image_url = item.image_url;
@@ -380,7 +381,7 @@ function mapItem(item) {
380
381
  if (item.add_on_options?.length) {
381
382
  result.add_ons = item.add_on_options.map((opt) => ({
382
383
  name: opt.name,
383
- price: String(opt.price ?? "0")
384
+ price: amt(opt.price ?? "0")
384
385
  }));
385
386
  }
386
387
  if (item.special_instructions) {
@@ -388,463 +389,18 @@ function mapItem(item) {
388
389
  }
389
390
  return result;
390
391
  }
391
- function transformToCheckoutCart(cart) {
392
+ function transformToCheckoutCart(cart, fx) {
393
+ const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
392
394
  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
395
+ items: cart.items.map((item) => mapItem(item, fx)),
396
+ subtotal: amt(cart.pricing.subtotal),
397
+ tax_amount: amt(cart.pricing.tax_amount),
398
+ total_discounts: amt(cart.pricing.total_discounts),
399
+ service_charge: amt(cart.pricing.service_charge),
400
+ total: amt(cart.pricing.total_price),
401
+ currency: fx?.displayCurrency ?? cart.pricing.currency
400
402
  };
401
403
  }
402
- var SPACE = { sm: 8};
403
- function shellColors(isDark, primaryColor) {
404
- return {
405
- text: isDark ? "#f4f4f5" : "#1a1a1a",
406
- textSecondary: isDark ? "#a1a1aa" : "#52525b",
407
- textMuted: isDark ? "#71717a" : "#a1a1aa",
408
- border: isDark ? "#27272a" : "#e4e4e7",
409
- surface: isDark ? "#18181b" : "#fafafa",
410
- error: "#dc2626",
411
- primary: primaryColor
412
- };
413
- }
414
- function statusToLabel(status) {
415
- if (!status) {
416
- return "";
417
- }
418
- if (status === "preparing") {
419
- return "Preparing checkout";
420
- }
421
- if (status === "recovering") {
422
- return "Resuming payment";
423
- }
424
- if (status === "processing") {
425
- return "Processing payment";
426
- }
427
- if (status === "awaiting_authorization") {
428
- return "Waiting for authorization";
429
- }
430
- if (status === "polling") {
431
- return "Confirming payment";
432
- }
433
- if (status === "finalizing") {
434
- return "Finalizing order";
435
- }
436
- if (status === "success") {
437
- return "Payment complete";
438
- }
439
- return "Payment failed";
440
- }
441
- function CimplifyCheckout({
442
- client,
443
- businessId,
444
- cartId,
445
- locationId,
446
- linkUrl,
447
- orderTypes,
448
- enrollInLink = true,
449
- onComplete,
450
- onError,
451
- onStatusChange,
452
- appearance,
453
- demoMode,
454
- className
455
- }) {
456
- const resolvedOrderTypes = useMemo(
457
- () => orderTypes && orderTypes.length > 0 ? orderTypes : ["pickup", "delivery"],
458
- [orderTypes]
459
- );
460
- const [orderType, setOrderType] = useState(resolvedOrderTypes[0] || "pickup");
461
- const [status, setStatus] = useState(null);
462
- const [statusText, setStatusText] = useState("");
463
- const [isSubmitting, setIsSubmitting] = useState(false);
464
- const [isInitializing, setIsInitializing] = useState(false);
465
- const [errorMessage, setErrorMessage] = useState(null);
466
- const [resolvedBusinessId, setResolvedBusinessId] = useState(businessId ?? null);
467
- const [resolvedCartId, setResolvedCartId] = useState(cartId ?? null);
468
- const [resolvedCart, setResolvedCart] = useState(null);
469
- const checkoutMountRef = useRef(null);
470
- const elementsRef = useRef(null);
471
- const activeCheckoutRef = useRef(null);
472
- const initialAppearanceRef = useRef(appearance);
473
- const hasWarnedInlineAppearanceRef = useRef(false);
474
- const isMountedRef = useRef(true);
475
- const demoRunRef = useRef(0);
476
- const isDemoCheckout = demoMode ?? client.getPublicKey().trim().length === 0;
477
- const isTestMode = client.isTestMode();
478
- const primaryColor = appearance?.variables?.primaryColor || "#0a2540";
479
- const isDark = appearance?.theme === "dark";
480
- const emitStatus = React2.useEffectEvent(
481
- (nextStatus, context = {}) => {
482
- setStatus(nextStatus);
483
- setStatusText(context.display_text || "");
484
- onStatusChange?.(nextStatus, context);
485
- }
486
- );
487
- const fireError = React2.useEffectEvent(
488
- (error) => {
489
- onError?.(error);
490
- }
491
- );
492
- useEffect(() => {
493
- if (!resolvedOrderTypes.includes(orderType)) {
494
- setOrderType(resolvedOrderTypes[0] || "pickup");
495
- }
496
- }, [resolvedOrderTypes, orderType]);
497
- useEffect(() => {
498
- if (appearance && appearance !== initialAppearanceRef.current && !hasWarnedInlineAppearanceRef.current) {
499
- hasWarnedInlineAppearanceRef.current = true;
500
- console.warn(
501
- "[Cimplify] `appearance` prop reference changed after mount. Elements keep the initial appearance to avoid iframe remount. Memoize appearance with useMemo() to remove this warning."
502
- );
503
- }
504
- }, [appearance]);
505
- useEffect(() => {
506
- let cancelled = false;
507
- async function bootstrap() {
508
- if (isDemoCheckout) {
509
- if (!cancelled) {
510
- setResolvedBusinessId(businessId ?? null);
511
- setResolvedCartId(cartId ?? "cart_demo");
512
- setIsInitializing(false);
513
- setErrorMessage(null);
514
- }
515
- return;
516
- }
517
- const needsBusinessResolve = !businessId;
518
- const needsCartResolve = !cartId;
519
- if (!needsBusinessResolve && !needsCartResolve) {
520
- if (!cancelled) {
521
- setResolvedBusinessId(businessId || null);
522
- setResolvedCartId(cartId || null);
523
- setIsInitializing(false);
524
- setErrorMessage(null);
525
- }
526
- client.cart.get().then((cartResult) => {
527
- if (!cancelled && cartResult.ok && cartResult.value) {
528
- setResolvedCart(cartResult.value);
529
- }
530
- }).catch(() => {
531
- });
532
- return;
533
- }
534
- if (!cancelled) {
535
- setIsInitializing(true);
536
- setErrorMessage(null);
537
- }
538
- let nextBusinessId = businessId ?? null;
539
- if (!nextBusinessId) {
540
- try {
541
- nextBusinessId = await client.resolveBusinessId();
542
- } catch {
543
- if (!cancelled) {
544
- const message = "Unable to initialize checkout business context.";
545
- setResolvedBusinessId(null);
546
- setResolvedCartId(null);
547
- setErrorMessage(message);
548
- setIsInitializing(false);
549
- fireError({ code: "BUSINESS_ID_REQUIRED", message });
550
- }
551
- return;
552
- }
553
- }
554
- let nextCartId = cartId ?? null;
555
- if (!nextCartId) {
556
- const cartResult = await client.cart.get();
557
- if (!cartResult.ok || !cartResult.value?.id || cartResult.value.items.length === 0) {
558
- if (!cancelled) {
559
- const message = "Your cart is empty. Add items before checkout.";
560
- setResolvedBusinessId(nextBusinessId);
561
- setResolvedCartId(null);
562
- setErrorMessage(message);
563
- setIsInitializing(false);
564
- fireError({ code: "CART_EMPTY", message });
565
- }
566
- return;
567
- }
568
- nextCartId = cartResult.value.id;
569
- if (!cancelled) {
570
- setResolvedCart(cartResult.value);
571
- }
572
- }
573
- if (!cancelled) {
574
- setResolvedBusinessId(nextBusinessId);
575
- setResolvedCartId(nextCartId);
576
- setIsInitializing(false);
577
- setErrorMessage(null);
578
- }
579
- }
580
- void bootstrap();
581
- return () => {
582
- cancelled = true;
583
- };
584
- }, [businessId, cartId, client, isDemoCheckout]);
585
- useEffect(() => {
586
- return () => {
587
- isMountedRef.current = false;
588
- demoRunRef.current += 1;
589
- activeCheckoutRef.current?.abort();
590
- activeCheckoutRef.current = null;
591
- };
592
- }, []);
593
- const handleSubmit = React2.useEffectEvent(async () => {
594
- if (isSubmitting || isInitializing || !resolvedCartId) {
595
- if (!resolvedCartId && !isInitializing) {
596
- const message = "Your cart is empty. Add items before checkout.";
597
- setErrorMessage(message);
598
- fireError({ code: "CART_EMPTY", message });
599
- }
600
- return;
601
- }
602
- setErrorMessage(null);
603
- setIsSubmitting(true);
604
- emitStatus("preparing", { display_text: statusToLabel("preparing") });
605
- if (isDemoCheckout) {
606
- const runId = demoRunRef.current + 1;
607
- demoRunRef.current = runId;
608
- const wait = async (ms) => {
609
- await new Promise((resolve) => setTimeout(resolve, ms));
610
- return isMountedRef.current && runId === demoRunRef.current;
611
- };
612
- try {
613
- if (!await wait(400)) return;
614
- emitStatus("processing", { display_text: statusToLabel("processing") });
615
- if (!await wait(900)) return;
616
- emitStatus("polling", { display_text: statusToLabel("polling") });
617
- if (!await wait(1200)) return;
618
- const result = {
619
- success: true,
620
- order: {
621
- id: `ord_demo_${Date.now()}`,
622
- order_number: `DEMO-${Math.random().toString(36).slice(2, 8).toUpperCase()}`,
623
- status: "confirmed",
624
- total: "0.00",
625
- currency: "USD"
626
- }
627
- };
628
- emitStatus("success", {
629
- order_id: result.order?.id,
630
- order_number: result.order?.order_number,
631
- display_text: statusToLabel("success")
632
- });
633
- onComplete(result);
634
- } finally {
635
- if (isMountedRef.current && runId === demoRunRef.current) {
636
- setIsSubmitting(false);
637
- }
638
- }
639
- return;
640
- }
641
- if (!elementsRef.current) {
642
- const message = "Checkout is still initializing. Please try again.";
643
- setErrorMessage(message);
644
- fireError({ code: "CHECKOUT_NOT_READY", message });
645
- setIsSubmitting(false);
646
- return;
647
- }
648
- const checkout = elementsRef.current.processCheckout({
649
- cart_id: resolvedCartId,
650
- location_id: locationId ?? client.getLocationId() ?? void 0,
651
- order_type: orderType,
652
- enroll_in_link: enrollInLink,
653
- on_status_change: emitStatus
654
- });
655
- activeCheckoutRef.current = checkout;
656
- try {
657
- const result = await checkout;
658
- if (result.success) {
659
- onComplete(result);
660
- return;
661
- }
662
- const code = result.error?.code || "CHECKOUT_FAILED";
663
- const message = result.error?.message || "Payment failed.";
664
- setErrorMessage(message);
665
- fireError({ code, message });
666
- } finally {
667
- if (isMountedRef.current) {
668
- activeCheckoutRef.current = null;
669
- setIsSubmitting(false);
670
- }
671
- }
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]);
718
- const colors = shellColors(isDark ?? false, primaryColor);
719
- if (isInitializing) {
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..." }) });
721
- }
722
- if (!isDemoCheckout && (!resolvedBusinessId || !resolvedCartId)) {
723
- return /* @__PURE__ */ jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { fontSize: 13, color: colors.error }, children: errorMessage || "Unable to initialize checkout. Please refresh and try again." }) });
724
- }
725
- return /* @__PURE__ */ jsxs("div", { className, "data-cimplify-checkout": "", children: [
726
- isTestMode && !isDemoCheckout && /* @__PURE__ */ jsx(
727
- "p",
728
- {
729
- "data-cimplify-test-mode": "",
730
- style: {
731
- marginBottom: "10px",
732
- fontSize: "12px",
733
- fontWeight: 600,
734
- color: "#92400e"
735
- },
736
- children: "Test mode - no real charges"
737
- }
738
- ),
739
- /* @__PURE__ */ jsx("div", { "data-cimplify-section": "checkout", children: /* @__PURE__ */ jsx("div", { ref: isDemoCheckout ? void 0 : checkoutMountRef }) }),
740
- status && /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.textSecondary }, children: statusText || statusToLabel(status) }),
741
- errorMessage && /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.error }, children: errorMessage })
742
- ] });
743
- }
744
-
745
- // src/utils/price.ts
746
- var CURRENCY_SYMBOLS = {
747
- // Major world currencies
748
- USD: "$",
749
- EUR: "\u20AC",
750
- GBP: "\xA3",
751
- JPY: "\xA5",
752
- CNY: "\xA5",
753
- CHF: "CHF",
754
- CAD: "C$",
755
- AUD: "A$",
756
- NZD: "NZ$",
757
- HKD: "HK$",
758
- SGD: "S$",
759
- INR: "\u20B9",
760
- BRL: "R$",
761
- MXN: "MX$",
762
- KRW: "\u20A9",
763
- RUB: "\u20BD",
764
- TRY: "\u20BA",
765
- THB: "\u0E3F",
766
- PLN: "z\u0142",
767
- SEK: "kr",
768
- NOK: "kr",
769
- DKK: "kr",
770
- CZK: "K\u010D",
771
- HUF: "Ft",
772
- ILS: "\u20AA",
773
- AED: "\u062F.\u0625",
774
- SAR: "\uFDFC",
775
- MYR: "RM",
776
- PHP: "\u20B1",
777
- IDR: "Rp",
778
- VND: "\u20AB",
779
- TWD: "NT$",
780
- // African currencies
781
- GHS: "GH\u20B5",
782
- NGN: "\u20A6",
783
- KES: "KSh",
784
- ZAR: "R",
785
- XOF: "CFA",
786
- XAF: "FCFA",
787
- EGP: "E\xA3",
788
- MAD: "MAD",
789
- TZS: "TSh",
790
- UGX: "USh",
791
- RWF: "FRw",
792
- ETB: "Br",
793
- ZMW: "ZK",
794
- BWP: "P",
795
- MUR: "\u20A8",
796
- SCR: "\u20A8",
797
- NAD: "N$",
798
- SZL: "E",
799
- LSL: "L",
800
- MWK: "MK",
801
- AOA: "Kz",
802
- CDF: "FC",
803
- GMD: "D",
804
- GNF: "FG",
805
- LRD: "L$",
806
- SLL: "Le",
807
- MZN: "MT",
808
- SDG: "SDG",
809
- SSP: "SSP",
810
- SOS: "Sh.So.",
811
- DJF: "Fdj",
812
- ERN: "Nfk",
813
- CVE: "$",
814
- STN: "Db",
815
- KMF: "CF",
816
- BIF: "FBu"
817
- };
818
- function getCurrencySymbol(currencyCode2) {
819
- return CURRENCY_SYMBOLS[currencyCode2.toUpperCase()] || currencyCode2;
820
- }
821
- function formatPrice(amount, currency = "GHS", locale = "en-US") {
822
- const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
823
- if (isNaN(numAmount)) {
824
- return `${getCurrencySymbol(currency)}0.00`;
825
- }
826
- try {
827
- return new Intl.NumberFormat(locale, {
828
- style: "currency",
829
- currency: currency.toUpperCase(),
830
- minimumFractionDigits: 2,
831
- maximumFractionDigits: 2
832
- }).format(numAmount);
833
- } catch {
834
- return `${getCurrencySymbol(currency)}${numAmount.toFixed(2)}`;
835
- }
836
- }
837
- function parsePrice(value) {
838
- if (value === void 0 || value === null) {
839
- return 0;
840
- }
841
- if (typeof value === "number") {
842
- return isNaN(value) ? 0 : value;
843
- }
844
- const cleaned = value.replace(/[^\d.-]/g, "");
845
- const parsed = parseFloat(cleaned);
846
- return isNaN(parsed) ? 0 : parsed;
847
- }
848
404
 
849
405
  // src/types/common.ts
850
406
  function money(value) {
@@ -853,6 +409,59 @@ function money(value) {
853
409
  function moneyFromNumber(value) {
854
410
  return value.toFixed(2);
855
411
  }
412
+ var SUPPORTED_CURRENCY_CODES = /* @__PURE__ */ new Set([
413
+ "USD",
414
+ "EUR",
415
+ "GBP",
416
+ "JPY",
417
+ "CNY",
418
+ "CHF",
419
+ "CAD",
420
+ "AUD",
421
+ "GHS",
422
+ "NGN",
423
+ "KES",
424
+ "ZAR",
425
+ "XOF",
426
+ "XAF",
427
+ "EGP",
428
+ "TZS",
429
+ "UGX",
430
+ "RWF",
431
+ "ETB",
432
+ "ZMW",
433
+ "BWP",
434
+ "MUR",
435
+ "NAD",
436
+ "MWK",
437
+ "AOA",
438
+ "CDF",
439
+ "GMD",
440
+ "GNF",
441
+ "LRD",
442
+ "SLL",
443
+ "MZN",
444
+ "BIF",
445
+ "INR",
446
+ "BRL",
447
+ "MXN",
448
+ "KRW",
449
+ "TRY",
450
+ "THB",
451
+ "MYR",
452
+ "PHP",
453
+ "IDR",
454
+ "VND",
455
+ "SGD",
456
+ "HKD",
457
+ "TWD",
458
+ "AED",
459
+ "SAR",
460
+ "ILS"
461
+ ]);
462
+ function isSupportedCurrency(code) {
463
+ return SUPPORTED_CURRENCY_CODES.has(code);
464
+ }
856
465
  function currencyCode(value) {
857
466
  return value;
858
467
  }
@@ -1448,14 +1057,118 @@ var CartOperations = class {
1448
1057
  });
1449
1058
  return ok(found);
1450
1059
  }
1451
- };
1452
-
1453
- // src/constants.ts
1454
- var MOBILE_MONEY_PROVIDER = {
1455
- MTN: "mtn",
1456
- VODAFONE: "vodafone",
1457
- AIRTEL: "airtel"
1458
- };
1060
+ };
1061
+
1062
+ // src/constants.ts
1063
+ var MOBILE_MONEY_PROVIDER = {
1064
+ MTN: "mtn",
1065
+ VODAFONE: "vodafone",
1066
+ AIRTEL: "airtel"
1067
+ };
1068
+
1069
+ // src/utils/price.ts
1070
+ var CURRENCY_SYMBOLS = {
1071
+ // Major world currencies
1072
+ USD: "$",
1073
+ EUR: "\u20AC",
1074
+ GBP: "\xA3",
1075
+ JPY: "\xA5",
1076
+ CNY: "\xA5",
1077
+ CHF: "CHF",
1078
+ CAD: "C$",
1079
+ AUD: "A$",
1080
+ NZD: "NZ$",
1081
+ HKD: "HK$",
1082
+ SGD: "S$",
1083
+ INR: "\u20B9",
1084
+ BRL: "R$",
1085
+ MXN: "MX$",
1086
+ KRW: "\u20A9",
1087
+ RUB: "\u20BD",
1088
+ TRY: "\u20BA",
1089
+ THB: "\u0E3F",
1090
+ PLN: "z\u0142",
1091
+ SEK: "kr",
1092
+ NOK: "kr",
1093
+ DKK: "kr",
1094
+ CZK: "K\u010D",
1095
+ HUF: "Ft",
1096
+ ILS: "\u20AA",
1097
+ AED: "\u062F.\u0625",
1098
+ SAR: "\uFDFC",
1099
+ MYR: "RM",
1100
+ PHP: "\u20B1",
1101
+ IDR: "Rp",
1102
+ VND: "\u20AB",
1103
+ TWD: "NT$",
1104
+ // African currencies
1105
+ GHS: "GH\u20B5",
1106
+ NGN: "\u20A6",
1107
+ KES: "KSh",
1108
+ ZAR: "R",
1109
+ XOF: "CFA",
1110
+ XAF: "FCFA",
1111
+ EGP: "E\xA3",
1112
+ MAD: "MAD",
1113
+ TZS: "TSh",
1114
+ UGX: "USh",
1115
+ RWF: "FRw",
1116
+ ETB: "Br",
1117
+ ZMW: "ZK",
1118
+ BWP: "P",
1119
+ MUR: "\u20A8",
1120
+ SCR: "\u20A8",
1121
+ NAD: "N$",
1122
+ SZL: "E",
1123
+ LSL: "L",
1124
+ MWK: "MK",
1125
+ AOA: "Kz",
1126
+ CDF: "FC",
1127
+ GMD: "D",
1128
+ GNF: "FG",
1129
+ LRD: "L$",
1130
+ SLL: "Le",
1131
+ MZN: "MT",
1132
+ SDG: "SDG",
1133
+ SSP: "SSP",
1134
+ SOS: "Sh.So.",
1135
+ DJF: "Fdj",
1136
+ ERN: "Nfk",
1137
+ CVE: "$",
1138
+ STN: "Db",
1139
+ KMF: "CF",
1140
+ BIF: "FBu"
1141
+ };
1142
+ function getCurrencySymbol(currencyCode2) {
1143
+ return CURRENCY_SYMBOLS[currencyCode2.toUpperCase()] || currencyCode2;
1144
+ }
1145
+ function formatPrice(amount, currency = "GHS", locale = "en-US") {
1146
+ const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
1147
+ if (isNaN(numAmount)) {
1148
+ return `${getCurrencySymbol(currency)}0.00`;
1149
+ }
1150
+ try {
1151
+ return new Intl.NumberFormat(locale, {
1152
+ style: "currency",
1153
+ currency: currency.toUpperCase(),
1154
+ minimumFractionDigits: 2,
1155
+ maximumFractionDigits: 2
1156
+ }).format(numAmount);
1157
+ } catch {
1158
+ return `${getCurrencySymbol(currency)}${numAmount.toFixed(2)}`;
1159
+ }
1160
+ }
1161
+ function parsePrice(value) {
1162
+ if (value === void 0 || value === null) {
1163
+ return 0;
1164
+ }
1165
+ if (typeof value === "number") {
1166
+ return isNaN(value) ? 0 : value;
1167
+ }
1168
+ const cleaned = value.replace(/[^\d.-]/g, "");
1169
+ const parsed = parseFloat(cleaned);
1170
+ return isNaN(parsed) ? 0 : parsed;
1171
+ }
1459
1172
 
1460
1173
  // src/utils/payment.ts
1461
1174
  var PAYMENT_SUCCESS_STATUSES = /* @__PURE__ */ new Set([
@@ -4400,247 +4113,610 @@ var CimplifyClient = class {
4400
4113
  }
4401
4114
  return createElements(this, businessId ?? this.businessId ?? void 0, options);
4402
4115
  }
4403
- };
4404
- function createCimplifyClient(config = {}) {
4405
- return new CimplifyClient(config);
4116
+ };
4117
+ function createCimplifyClient(config = {}) {
4118
+ return new CimplifyClient(config);
4119
+ }
4120
+ var LOCATION_STORAGE_KEY = "cimplify_location_id";
4121
+ var DISPLAY_CURRENCY_STORAGE_KEY = "cimplify_display_currency";
4122
+ var FX_REFRESH_INTERVAL = 12e4;
4123
+ var DEFAULT_CURRENCY = "USD";
4124
+ var DEFAULT_COUNTRY = "US";
4125
+ function createDefaultClient() {
4126
+ const processRef = globalThis.process;
4127
+ const envPublicKey = processRef?.env?.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY || "";
4128
+ return createCimplifyClient({ publicKey: envPublicKey });
4129
+ }
4130
+ function getStoredLocationId() {
4131
+ if (typeof window === "undefined" || !window.localStorage) {
4132
+ return null;
4133
+ }
4134
+ const value = window.localStorage.getItem(LOCATION_STORAGE_KEY);
4135
+ if (!value) {
4136
+ return null;
4137
+ }
4138
+ const normalized = value.trim();
4139
+ return normalized.length > 0 ? normalized : null;
4140
+ }
4141
+ function setStoredLocationId(locationId) {
4142
+ if (typeof window === "undefined" || !window.localStorage) {
4143
+ return;
4144
+ }
4145
+ if (!locationId) {
4146
+ window.localStorage.removeItem(LOCATION_STORAGE_KEY);
4147
+ return;
4148
+ }
4149
+ window.localStorage.setItem(LOCATION_STORAGE_KEY, locationId);
4150
+ }
4151
+ function getStoredDisplayCurrency() {
4152
+ if (typeof window === "undefined" || !window.localStorage) {
4153
+ return null;
4154
+ }
4155
+ const value = window.localStorage.getItem(DISPLAY_CURRENCY_STORAGE_KEY);
4156
+ if (!value) {
4157
+ return null;
4158
+ }
4159
+ const normalized = value.trim().toUpperCase();
4160
+ return normalized.length > 0 ? normalized : null;
4161
+ }
4162
+ function setStoredDisplayCurrency(currency) {
4163
+ if (typeof window === "undefined" || !window.localStorage) {
4164
+ return;
4165
+ }
4166
+ if (!currency) {
4167
+ window.localStorage.removeItem(DISPLAY_CURRENCY_STORAGE_KEY);
4168
+ return;
4169
+ }
4170
+ window.localStorage.setItem(DISPLAY_CURRENCY_STORAGE_KEY, currency.toUpperCase());
4171
+ }
4172
+ function resolveInitialLocation(locations) {
4173
+ if (locations.length === 0) {
4174
+ return null;
4175
+ }
4176
+ const storedId = getStoredLocationId();
4177
+ if (storedId) {
4178
+ const matched = locations.find((location) => location.id === storedId);
4179
+ if (matched) {
4180
+ return matched;
4181
+ }
4182
+ }
4183
+ return locations[0];
4184
+ }
4185
+ var CimplifyContext = createContext(null);
4186
+ function CimplifyProvider({
4187
+ client,
4188
+ children,
4189
+ onLocationChange
4190
+ }) {
4191
+ const resolvedClient = useMemo(() => client ?? createDefaultClient(), [client]);
4192
+ const onLocationChangeRef = useRef(onLocationChange);
4193
+ const [business, setBusiness] = useState(null);
4194
+ const [locations, setLocations] = useState([]);
4195
+ const [currentLocation, setCurrentLocationState] = useState(null);
4196
+ const [isReady, setIsReady] = useState(false);
4197
+ useEffect(() => {
4198
+ onLocationChangeRef.current = onLocationChange;
4199
+ }, [onLocationChange]);
4200
+ const isDemoMode = resolvedClient.getPublicKey().trim().length === 0;
4201
+ const baseCurrency = business?.default_currency || DEFAULT_CURRENCY;
4202
+ const [displayCurrencyOverride, setDisplayCurrencyOverride] = useState(
4203
+ () => getStoredDisplayCurrency()
4204
+ );
4205
+ const [fxRate, setFxRate] = useState(null);
4206
+ const displayCurrency = displayCurrencyOverride && displayCurrencyOverride !== baseCurrency ? displayCurrencyOverride : baseCurrency;
4207
+ const setDisplayCurrency = useCallback(
4208
+ (currency) => {
4209
+ const normalized = currency?.trim().toUpperCase() || null;
4210
+ if (normalized && !isSupportedCurrency(normalized)) {
4211
+ return;
4212
+ }
4213
+ setDisplayCurrencyOverride(normalized);
4214
+ setStoredDisplayCurrency(normalized);
4215
+ if (!normalized || normalized === baseCurrency) {
4216
+ setFxRate(null);
4217
+ }
4218
+ },
4219
+ [baseCurrency]
4220
+ );
4221
+ useEffect(() => {
4222
+ if (displayCurrency === baseCurrency || isDemoMode) {
4223
+ setFxRate(null);
4224
+ return;
4225
+ }
4226
+ let cancelled = false;
4227
+ async function fetchRate() {
4228
+ const result = await resolvedClient.fx.getRate(
4229
+ baseCurrency,
4230
+ displayCurrency
4231
+ );
4232
+ if (cancelled) return;
4233
+ if (result.ok) {
4234
+ setFxRate(result.value.rate);
4235
+ } else {
4236
+ setFxRate(null);
4237
+ }
4238
+ }
4239
+ void fetchRate();
4240
+ const intervalId = setInterval(() => void fetchRate(), FX_REFRESH_INTERVAL);
4241
+ return () => {
4242
+ cancelled = true;
4243
+ clearInterval(intervalId);
4244
+ };
4245
+ }, [resolvedClient, baseCurrency, displayCurrency, isDemoMode]);
4246
+ const convertPrice = useCallback(
4247
+ (amount) => {
4248
+ const num = typeof amount === "string" ? parseFloat(amount) : amount;
4249
+ if (isNaN(num)) return 0;
4250
+ if (!fxRate || displayCurrency === baseCurrency) return num;
4251
+ return Math.round(num * fxRate * 100) / 100;
4252
+ },
4253
+ [fxRate, displayCurrency, baseCurrency]
4254
+ );
4255
+ const setCurrentLocation = useCallback(
4256
+ (location) => {
4257
+ setCurrentLocationState(location);
4258
+ resolvedClient.setLocationId(location.id);
4259
+ setStoredLocationId(location.id);
4260
+ onLocationChangeRef.current?.(location);
4261
+ },
4262
+ [resolvedClient]
4263
+ );
4264
+ useEffect(() => {
4265
+ let cancelled = false;
4266
+ async function bootstrap() {
4267
+ setIsReady(false);
4268
+ if (isDemoMode) {
4269
+ if (!cancelled) {
4270
+ setBusiness(null);
4271
+ setLocations([]);
4272
+ setCurrentLocationState(null);
4273
+ resolvedClient.setLocationId(null);
4274
+ setStoredLocationId(null);
4275
+ setIsReady(true);
4276
+ }
4277
+ return;
4278
+ }
4279
+ const [businessResult, locationsResult] = await Promise.all([
4280
+ resolvedClient.business.getInfo(),
4281
+ resolvedClient.business.getLocations()
4282
+ ]);
4283
+ if (cancelled) {
4284
+ return;
4285
+ }
4286
+ const nextBusiness = businessResult.ok ? businessResult.value : null;
4287
+ const nextLocations = locationsResult.ok && Array.isArray(locationsResult.value) ? locationsResult.value : [];
4288
+ const initialLocation = resolveInitialLocation(nextLocations);
4289
+ setBusiness(nextBusiness);
4290
+ if (nextBusiness?.id) {
4291
+ resolvedClient.setBusinessId(nextBusiness.id);
4292
+ }
4293
+ setLocations(nextLocations);
4294
+ if (initialLocation) {
4295
+ setCurrentLocationState(initialLocation);
4296
+ resolvedClient.setLocationId(initialLocation.id);
4297
+ setStoredLocationId(initialLocation.id);
4298
+ } else {
4299
+ setCurrentLocationState(null);
4300
+ resolvedClient.setLocationId(null);
4301
+ setStoredLocationId(null);
4302
+ }
4303
+ setIsReady(true);
4304
+ }
4305
+ bootstrap().catch(() => {
4306
+ if (cancelled) {
4307
+ return;
4308
+ }
4309
+ setBusiness(null);
4310
+ setLocations([]);
4311
+ setCurrentLocationState(null);
4312
+ resolvedClient.setLocationId(null);
4313
+ setStoredLocationId(null);
4314
+ setIsReady(true);
4315
+ });
4316
+ return () => {
4317
+ cancelled = true;
4318
+ };
4319
+ }, [resolvedClient, isDemoMode]);
4320
+ const contextValue = useMemo(
4321
+ () => ({
4322
+ client: resolvedClient,
4323
+ business,
4324
+ currency: baseCurrency,
4325
+ country: business?.country_code || DEFAULT_COUNTRY,
4326
+ locations,
4327
+ currentLocation,
4328
+ setCurrentLocation,
4329
+ isReady,
4330
+ isDemoMode,
4331
+ baseCurrency,
4332
+ displayCurrency,
4333
+ setDisplayCurrency,
4334
+ convertPrice,
4335
+ fxRate
4336
+ }),
4337
+ [
4338
+ resolvedClient,
4339
+ business,
4340
+ baseCurrency,
4341
+ locations,
4342
+ currentLocation,
4343
+ setCurrentLocation,
4344
+ isReady,
4345
+ isDemoMode,
4346
+ displayCurrency,
4347
+ setDisplayCurrency,
4348
+ convertPrice,
4349
+ fxRate
4350
+ ]
4351
+ );
4352
+ return /* @__PURE__ */ jsx(CimplifyContext.Provider, { value: contextValue, children });
4353
+ }
4354
+ function useCimplify() {
4355
+ const context = useContext(CimplifyContext);
4356
+ if (!context) {
4357
+ throw new Error("useCimplify must be used within CimplifyProvider");
4358
+ }
4359
+ return context;
4406
4360
  }
4407
- var LOCATION_STORAGE_KEY = "cimplify_location_id";
4408
- var DISPLAY_CURRENCY_STORAGE_KEY = "cimplify_display_currency";
4409
- var FX_REFRESH_INTERVAL = 12e4;
4410
- var DEFAULT_CURRENCY = "USD";
4411
- var DEFAULT_COUNTRY = "US";
4412
- function createDefaultClient() {
4413
- const processRef = globalThis.process;
4414
- const envPublicKey = processRef?.env?.NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY || "";
4415
- return createCimplifyClient({ publicKey: envPublicKey });
4361
+ function useOptionalCimplify() {
4362
+ return useContext(CimplifyContext);
4416
4363
  }
4417
- function getStoredLocationId() {
4418
- if (typeof window === "undefined" || !window.localStorage) {
4419
- return null;
4420
- }
4421
- const value = window.localStorage.getItem(LOCATION_STORAGE_KEY);
4422
- if (!value) {
4423
- return null;
4424
- }
4425
- const normalized = value.trim();
4426
- return normalized.length > 0 ? normalized : null;
4364
+ var SPACE = { sm: 8};
4365
+ function shellColors(isDark, primaryColor) {
4366
+ return {
4367
+ text: isDark ? "#f4f4f5" : "#1a1a1a",
4368
+ textSecondary: isDark ? "#a1a1aa" : "#52525b",
4369
+ textMuted: isDark ? "#71717a" : "#a1a1aa",
4370
+ border: isDark ? "#27272a" : "#e4e4e7",
4371
+ surface: isDark ? "#18181b" : "#fafafa",
4372
+ error: "#dc2626",
4373
+ primary: primaryColor
4374
+ };
4427
4375
  }
4428
- function setStoredLocationId(locationId) {
4429
- if (typeof window === "undefined" || !window.localStorage) {
4430
- return;
4376
+ function statusToLabel(status) {
4377
+ if (!status) {
4378
+ return "";
4431
4379
  }
4432
- if (!locationId) {
4433
- window.localStorage.removeItem(LOCATION_STORAGE_KEY);
4434
- return;
4380
+ if (status === "preparing") {
4381
+ return "Preparing checkout";
4435
4382
  }
4436
- window.localStorage.setItem(LOCATION_STORAGE_KEY, locationId);
4437
- }
4438
- function getStoredDisplayCurrency() {
4439
- if (typeof window === "undefined" || !window.localStorage) {
4440
- return null;
4383
+ if (status === "recovering") {
4384
+ return "Resuming payment";
4441
4385
  }
4442
- const value = window.localStorage.getItem(DISPLAY_CURRENCY_STORAGE_KEY);
4443
- if (!value) {
4444
- return null;
4386
+ if (status === "processing") {
4387
+ return "Processing payment";
4445
4388
  }
4446
- const normalized = value.trim().toUpperCase();
4447
- return normalized.length > 0 ? normalized : null;
4448
- }
4449
- function setStoredDisplayCurrency(currency) {
4450
- if (typeof window === "undefined" || !window.localStorage) {
4451
- return;
4389
+ if (status === "awaiting_authorization") {
4390
+ return "Waiting for authorization";
4452
4391
  }
4453
- if (!currency) {
4454
- window.localStorage.removeItem(DISPLAY_CURRENCY_STORAGE_KEY);
4455
- return;
4392
+ if (status === "polling") {
4393
+ return "Confirming payment";
4456
4394
  }
4457
- window.localStorage.setItem(DISPLAY_CURRENCY_STORAGE_KEY, currency.toUpperCase());
4458
- }
4459
- function resolveInitialLocation(locations) {
4460
- if (locations.length === 0) {
4461
- return null;
4395
+ if (status === "finalizing") {
4396
+ return "Finalizing order";
4462
4397
  }
4463
- const storedId = getStoredLocationId();
4464
- if (storedId) {
4465
- const matched = locations.find((location) => location.id === storedId);
4466
- if (matched) {
4467
- return matched;
4468
- }
4398
+ if (status === "success") {
4399
+ return "Payment complete";
4469
4400
  }
4470
- return locations[0];
4401
+ return "Payment failed";
4471
4402
  }
4472
- var CimplifyContext = createContext(null);
4473
- function CimplifyProvider({
4403
+ function CimplifyCheckout({
4474
4404
  client,
4475
- children,
4476
- onLocationChange
4405
+ businessId,
4406
+ cartId,
4407
+ locationId,
4408
+ linkUrl,
4409
+ orderTypes,
4410
+ enrollInLink = true,
4411
+ onComplete,
4412
+ onError,
4413
+ onStatusChange,
4414
+ appearance,
4415
+ demoMode,
4416
+ className
4477
4417
  }) {
4478
- const resolvedClient = useMemo(() => client ?? createDefaultClient(), [client]);
4479
- const onLocationChangeRef = useRef(onLocationChange);
4480
- const [business, setBusiness] = useState(null);
4481
- const [locations, setLocations] = useState([]);
4482
- const [currentLocation, setCurrentLocationState] = useState(null);
4483
- const [isReady, setIsReady] = useState(false);
4484
- useEffect(() => {
4485
- onLocationChangeRef.current = onLocationChange;
4486
- }, [onLocationChange]);
4487
- const isDemoMode = resolvedClient.getPublicKey().trim().length === 0;
4488
- const baseCurrency = business?.default_currency || DEFAULT_CURRENCY;
4489
- const [displayCurrencyOverride, setDisplayCurrencyOverride] = useState(
4490
- () => getStoredDisplayCurrency()
4418
+ const resolvedOrderTypes = useMemo(
4419
+ () => orderTypes && orderTypes.length > 0 ? orderTypes : ["pickup", "delivery"],
4420
+ [orderTypes]
4491
4421
  );
4492
- const [fxRate, setFxRate] = useState(null);
4493
- const displayCurrency = displayCurrencyOverride && displayCurrencyOverride !== baseCurrency ? displayCurrencyOverride : baseCurrency;
4494
- const setDisplayCurrency = useCallback(
4495
- (currency) => {
4496
- const normalized = currency?.trim().toUpperCase() || null;
4497
- setDisplayCurrencyOverride(normalized);
4498
- setStoredDisplayCurrency(normalized);
4499
- if (!normalized || normalized === baseCurrency) {
4500
- setFxRate(null);
4501
- }
4502
- },
4503
- [baseCurrency]
4422
+ const [orderType, setOrderType] = useState(resolvedOrderTypes[0] || "pickup");
4423
+ const [status, setStatus] = useState(null);
4424
+ const [statusText, setStatusText] = useState("");
4425
+ const [isSubmitting, setIsSubmitting] = useState(false);
4426
+ const [isInitializing, setIsInitializing] = useState(false);
4427
+ const [errorMessage, setErrorMessage] = useState(null);
4428
+ const [resolvedBusinessId, setResolvedBusinessId] = useState(businessId ?? null);
4429
+ const [resolvedCartId, setResolvedCartId] = useState(cartId ?? null);
4430
+ const [resolvedCart, setResolvedCart] = useState(null);
4431
+ const checkoutMountRef = useRef(null);
4432
+ const elementsRef = useRef(null);
4433
+ const activeCheckoutRef = useRef(null);
4434
+ const initialAppearanceRef = useRef(appearance);
4435
+ const hasWarnedInlineAppearanceRef = useRef(false);
4436
+ const isMountedRef = useRef(true);
4437
+ const demoRunRef = useRef(0);
4438
+ const isDemoCheckout = demoMode ?? client.getPublicKey().trim().length === 0;
4439
+ const isTestMode = client.isTestMode();
4440
+ const cimplifyCtx = useOptionalCimplify();
4441
+ const fxOptions = useMemo(() => {
4442
+ if (!cimplifyCtx?.fxRate) return void 0;
4443
+ if (cimplifyCtx.displayCurrency === cimplifyCtx.baseCurrency) return void 0;
4444
+ return {
4445
+ displayCurrency: cimplifyCtx.displayCurrency,
4446
+ convertPrice: cimplifyCtx.convertPrice
4447
+ };
4448
+ }, [cimplifyCtx?.fxRate, cimplifyCtx?.displayCurrency, cimplifyCtx?.baseCurrency, cimplifyCtx?.convertPrice]);
4449
+ const fxOptionsRef = useRef(fxOptions);
4450
+ fxOptionsRef.current = fxOptions;
4451
+ const resolvedCartRef = useRef(resolvedCart);
4452
+ resolvedCartRef.current = resolvedCart;
4453
+ const primaryColor = appearance?.variables?.primaryColor || "#0a2540";
4454
+ const isDark = appearance?.theme === "dark";
4455
+ const emitStatus = React3.useEffectEvent(
4456
+ (nextStatus, context = {}) => {
4457
+ setStatus(nextStatus);
4458
+ setStatusText(context.display_text || "");
4459
+ onStatusChange?.(nextStatus, context);
4460
+ }
4461
+ );
4462
+ const fireError = React3.useEffectEvent(
4463
+ (error) => {
4464
+ onError?.(error);
4465
+ }
4504
4466
  );
4505
4467
  useEffect(() => {
4506
- if (displayCurrency === baseCurrency || isDemoMode) {
4507
- setFxRate(null);
4508
- return;
4468
+ if (!resolvedOrderTypes.includes(orderType)) {
4469
+ setOrderType(resolvedOrderTypes[0] || "pickup");
4509
4470
  }
4510
- let cancelled = false;
4511
- async function fetchRate() {
4512
- const result = await resolvedClient.fx.getRate(
4513
- baseCurrency,
4514
- displayCurrency
4471
+ }, [resolvedOrderTypes, orderType]);
4472
+ useEffect(() => {
4473
+ if (appearance && appearance !== initialAppearanceRef.current && !hasWarnedInlineAppearanceRef.current) {
4474
+ hasWarnedInlineAppearanceRef.current = true;
4475
+ console.warn(
4476
+ "[Cimplify] `appearance` prop reference changed after mount. Elements keep the initial appearance to avoid iframe remount. Memoize appearance with useMemo() to remove this warning."
4515
4477
  );
4516
- if (!cancelled && result.ok) {
4517
- setFxRate(result.value.rate);
4478
+ }
4479
+ }, [appearance]);
4480
+ useEffect(() => {
4481
+ let cancelled = false;
4482
+ async function bootstrap() {
4483
+ if (isDemoCheckout) {
4484
+ if (!cancelled) {
4485
+ setResolvedBusinessId(businessId ?? null);
4486
+ setResolvedCartId(cartId ?? "cart_demo");
4487
+ setIsInitializing(false);
4488
+ setErrorMessage(null);
4489
+ }
4490
+ return;
4491
+ }
4492
+ const needsBusinessResolve = !businessId;
4493
+ const needsCartResolve = !cartId;
4494
+ if (!needsBusinessResolve && !needsCartResolve) {
4495
+ if (!cancelled) {
4496
+ setResolvedBusinessId(businessId || null);
4497
+ setResolvedCartId(cartId || null);
4498
+ setIsInitializing(false);
4499
+ setErrorMessage(null);
4500
+ }
4501
+ client.cart.get().then((cartResult) => {
4502
+ if (!cancelled && cartResult.ok && cartResult.value) {
4503
+ setResolvedCart(cartResult.value);
4504
+ }
4505
+ }).catch(() => {
4506
+ });
4507
+ return;
4508
+ }
4509
+ if (!cancelled) {
4510
+ setIsInitializing(true);
4511
+ setErrorMessage(null);
4512
+ }
4513
+ let nextBusinessId = businessId ?? null;
4514
+ if (!nextBusinessId) {
4515
+ try {
4516
+ nextBusinessId = await client.resolveBusinessId();
4517
+ } catch {
4518
+ if (!cancelled) {
4519
+ const message = "Unable to initialize checkout business context.";
4520
+ setResolvedBusinessId(null);
4521
+ setResolvedCartId(null);
4522
+ setErrorMessage(message);
4523
+ setIsInitializing(false);
4524
+ fireError({ code: "BUSINESS_ID_REQUIRED", message });
4525
+ }
4526
+ return;
4527
+ }
4528
+ }
4529
+ let nextCartId = cartId ?? null;
4530
+ if (!nextCartId) {
4531
+ const cartResult = await client.cart.get();
4532
+ if (!cartResult.ok || !cartResult.value?.id || cartResult.value.items.length === 0) {
4533
+ if (!cancelled) {
4534
+ const message = "Your cart is empty. Add items before checkout.";
4535
+ setResolvedBusinessId(nextBusinessId);
4536
+ setResolvedCartId(null);
4537
+ setErrorMessage(message);
4538
+ setIsInitializing(false);
4539
+ fireError({ code: "CART_EMPTY", message });
4540
+ }
4541
+ return;
4542
+ }
4543
+ nextCartId = cartResult.value.id;
4544
+ if (!cancelled) {
4545
+ setResolvedCart(cartResult.value);
4546
+ }
4547
+ }
4548
+ if (!cancelled) {
4549
+ setResolvedBusinessId(nextBusinessId);
4550
+ setResolvedCartId(nextCartId);
4551
+ setIsInitializing(false);
4552
+ setErrorMessage(null);
4518
4553
  }
4519
4554
  }
4520
- void fetchRate();
4521
- const intervalId = setInterval(() => void fetchRate(), FX_REFRESH_INTERVAL);
4555
+ void bootstrap();
4522
4556
  return () => {
4523
4557
  cancelled = true;
4524
- clearInterval(intervalId);
4525
4558
  };
4526
- }, [resolvedClient, baseCurrency, displayCurrency, isDemoMode]);
4527
- const convertPrice = useCallback(
4528
- (amount) => {
4529
- const num = typeof amount === "string" ? parseFloat(amount) : amount;
4530
- if (isNaN(num)) return 0;
4531
- if (!fxRate || displayCurrency === baseCurrency) return num;
4532
- return Math.round(num * fxRate * 100) / 100;
4533
- },
4534
- [fxRate, displayCurrency, baseCurrency]
4535
- );
4536
- const setCurrentLocation = useCallback(
4537
- (location) => {
4538
- setCurrentLocationState(location);
4539
- resolvedClient.setLocationId(location.id);
4540
- setStoredLocationId(location.id);
4541
- onLocationChangeRef.current?.(location);
4542
- },
4543
- [resolvedClient]
4544
- );
4559
+ }, [businessId, cartId, client, isDemoCheckout]);
4545
4560
  useEffect(() => {
4546
- let cancelled = false;
4547
- async function bootstrap() {
4548
- setIsReady(false);
4549
- if (isDemoMode) {
4550
- if (!cancelled) {
4551
- setBusiness(null);
4552
- setLocations([]);
4553
- setCurrentLocationState(null);
4554
- resolvedClient.setLocationId(null);
4555
- setStoredLocationId(null);
4556
- setIsReady(true);
4561
+ return () => {
4562
+ isMountedRef.current = false;
4563
+ demoRunRef.current += 1;
4564
+ activeCheckoutRef.current?.abort();
4565
+ activeCheckoutRef.current = null;
4566
+ };
4567
+ }, []);
4568
+ const handleSubmit = React3.useEffectEvent(async () => {
4569
+ if (isSubmitting || isInitializing || !resolvedCartId) {
4570
+ if (!resolvedCartId && !isInitializing) {
4571
+ const message = "Your cart is empty. Add items before checkout.";
4572
+ setErrorMessage(message);
4573
+ fireError({ code: "CART_EMPTY", message });
4574
+ }
4575
+ return;
4576
+ }
4577
+ setErrorMessage(null);
4578
+ setIsSubmitting(true);
4579
+ emitStatus("preparing", { display_text: statusToLabel("preparing") });
4580
+ if (isDemoCheckout) {
4581
+ const runId = demoRunRef.current + 1;
4582
+ demoRunRef.current = runId;
4583
+ const wait = async (ms) => {
4584
+ await new Promise((resolve) => setTimeout(resolve, ms));
4585
+ return isMountedRef.current && runId === demoRunRef.current;
4586
+ };
4587
+ try {
4588
+ if (!await wait(400)) return;
4589
+ emitStatus("processing", { display_text: statusToLabel("processing") });
4590
+ if (!await wait(900)) return;
4591
+ emitStatus("polling", { display_text: statusToLabel("polling") });
4592
+ if (!await wait(1200)) return;
4593
+ const result = {
4594
+ success: true,
4595
+ order: {
4596
+ id: `ord_demo_${Date.now()}`,
4597
+ order_number: `DEMO-${Math.random().toString(36).slice(2, 8).toUpperCase()}`,
4598
+ status: "confirmed",
4599
+ total: "0.00",
4600
+ currency: "USD"
4601
+ }
4602
+ };
4603
+ emitStatus("success", {
4604
+ order_id: result.order?.id,
4605
+ order_number: result.order?.order_number,
4606
+ display_text: statusToLabel("success")
4607
+ });
4608
+ onComplete(result);
4609
+ } finally {
4610
+ if (isMountedRef.current && runId === demoRunRef.current) {
4611
+ setIsSubmitting(false);
4557
4612
  }
4558
- return;
4559
4613
  }
4560
- const [businessResult, locationsResult] = await Promise.all([
4561
- resolvedClient.business.getInfo(),
4562
- resolvedClient.business.getLocations()
4563
- ]);
4564
- if (cancelled) {
4614
+ return;
4615
+ }
4616
+ if (!elementsRef.current) {
4617
+ const message = "Checkout is still initializing. Please try again.";
4618
+ setErrorMessage(message);
4619
+ fireError({ code: "CHECKOUT_NOT_READY", message });
4620
+ setIsSubmitting(false);
4621
+ return;
4622
+ }
4623
+ const checkout = elementsRef.current.processCheckout({
4624
+ cart_id: resolvedCartId,
4625
+ location_id: locationId ?? client.getLocationId() ?? void 0,
4626
+ order_type: orderType,
4627
+ enroll_in_link: enrollInLink,
4628
+ on_status_change: emitStatus,
4629
+ pay_currency: fxOptions?.displayCurrency
4630
+ });
4631
+ activeCheckoutRef.current = checkout;
4632
+ try {
4633
+ const result = await checkout;
4634
+ if (result.success) {
4635
+ onComplete(result);
4565
4636
  return;
4566
4637
  }
4567
- const nextBusiness = businessResult.ok ? businessResult.value : null;
4568
- const nextLocations = locationsResult.ok && Array.isArray(locationsResult.value) ? locationsResult.value : [];
4569
- const initialLocation = resolveInitialLocation(nextLocations);
4570
- setBusiness(nextBusiness);
4571
- if (nextBusiness?.id) {
4572
- resolvedClient.setBusinessId(nextBusiness.id);
4573
- }
4574
- setLocations(nextLocations);
4575
- if (initialLocation) {
4576
- setCurrentLocationState(initialLocation);
4577
- resolvedClient.setLocationId(initialLocation.id);
4578
- setStoredLocationId(initialLocation.id);
4579
- } else {
4580
- setCurrentLocationState(null);
4581
- resolvedClient.setLocationId(null);
4582
- setStoredLocationId(null);
4638
+ const code = result.error?.code || "CHECKOUT_FAILED";
4639
+ const message = result.error?.message || "Payment failed.";
4640
+ setErrorMessage(message);
4641
+ fireError({ code, message });
4642
+ } finally {
4643
+ if (isMountedRef.current) {
4644
+ activeCheckoutRef.current = null;
4645
+ setIsSubmitting(false);
4583
4646
  }
4584
- setIsReady(true);
4585
4647
  }
4586
- bootstrap().catch(() => {
4587
- if (cancelled) {
4588
- return;
4648
+ });
4649
+ useEffect(() => {
4650
+ if (isDemoCheckout || !resolvedBusinessId) {
4651
+ elementsRef.current = null;
4652
+ return;
4653
+ }
4654
+ const elements = client.elements(resolvedBusinessId, {
4655
+ appearance: initialAppearanceRef.current,
4656
+ linkUrl
4657
+ });
4658
+ elementsRef.current = elements;
4659
+ const checkout = elements.create("checkout", {
4660
+ orderTypes: resolvedOrderTypes,
4661
+ defaultOrderType: resolvedOrderTypes[0]
4662
+ });
4663
+ if (checkoutMountRef.current) {
4664
+ checkout.mount(checkoutMountRef.current);
4665
+ }
4666
+ checkout.on("ready", () => {
4667
+ const cart = resolvedCartRef.current;
4668
+ if (cart) {
4669
+ checkout.setCart(transformToCheckoutCart(cart, fxOptionsRef.current));
4589
4670
  }
4590
- setBusiness(null);
4591
- setLocations([]);
4592
- setCurrentLocationState(null);
4593
- resolvedClient.setLocationId(null);
4594
- setStoredLocationId(null);
4595
- setIsReady(true);
4671
+ });
4672
+ checkout.on("order_type_changed", (data) => {
4673
+ const typed = data;
4674
+ if (typed.orderType) {
4675
+ setOrderType(typed.orderType);
4676
+ }
4677
+ });
4678
+ checkout.on("request_submit", () => {
4679
+ void handleSubmit();
4596
4680
  });
4597
4681
  return () => {
4598
- cancelled = true;
4682
+ activeCheckoutRef.current?.abort();
4683
+ activeCheckoutRef.current = null;
4684
+ elements.destroy();
4685
+ elementsRef.current = null;
4599
4686
  };
4600
- }, [resolvedClient, isDemoMode]);
4601
- const contextValue = useMemo(
4602
- () => ({
4603
- client: resolvedClient,
4604
- business,
4605
- currency: baseCurrency,
4606
- country: business?.country_code || DEFAULT_COUNTRY,
4607
- locations,
4608
- currentLocation,
4609
- setCurrentLocation,
4610
- isReady,
4611
- isDemoMode,
4612
- baseCurrency,
4613
- displayCurrency,
4614
- setDisplayCurrency,
4615
- convertPrice,
4616
- fxRate
4617
- }),
4618
- [
4619
- resolvedClient,
4620
- business,
4621
- baseCurrency,
4622
- locations,
4623
- currentLocation,
4624
- setCurrentLocation,
4625
- isReady,
4626
- isDemoMode,
4627
- displayCurrency,
4628
- setDisplayCurrency,
4629
- convertPrice,
4630
- fxRate
4631
- ]
4632
- );
4633
- return /* @__PURE__ */ jsx(CimplifyContext.Provider, { value: contextValue, children });
4634
- }
4635
- function useCimplify() {
4636
- const context = useContext(CimplifyContext);
4637
- if (!context) {
4638
- throw new Error("useCimplify must be used within CimplifyProvider");
4687
+ }, [client, resolvedBusinessId, isDemoCheckout]);
4688
+ useEffect(() => {
4689
+ if (!resolvedCart || !elementsRef.current) return;
4690
+ const checkoutElement = elementsRef.current.getElement("checkout");
4691
+ if (checkoutElement) {
4692
+ checkoutElement.setCart(transformToCheckoutCart(resolvedCart, fxOptions));
4693
+ }
4694
+ }, [resolvedCart, fxOptions]);
4695
+ const colors = shellColors(isDark ?? false, primaryColor);
4696
+ if (isInitializing) {
4697
+ return /* @__PURE__ */ jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { fontSize: 13, color: colors.textSecondary }, children: "Preparing checkout..." }) });
4639
4698
  }
4640
- return context;
4641
- }
4642
- function useOptionalCimplify() {
4643
- return useContext(CimplifyContext);
4699
+ if (!isDemoCheckout && (!resolvedBusinessId || !resolvedCartId)) {
4700
+ return /* @__PURE__ */ jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { fontSize: 13, color: colors.error }, children: errorMessage || "Unable to initialize checkout. Please refresh and try again." }) });
4701
+ }
4702
+ return /* @__PURE__ */ jsxs("div", { className, "data-cimplify-checkout": "", children: [
4703
+ isTestMode && !isDemoCheckout && /* @__PURE__ */ jsx(
4704
+ "p",
4705
+ {
4706
+ "data-cimplify-test-mode": "",
4707
+ style: {
4708
+ marginBottom: "10px",
4709
+ fontSize: "12px",
4710
+ fontWeight: 600,
4711
+ color: "#92400e"
4712
+ },
4713
+ children: "Test mode - no real charges"
4714
+ }
4715
+ ),
4716
+ /* @__PURE__ */ jsx("div", { "data-cimplify-section": "checkout", children: /* @__PURE__ */ jsx("div", { ref: isDemoCheckout ? void 0 : checkoutMountRef }) }),
4717
+ status && /* @__PURE__ */ jsx("p", { "data-cimplify-status": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.textSecondary }, children: statusText || statusToLabel(status) }),
4718
+ errorMessage && /* @__PURE__ */ jsx("p", { "data-cimplify-error": "", style: { marginTop: SPACE.sm, fontSize: 13, color: colors.error }, children: errorMessage })
4719
+ ] });
4644
4720
  }
4645
4721
  function Price({ amount, className, prefix }) {
4646
4722
  const { displayCurrency, convertPrice } = useCimplify();