@cimplify/sdk 0.8.5 → 0.8.7

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
@@ -742,6 +742,110 @@ function CimplifyCheckout({
742
742
  ] });
743
743
  }
744
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
+
745
849
  // src/types/common.ts
746
850
  function money(value) {
747
851
  return value;
@@ -1353,19 +1457,6 @@ var MOBILE_MONEY_PROVIDER = {
1353
1457
  AIRTEL: "airtel"
1354
1458
  };
1355
1459
 
1356
- // src/utils/price.ts
1357
- function parsePrice(value) {
1358
- if (value === void 0 || value === null) {
1359
- return 0;
1360
- }
1361
- if (typeof value === "number") {
1362
- return isNaN(value) ? 0 : value;
1363
- }
1364
- const cleaned = value.replace(/[^\d.-]/g, "");
1365
- const parsed = parseFloat(cleaned);
1366
- return isNaN(parsed) ? 0 : parsed;
1367
- }
1368
-
1369
1460
  // src/utils/payment.ts
1370
1461
  var PAYMENT_SUCCESS_STATUSES = /* @__PURE__ */ new Set([
1371
1462
  "success",
@@ -1557,6 +1648,10 @@ var DEFAULT_POLL_INTERVAL_MS = 3e3;
1557
1648
  var DEFAULT_MAX_POLL_ATTEMPTS = 60;
1558
1649
  var MAX_CONSECUTIVE_NETWORK_ERRORS = 5;
1559
1650
  var CARD_PROVIDER_PAYSTACK = "paystack";
1651
+ var NEXT_ACTION_NONE = "none";
1652
+ var NEXT_ACTION_CARD_POPUP = "card_popup";
1653
+ var NEXT_ACTION_REDIRECT = "redirect";
1654
+ var NEXT_ACTION_AUTHORIZATION = "authorization";
1560
1655
  function normalizeCardPopupError(error) {
1561
1656
  if (!error || error === "PAYMENT_CANCELLED" || error === "CANCELLED") {
1562
1657
  return "PAYMENT_CANCELLED";
@@ -1596,7 +1691,8 @@ function isValidMobileMoneyProvider(value) {
1596
1691
  return VALID_MOBILE_MONEY_PROVIDERS.has(value);
1597
1692
  }
1598
1693
  function normalizeAuthorizationType(value) {
1599
- return value === "otp" || value === "pin" ? value : void 0;
1694
+ const lower = value?.toLowerCase();
1695
+ return lower === "otp" || lower === "pin" ? lower : void 0;
1600
1696
  }
1601
1697
  function formatMoney2(value) {
1602
1698
  if (typeof value === "number" && Number.isFinite(value)) {
@@ -1643,6 +1739,9 @@ var CheckoutResolver = class {
1643
1739
  if (!checkoutResult.order_id) {
1644
1740
  return this.fail("CHECKOUT_FAILED", "Checkout did not return an order ID.", false);
1645
1741
  }
1742
+ if (checkoutResult.next_action) {
1743
+ return this.resolveNextAction(checkoutResult);
1744
+ }
1646
1745
  let latestCheckoutResult = checkoutResult;
1647
1746
  let authorizationType = normalizeAuthorizationType(checkoutResult.authorization_type);
1648
1747
  let paymentReference = checkoutResult.payment_reference;
@@ -1758,6 +1857,110 @@ var CheckoutResolver = class {
1758
1857
  return this.fail("CHECKOUT_FAILED", message, true);
1759
1858
  }
1760
1859
  }
1860
+ async resolveNextAction(checkoutResult) {
1861
+ const action = checkoutResult.next_action;
1862
+ if (this.isSuccessfulStatus(checkoutResult.payment_status)) {
1863
+ return this.finalizeSuccess(checkoutResult);
1864
+ }
1865
+ let latestCheckoutResult = checkoutResult;
1866
+ let paymentReference = checkoutResult.payment_reference;
1867
+ switch (action.type) {
1868
+ case NEXT_ACTION_NONE:
1869
+ return this.finalizeSuccess(checkoutResult);
1870
+ case NEXT_ACTION_CARD_POPUP: {
1871
+ const provider = normalizeCardProvider(action.provider);
1872
+ this.emit("awaiting_authorization", {
1873
+ display_text: "Complete payment in the card authorization popup.",
1874
+ order_id: checkoutResult.order_id,
1875
+ order_number: checkoutResult.order_number
1876
+ });
1877
+ if (!this.allowPopups) {
1878
+ return this.fail(
1879
+ "PROVIDER_UNAVAILABLE",
1880
+ "Card payment popup is unavailable in this environment.",
1881
+ false
1882
+ );
1883
+ }
1884
+ const popupResult = await openCardPopup(
1885
+ provider,
1886
+ checkoutResult,
1887
+ this.checkoutData.customer.email || "customer@cimplify.io",
1888
+ this.getOrderCurrency(checkoutResult),
1889
+ this.signal
1890
+ );
1891
+ if (!popupResult.success) {
1892
+ const popupError = normalizeCardPopupError(popupResult.error);
1893
+ if (popupError === "POPUP_BLOCKED") {
1894
+ return this.fail(
1895
+ "POPUP_BLOCKED",
1896
+ "Unable to open card payment popup. Please allow popups and try again.",
1897
+ true
1898
+ );
1899
+ }
1900
+ if (popupError === "PROVIDER_UNAVAILABLE") {
1901
+ return this.fail("PROVIDER_UNAVAILABLE", "Card payment provider is unavailable.", false);
1902
+ }
1903
+ return this.fail("PAYMENT_CANCELLED", "Card payment was cancelled before completion.", true);
1904
+ }
1905
+ paymentReference = popupResult.reference || paymentReference;
1906
+ break;
1907
+ }
1908
+ case NEXT_ACTION_REDIRECT: {
1909
+ if (typeof window !== "undefined" && this.returnUrl) {
1910
+ window.location.assign(action.authorization_url);
1911
+ return this.fail("REDIRECT_REQUIRED", "Redirecting to complete payment authorization.", true);
1912
+ }
1913
+ if (typeof window !== "undefined") {
1914
+ const popup = window.open(
1915
+ action.authorization_url,
1916
+ "cimplify-auth",
1917
+ "width=520,height=760,noopener,noreferrer"
1918
+ );
1919
+ if (!popup) {
1920
+ return this.fail(
1921
+ "POPUP_BLOCKED",
1922
+ "Authorization popup was blocked. Please allow popups and retry.",
1923
+ true
1924
+ );
1925
+ }
1926
+ }
1927
+ break;
1928
+ }
1929
+ case NEXT_ACTION_AUTHORIZATION: {
1930
+ const authType = normalizeAuthorizationType(action.authorization_type);
1931
+ const authorization = await this.handleAuthorization({
1932
+ authorizationType: authType,
1933
+ paymentReference,
1934
+ displayText: action.display_text,
1935
+ provider: checkoutResult.provider
1936
+ });
1937
+ if (!authorization.ok) {
1938
+ return authorization.result;
1939
+ }
1940
+ if (authorization.value) {
1941
+ latestCheckoutResult = authorization.value;
1942
+ paymentReference = authorization.value.payment_reference || paymentReference;
1943
+ if (this.isSuccessfulStatus(authorization.value.payment_status)) {
1944
+ return this.finalizeSuccess(authorization.value);
1945
+ }
1946
+ if (this.isFailureStatus(authorization.value.payment_status)) {
1947
+ return this.fail(
1948
+ "AUTHORIZATION_FAILED",
1949
+ authorization.value.display_text || "Payment authorization failed.",
1950
+ false
1951
+ );
1952
+ }
1953
+ }
1954
+ break;
1955
+ }
1956
+ }
1957
+ return this.pollUntilTerminal({
1958
+ orderId: checkoutResult.order_id,
1959
+ latestCheckoutResult,
1960
+ paymentReference,
1961
+ authorizationType: normalizeAuthorizationType(checkoutResult.authorization_type)
1962
+ });
1963
+ }
1761
1964
  async pollUntilTerminal(input) {
1762
1965
  let consecutiveErrors = 0;
1763
1966
  let latestCheckoutResult = input.latestCheckoutResult;
@@ -4202,6 +4405,8 @@ function createCimplifyClient(config = {}) {
4202
4405
  return new CimplifyClient(config);
4203
4406
  }
4204
4407
  var LOCATION_STORAGE_KEY = "cimplify_location_id";
4408
+ var DISPLAY_CURRENCY_STORAGE_KEY = "cimplify_display_currency";
4409
+ var FX_REFRESH_INTERVAL = 12e4;
4205
4410
  var DEFAULT_CURRENCY = "USD";
4206
4411
  var DEFAULT_COUNTRY = "US";
4207
4412
  function createDefaultClient() {
@@ -4230,6 +4435,27 @@ function setStoredLocationId(locationId) {
4230
4435
  }
4231
4436
  window.localStorage.setItem(LOCATION_STORAGE_KEY, locationId);
4232
4437
  }
4438
+ function getStoredDisplayCurrency() {
4439
+ if (typeof window === "undefined" || !window.localStorage) {
4440
+ return null;
4441
+ }
4442
+ const value = window.localStorage.getItem(DISPLAY_CURRENCY_STORAGE_KEY);
4443
+ if (!value) {
4444
+ return null;
4445
+ }
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;
4452
+ }
4453
+ if (!currency) {
4454
+ window.localStorage.removeItem(DISPLAY_CURRENCY_STORAGE_KEY);
4455
+ return;
4456
+ }
4457
+ window.localStorage.setItem(DISPLAY_CURRENCY_STORAGE_KEY, currency.toUpperCase());
4458
+ }
4233
4459
  function resolveInitialLocation(locations) {
4234
4460
  if (locations.length === 0) {
4235
4461
  return null;
@@ -4259,6 +4485,54 @@ function CimplifyProvider({
4259
4485
  onLocationChangeRef.current = onLocationChange;
4260
4486
  }, [onLocationChange]);
4261
4487
  const isDemoMode = resolvedClient.getPublicKey().trim().length === 0;
4488
+ const baseCurrency = business?.default_currency || DEFAULT_CURRENCY;
4489
+ const [displayCurrencyOverride, setDisplayCurrencyOverride] = useState(
4490
+ () => getStoredDisplayCurrency()
4491
+ );
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]
4504
+ );
4505
+ useEffect(() => {
4506
+ if (displayCurrency === baseCurrency || isDemoMode) {
4507
+ setFxRate(null);
4508
+ return;
4509
+ }
4510
+ let cancelled = false;
4511
+ async function fetchRate() {
4512
+ const result = await resolvedClient.fx.getRate(
4513
+ baseCurrency,
4514
+ displayCurrency
4515
+ );
4516
+ if (!cancelled && result.ok) {
4517
+ setFxRate(result.value.rate);
4518
+ }
4519
+ }
4520
+ void fetchRate();
4521
+ const intervalId = setInterval(() => void fetchRate(), FX_REFRESH_INTERVAL);
4522
+ return () => {
4523
+ cancelled = true;
4524
+ clearInterval(intervalId);
4525
+ };
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
+ );
4262
4536
  const setCurrentLocation = useCallback(
4263
4537
  (location) => {
4264
4538
  setCurrentLocationState(location);
@@ -4328,22 +4602,32 @@ function CimplifyProvider({
4328
4602
  () => ({
4329
4603
  client: resolvedClient,
4330
4604
  business,
4331
- currency: business?.default_currency || DEFAULT_CURRENCY,
4605
+ currency: baseCurrency,
4332
4606
  country: business?.country_code || DEFAULT_COUNTRY,
4333
4607
  locations,
4334
4608
  currentLocation,
4335
4609
  setCurrentLocation,
4336
4610
  isReady,
4337
- isDemoMode
4611
+ isDemoMode,
4612
+ baseCurrency,
4613
+ displayCurrency,
4614
+ setDisplayCurrency,
4615
+ convertPrice,
4616
+ fxRate
4338
4617
  }),
4339
4618
  [
4340
4619
  resolvedClient,
4341
4620
  business,
4621
+ baseCurrency,
4342
4622
  locations,
4343
4623
  currentLocation,
4344
4624
  setCurrentLocation,
4345
4625
  isReady,
4346
- isDemoMode
4626
+ isDemoMode,
4627
+ displayCurrency,
4628
+ setDisplayCurrency,
4629
+ convertPrice,
4630
+ fxRate
4347
4631
  ]
4348
4632
  );
4349
4633
  return /* @__PURE__ */ jsx(CimplifyContext.Provider, { value: contextValue, children });
@@ -4358,6 +4642,13 @@ function useCimplify() {
4358
4642
  function useOptionalCimplify() {
4359
4643
  return useContext(CimplifyContext);
4360
4644
  }
4645
+ function Price({ amount, className, prefix }) {
4646
+ const { displayCurrency, convertPrice } = useCimplify();
4647
+ return /* @__PURE__ */ jsxs("span", { className, children: [
4648
+ prefix,
4649
+ formatPrice(convertPrice(amount), displayCurrency)
4650
+ ] });
4651
+ }
4361
4652
  var productsCache = /* @__PURE__ */ new Map();
4362
4653
  var productsInflight = /* @__PURE__ */ new Map();
4363
4654
  function buildProductsCacheKey(client, locationId, options) {
@@ -6387,4 +6678,4 @@ function useCheckout() {
6387
6678
  return { submit, process, isLoading };
6388
6679
  }
6389
6680
 
6390
- export { Ad, AdProvider, AddressElement, AuthElement, CimplifyCheckout, CimplifyProvider, ElementsProvider, PaymentElement, useAds, useBundle, useCart, useCategories, useCheckout, useCimplify, useCollection, useCollections, useComposite, useElements, useElementsReady, useLocations, useOptionalCimplify, useOrder, useProduct, useProducts, useQuote, useSearch };
6681
+ export { Ad, AdProvider, AddressElement, AuthElement, CimplifyCheckout, CimplifyProvider, ElementsProvider, PaymentElement, Price, useAds, useBundle, useCart, useCategories, useCheckout, useCimplify, useCollection, useCollections, useComposite, useElements, useElementsReady, useLocations, useOptionalCimplify, useOrder, useProduct, useProducts, useQuote, useSearch };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",