@solvapay/react 1.2.1 → 1.3.0-preview-cb8ad16e16ce8f8f357bd7f2e7068d85159df587

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/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  confirmPayment: () => confirmPayment,
62
62
  createAnonymousAuthAdapter: () => createAnonymousAuthAdapter,
63
63
  createHttpTransport: () => createHttpTransport,
64
+ createSessionAuthAdapter: () => createSessionAuthAdapter,
64
65
  defaultAuthAdapter: () => defaultAuthAdapter,
65
66
  deriveVariant: () => deriveVariant,
66
67
  enCopy: () => enCopy,
@@ -263,6 +264,17 @@ async function buildRequestHeaders(config) {
263
264
  return { headers, userId };
264
265
  }
265
266
 
267
+ // src/utils/readErrorMessage.ts
268
+ async function readErrorMessage(res, fallbackPrefix) {
269
+ let serverMessage;
270
+ try {
271
+ const data = await res.clone().json();
272
+ serverMessage = data?.error;
273
+ } catch {
274
+ }
275
+ return serverMessage || `${fallbackPrefix}: ${res.statusText || res.status}`;
276
+ }
277
+
266
278
  // src/transport/http.ts
267
279
  async function request(config, url, opts) {
268
280
  const { headers } = await buildRequestHeaders(config);
@@ -273,13 +285,8 @@ async function request(config, url, opts) {
273
285
  }
274
286
  const res = await fetchFn(url, init);
275
287
  if (!res.ok) {
276
- let serverMessage;
277
- try {
278
- const data = await res.clone().json();
279
- serverMessage = data?.error;
280
- } catch {
281
- }
282
- const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
288
+ const message = await readErrorMessage(res, opts.errorPrefix);
289
+ const error = new Error(message);
283
290
  config?.onError?.(error, opts.onErrorContext);
284
291
  throw error;
285
292
  }
@@ -319,6 +326,7 @@ function createHttpTransport(config) {
319
326
  const body = {};
320
327
  if (params.planRef) body.planRef = params.planRef;
321
328
  if (params.productRef) body.productRef = params.productRef;
329
+ if (params.currency) body.currency = params.currency;
322
330
  if (params.customer && (params.customer.name || params.customer.email)) {
323
331
  body.customer = params.customer;
324
332
  }
@@ -551,6 +559,7 @@ var enCopy = {
551
559
  currentBadge: "Current",
552
560
  popularBadge: "Popular",
553
561
  freeBadge: "Free",
562
+ usageRateLabel: "Usage-based",
554
563
  perIntervalShort: "/{interval}",
555
564
  continueButton: "Continue",
556
565
  backButton: "\u2190 Back to plans",
@@ -1426,7 +1435,7 @@ var MissingProductRefError = class extends SolvaPayError {
1426
1435
  };
1427
1436
 
1428
1437
  // src/hooks/useCheckout.ts
1429
- var import_react6 = require("react");
1438
+ var import_react7 = require("react");
1430
1439
  var import_stripe_js = require("@stripe/stripe-js");
1431
1440
 
1432
1441
  // src/hooks/useSolvaPay.ts
@@ -1441,6 +1450,18 @@ function useSolvaPay() {
1441
1450
  return context;
1442
1451
  }
1443
1452
 
1453
+ // src/components/PlanSelectionContext.tsx
1454
+ var import_react6 = require("react");
1455
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1456
+ var PlanSelectionContext = (0, import_react6.createContext)(null);
1457
+ var PlanSelectionProvider = ({
1458
+ value,
1459
+ children
1460
+ }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PlanSelectionContext.Provider, { value, children });
1461
+ function usePlanSelection() {
1462
+ return (0, import_react6.useContext)(PlanSelectionContext);
1463
+ }
1464
+
1444
1465
  // src/hooks/useCheckout.ts
1445
1466
  var stripePromiseCache = /* @__PURE__ */ new Map();
1446
1467
  function getStripeCacheKey(publishableKey, accountId) {
@@ -1453,7 +1474,11 @@ async function resolvePlanRef(productRef, config) {
1453
1474
  const { headers } = await buildRequestHeaders(config);
1454
1475
  const res = await fetchFn(url, { method: "GET", headers });
1455
1476
  if (!res.ok) {
1456
- throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
1477
+ const message = await readErrorMessage(
1478
+ res,
1479
+ `Failed to fetch plans for product "${productRef}"`
1480
+ );
1481
+ throw new Error(message);
1457
1482
  }
1458
1483
  const data = await res.json();
1459
1484
  const allPlans = data.plans ?? [];
@@ -1475,15 +1500,16 @@ async function resolvePlanRef(productRef, config) {
1475
1500
  );
1476
1501
  }
1477
1502
  function useCheckout(options) {
1478
- const { planRef, productRef, customer } = options;
1503
+ const { planRef, productRef, currency: currencyOverride, customer } = options;
1504
+ const planSelection = usePlanSelection();
1479
1505
  const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
1480
- const [loading, setLoading] = (0, import_react6.useState)(false);
1481
- const [error, setError] = (0, import_react6.useState)(null);
1482
- const [stripePromise, setStripePromise] = (0, import_react6.useState)(null);
1483
- const [clientSecret, setClientSecret] = (0, import_react6.useState)(null);
1484
- const [resolvedPlanRef, setResolvedPlanRef] = (0, import_react6.useState)(planRef || null);
1485
- const isStartingRef = (0, import_react6.useRef)(false);
1486
- const startCheckout = (0, import_react6.useCallback)(async () => {
1506
+ const [loading, setLoading] = (0, import_react7.useState)(false);
1507
+ const [error, setError] = (0, import_react7.useState)(null);
1508
+ const [stripePromise, setStripePromise] = (0, import_react7.useState)(null);
1509
+ const [clientSecret, setClientSecret] = (0, import_react7.useState)(null);
1510
+ const [resolvedPlanRef, setResolvedPlanRef] = (0, import_react7.useState)(planRef || null);
1511
+ const isStartingRef = (0, import_react7.useRef)(false);
1512
+ const startCheckout = (0, import_react7.useCallback)(async () => {
1487
1513
  if (isStartingRef.current || loading) {
1488
1514
  return;
1489
1515
  }
@@ -1507,9 +1533,11 @@ function useCheckout(options) {
1507
1533
  if (!effectivePlanRef) {
1508
1534
  throw new Error("Could not determine plan reference for checkout");
1509
1535
  }
1536
+ const selectedCurrency = currencyOverride ?? planSelection?.selectedCurrency ?? void 0;
1510
1537
  const result = await createPayment({
1511
1538
  planRef: effectivePlanRef,
1512
1539
  productRef,
1540
+ ...selectedCurrency && { currency: selectedCurrency },
1513
1541
  customer
1514
1542
  });
1515
1543
  if (!result || typeof result !== "object") {
@@ -1543,8 +1571,18 @@ function useCheckout(options) {
1543
1571
  setLoading(false);
1544
1572
  isStartingRef.current = false;
1545
1573
  }
1546
- }, [planRef, productRef, customer, createPayment, updateCustomerRef, loading, _config]);
1547
- const reset = (0, import_react6.useCallback)(() => {
1574
+ }, [
1575
+ planRef,
1576
+ productRef,
1577
+ currencyOverride,
1578
+ planSelection?.selectedCurrency,
1579
+ customer,
1580
+ createPayment,
1581
+ updateCustomerRef,
1582
+ loading,
1583
+ _config
1584
+ ]);
1585
+ const reset = (0, import_react7.useCallback)(() => {
1548
1586
  isStartingRef.current = false;
1549
1587
  setLoading(false);
1550
1588
  setError(null);
@@ -1584,10 +1622,10 @@ function useCustomer() {
1584
1622
  }
1585
1623
 
1586
1624
  // src/hooks/usePlan.ts
1587
- var import_react8 = require("react");
1625
+ var import_react9 = require("react");
1588
1626
 
1589
1627
  // src/hooks/usePlans.ts
1590
- var import_react7 = require("react");
1628
+ var import_react8 = require("react");
1591
1629
  var plansCache = /* @__PURE__ */ new Map();
1592
1630
  var CACHE_DURATION2 = 5 * 60 * 1e3;
1593
1631
  function processPlans(raw, filter, sortBy) {
@@ -1617,21 +1655,21 @@ function usePlans(options) {
1617
1655
  initialPlanRef,
1618
1656
  selectionReady = true
1619
1657
  } = options;
1620
- const solvaContext = (0, import_react7.useContext)(SolvaPayContext);
1658
+ const solvaContext = (0, import_react8.useContext)(SolvaPayContext);
1621
1659
  const config = solvaContext?._config;
1622
- const effectiveFetcher = (0, import_react7.useMemo)(
1660
+ const effectiveFetcher = (0, import_react8.useMemo)(
1623
1661
  () => fetcher ?? ((ref) => defaultListPlans(ref, config)),
1624
1662
  [fetcher, config]
1625
1663
  );
1626
- const fetcherRef = (0, import_react7.useRef)(effectiveFetcher);
1627
- const filterRef = (0, import_react7.useRef)(filter);
1628
- const sortByRef = (0, import_react7.useRef)(sortBy);
1629
- const autoSelectFirstPaidRef = (0, import_react7.useRef)(autoSelectFirstPaid);
1630
- const initialPlanRefRef = (0, import_react7.useRef)(initialPlanRef);
1631
- const selectionReadyRef = (0, import_react7.useRef)(selectionReady);
1632
- const hasAppliedInitialRef = (0, import_react7.useRef)(false);
1633
- const userHasSelectedRef = (0, import_react7.useRef)(false);
1634
- const [selectedPlanIndex, setSelectedPlanIndexState] = (0, import_react7.useState)(() => {
1664
+ const fetcherRef = (0, import_react8.useRef)(effectiveFetcher);
1665
+ const filterRef = (0, import_react8.useRef)(filter);
1666
+ const sortByRef = (0, import_react8.useRef)(sortBy);
1667
+ const autoSelectFirstPaidRef = (0, import_react8.useRef)(autoSelectFirstPaid);
1668
+ const initialPlanRefRef = (0, import_react8.useRef)(initialPlanRef);
1669
+ const selectionReadyRef = (0, import_react8.useRef)(selectionReady);
1670
+ const hasAppliedInitialRef = (0, import_react8.useRef)(false);
1671
+ const userHasSelectedRef = (0, import_react8.useRef)(false);
1672
+ const [selectedPlanIndex, setSelectedPlanIndexState] = (0, import_react8.useState)(() => {
1635
1673
  if (!selectionReady || !productRef) return 0;
1636
1674
  const cached = plansCache.get(productRef);
1637
1675
  if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
@@ -1643,7 +1681,7 @@ function usePlans(options) {
1643
1681
  hasAppliedInitialRef.current = true;
1644
1682
  return idx;
1645
1683
  });
1646
- const [plans, setPlans] = (0, import_react7.useState)(() => {
1684
+ const [plans, setPlans] = (0, import_react8.useState)(() => {
1647
1685
  if (!productRef) return [];
1648
1686
  const cached = plansCache.get(productRef);
1649
1687
  if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
@@ -1651,31 +1689,31 @@ function usePlans(options) {
1651
1689
  }
1652
1690
  return processPlans(cached.plans, filter, sortBy);
1653
1691
  });
1654
- const [loading, setLoading] = (0, import_react7.useState)(() => plans.length === 0);
1655
- const [error, setError] = (0, import_react7.useState)(null);
1656
- (0, import_react7.useEffect)(() => {
1692
+ const [loading, setLoading] = (0, import_react8.useState)(() => plans.length === 0);
1693
+ const [error, setError] = (0, import_react8.useState)(null);
1694
+ (0, import_react8.useEffect)(() => {
1657
1695
  fetcherRef.current = effectiveFetcher;
1658
1696
  }, [effectiveFetcher]);
1659
- (0, import_react7.useEffect)(() => {
1697
+ (0, import_react8.useEffect)(() => {
1660
1698
  filterRef.current = filter;
1661
1699
  }, [filter]);
1662
- (0, import_react7.useEffect)(() => {
1700
+ (0, import_react8.useEffect)(() => {
1663
1701
  sortByRef.current = sortBy;
1664
1702
  }, [sortBy]);
1665
- (0, import_react7.useEffect)(() => {
1703
+ (0, import_react8.useEffect)(() => {
1666
1704
  autoSelectFirstPaidRef.current = autoSelectFirstPaid;
1667
1705
  }, [autoSelectFirstPaid]);
1668
- (0, import_react7.useEffect)(() => {
1706
+ (0, import_react8.useEffect)(() => {
1669
1707
  initialPlanRefRef.current = initialPlanRef;
1670
1708
  }, [initialPlanRef]);
1671
- (0, import_react7.useEffect)(() => {
1709
+ (0, import_react8.useEffect)(() => {
1672
1710
  selectionReadyRef.current = selectionReady;
1673
1711
  }, [selectionReady]);
1674
- const setSelectedPlanIndex = (0, import_react7.useCallback)((index) => {
1712
+ const setSelectedPlanIndex = (0, import_react8.useCallback)((index) => {
1675
1713
  userHasSelectedRef.current = true;
1676
1714
  setSelectedPlanIndexState(index);
1677
1715
  }, []);
1678
- const applyInitialSelection = (0, import_react7.useCallback)((processedPlans) => {
1716
+ const applyInitialSelection = (0, import_react8.useCallback)((processedPlans) => {
1679
1717
  if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
1680
1718
  if (!selectionReadyRef.current || processedPlans.length === 0) return;
1681
1719
  hasAppliedInitialRef.current = true;
@@ -1686,7 +1724,7 @@ function usePlans(options) {
1686
1724
  );
1687
1725
  setSelectedPlanIndexState(idx);
1688
1726
  }, []);
1689
- const fetchPlans = (0, import_react7.useCallback)(
1727
+ const fetchPlans = (0, import_react8.useCallback)(
1690
1728
  async (force = false) => {
1691
1729
  if (!productRef) {
1692
1730
  setError(new Error("Product reference not configured"));
@@ -1749,16 +1787,16 @@ function usePlans(options) {
1749
1787
  },
1750
1788
  [productRef, applyInitialSelection]
1751
1789
  );
1752
- (0, import_react7.useEffect)(() => {
1790
+ (0, import_react8.useEffect)(() => {
1753
1791
  fetchPlans();
1754
1792
  }, [fetchPlans]);
1755
- (0, import_react7.useEffect)(() => {
1793
+ (0, import_react8.useEffect)(() => {
1756
1794
  if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
1757
1795
  if (!selectionReady || plans.length === 0) return;
1758
1796
  applyInitialSelection(plans);
1759
1797
  }, [selectionReady, plans, applyInitialSelection]);
1760
- const selectedPlan = (0, import_react7.useMemo)(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
1761
- const selectPlan = (0, import_react7.useCallback)(
1798
+ const selectedPlan = (0, import_react8.useMemo)(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
1799
+ const selectPlan = (0, import_react8.useCallback)(
1762
1800
  (planRef) => {
1763
1801
  const index = plans.findIndex((p) => p.reference === planRef);
1764
1802
  if (index >= 0) {
@@ -1792,7 +1830,8 @@ async function defaultListPlans(productRef, config) {
1792
1830
  const { headers } = await buildRequestHeaders(config);
1793
1831
  const res = await fetchFn(url, { method: "GET", headers });
1794
1832
  if (!res.ok) {
1795
- const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
1833
+ const message = await readErrorMessage(res, "Failed to fetch plans");
1834
+ const error = new Error(message);
1796
1835
  config?.onError?.(error, "listPlans");
1797
1836
  throw error;
1798
1837
  }
@@ -1804,22 +1843,22 @@ async function defaultListPlans(productRef, config) {
1804
1843
  function usePlan(options) {
1805
1844
  const { planRef, productRef } = options;
1806
1845
  const { _config } = useSolvaPay();
1807
- const findPlan = (0, import_react8.useCallback)(
1846
+ const findPlan = (0, import_react9.useCallback)(
1808
1847
  (plans) => {
1809
1848
  if (!planRef) return null;
1810
1849
  return plans.find((p) => p.reference === planRef) || null;
1811
1850
  },
1812
1851
  [planRef]
1813
1852
  );
1814
- const [plan, setPlan] = (0, import_react8.useState)(() => {
1853
+ const [plan, setPlan] = (0, import_react9.useState)(() => {
1815
1854
  if (!planRef || !productRef) return null;
1816
1855
  const cached = plansCache.get(productRef);
1817
1856
  if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2) return null;
1818
1857
  return findPlan(cached.plans);
1819
1858
  });
1820
- const [loading, setLoading] = (0, import_react8.useState)(() => !!(planRef && !plan));
1821
- const [error, setError] = (0, import_react8.useState)(null);
1822
- const load = (0, import_react8.useCallback)(
1859
+ const [loading, setLoading] = (0, import_react9.useState)(() => !!(planRef && !plan));
1860
+ const [error, setError] = (0, import_react9.useState)(null);
1861
+ const load = (0, import_react9.useCallback)(
1823
1862
  async (force = false) => {
1824
1863
  if (!planRef) {
1825
1864
  setPlan(null);
@@ -1886,7 +1925,7 @@ function usePlan(options) {
1886
1925
  },
1887
1926
  [planRef, productRef, _config, findPlan]
1888
1927
  );
1889
- (0, import_react8.useEffect)(() => {
1928
+ (0, import_react9.useEffect)(() => {
1890
1929
  load();
1891
1930
  }, [load]);
1892
1931
  return {
@@ -1898,7 +1937,7 @@ function usePlan(options) {
1898
1937
  }
1899
1938
 
1900
1939
  // src/hooks/useProduct.ts
1901
- var import_react9 = require("react");
1940
+ var import_react10 = require("react");
1902
1941
 
1903
1942
  // src/transport/cache-key.ts
1904
1943
  var transportIds = /* @__PURE__ */ new WeakMap();
@@ -1934,16 +1973,16 @@ async function fetchProduct(productRef, config) {
1934
1973
  function useProduct(productRef) {
1935
1974
  const { _config } = useSolvaPay();
1936
1975
  const cacheKey2 = productRef ? cacheKeyFor(_config, productRef) : "";
1937
- const [product, setProduct] = (0, import_react9.useState)(
1976
+ const [product, setProduct] = (0, import_react10.useState)(
1938
1977
  () => productRef ? productCache.get(cacheKey2)?.product ?? null : null
1939
1978
  );
1940
- const [loading, setLoading] = (0, import_react9.useState)(() => {
1979
+ const [loading, setLoading] = (0, import_react10.useState)(() => {
1941
1980
  if (!productRef) return false;
1942
1981
  const cached = productCache.get(cacheKey2);
1943
1982
  return !cached || !cached.product && !cached.promise;
1944
1983
  });
1945
- const [error, setError] = (0, import_react9.useState)(null);
1946
- const load = (0, import_react9.useCallback)(
1984
+ const [error, setError] = (0, import_react10.useState)(null);
1985
+ const load = (0, import_react10.useCallback)(
1947
1986
  async (force = false) => {
1948
1987
  if (!productRef) {
1949
1988
  setProduct(null);
@@ -2004,7 +2043,7 @@ function useProduct(productRef) {
2004
2043
  },
2005
2044
  [productRef, _config]
2006
2045
  );
2007
- (0, import_react9.useEffect)(() => {
2046
+ (0, import_react10.useEffect)(() => {
2008
2047
  load();
2009
2048
  }, [load]);
2010
2049
  return {
@@ -2016,14 +2055,14 @@ function useProduct(productRef) {
2016
2055
  }
2017
2056
 
2018
2057
  // src/hooks/useActivation.ts
2019
- var import_react10 = require("react");
2058
+ var import_react11 = require("react");
2020
2059
  function useActivation() {
2021
2060
  const { activatePlan } = useSolvaPay();
2022
2061
  const copy = useCopy();
2023
- const [state, setState] = (0, import_react10.useState)("idle");
2024
- const [error, setError] = (0, import_react10.useState)(null);
2025
- const [result, setResult] = (0, import_react10.useState)(null);
2026
- const activate = (0, import_react10.useCallback)(
2062
+ const [state, setState] = (0, import_react11.useState)("idle");
2063
+ const [error, setError] = (0, import_react11.useState)(null);
2064
+ const [result, setResult] = (0, import_react11.useState)(null);
2065
+ const activate = (0, import_react11.useCallback)(
2027
2066
  async (params) => {
2028
2067
  setState("activating");
2029
2068
  setError(null);
@@ -2058,7 +2097,7 @@ function useActivation() {
2058
2097
  },
2059
2098
  [activatePlan, copy]
2060
2099
  );
2061
- const reset = (0, import_react10.useCallback)(() => {
2100
+ const reset = (0, import_react11.useCallback)(() => {
2062
2101
  setState("idle");
2063
2102
  setError(null);
2064
2103
  setResult(null);
@@ -2066,18 +2105,6 @@ function useActivation() {
2066
2105
  return { activate, state, error, result, reset };
2067
2106
  }
2068
2107
 
2069
- // src/components/PlanSelectionContext.tsx
2070
- var import_react11 = require("react");
2071
- var import_jsx_runtime5 = require("react/jsx-runtime");
2072
- var PlanSelectionContext = (0, import_react11.createContext)(null);
2073
- var PlanSelectionProvider = ({
2074
- value,
2075
- children
2076
- }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PlanSelectionContext.Provider, { value, children });
2077
- function usePlanSelection() {
2078
- return (0, import_react11.useContext)(PlanSelectionContext);
2079
- }
2080
-
2081
2108
  // src/components/PaymentFormContext.tsx
2082
2109
  var import_react12 = require("react");
2083
2110
  var import_jsx_runtime6 = require("react/jsx-runtime");
@@ -2126,7 +2153,7 @@ function toMajorUnits(amountMinor, currency) {
2126
2153
  return fractionDigits === 0 ? amountMinor : amountMinor / 100;
2127
2154
  }
2128
2155
  function formatPrice(amountMinor, currency, opts = {}) {
2129
- const { locale, interval, intervalCount = 1, free = "Free" } = opts;
2156
+ const { locale, interval, intervalCount = 1, free = "Free", currencyDisplay } = opts;
2130
2157
  if (amountMinor === 0 && free !== "") {
2131
2158
  return free;
2132
2159
  }
@@ -2135,12 +2162,16 @@ function formatPrice(amountMinor, currency, opts = {}) {
2135
2162
  const isWhole = amountMinor % minorPerMajor === 0;
2136
2163
  const fractionDigits = isWhole ? 0 : naturalFractionDigits;
2137
2164
  const major = toMajorUnits(amountMinor, currency);
2138
- const formatter = new Intl.NumberFormat(locale, {
2165
+ const formatterOptions = {
2139
2166
  style: "currency",
2140
2167
  currency: currency.toUpperCase(),
2141
2168
  minimumFractionDigits: fractionDigits,
2142
2169
  maximumFractionDigits: fractionDigits
2143
- });
2170
+ };
2171
+ if (currencyDisplay) {
2172
+ formatterOptions.currencyDisplay = currencyDisplay;
2173
+ }
2174
+ const formatter = new Intl.NumberFormat(locale, formatterOptions);
2144
2175
  const formatted = formatter.format(major);
2145
2176
  if (!interval) return formatted;
2146
2177
  const suffix = intervalCount > 1 ? `${intervalCount} ${interval}s` : interval;
@@ -4376,6 +4407,28 @@ function isPaygPlan(plan) {
4376
4407
  return type === "usage-based" || type === "hybrid";
4377
4408
  }
4378
4409
 
4410
+ // src/utils/planPricing.ts
4411
+ function getPlanPricingOptions(plan) {
4412
+ if (plan.pricingOptions && plan.pricingOptions.length > 0) {
4413
+ return plan.pricingOptions;
4414
+ }
4415
+ return [
4416
+ {
4417
+ currency: plan.currency ?? "USD",
4418
+ price: plan.price ?? 0,
4419
+ default: true
4420
+ }
4421
+ ];
4422
+ }
4423
+ function resolvePlanPricingOption(plan, currency) {
4424
+ const options = getPlanPricingOptions(plan);
4425
+ if (currency) {
4426
+ const match = options.find((option) => option.currency.toUpperCase() === currency.toUpperCase());
4427
+ if (match) return match;
4428
+ }
4429
+ return options.find((option) => option.default) ?? options[0];
4430
+ }
4431
+
4379
4432
  // src/primitives/PlanSelector.tsx
4380
4433
  var import_jsx_runtime20 = require("react/jsx-runtime");
4381
4434
  var PlanSelectorContext = (0, import_react26.createContext)(null);
@@ -4456,6 +4509,23 @@ var Root6 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
4456
4509
  setSelectedPlanIndex(-1);
4457
4510
  }, [setSelectedPlanIndex]);
4458
4511
  const selectedPlanRef = selectedPlan?.reference ?? null;
4512
+ const [preferredCurrency, setPreferredCurrencyState] = (0, import_react26.useState)(null);
4513
+ const [selectedCurrencies, setSelectedCurrencies] = (0, import_react26.useState)({});
4514
+ const getSelectedOption = (0, import_react26.useCallback)(
4515
+ (plan) => resolvePlanPricingOption(
4516
+ plan,
4517
+ selectedCurrencies[plan.reference] ?? preferredCurrency ?? null
4518
+ ),
4519
+ [selectedCurrencies, preferredCurrency]
4520
+ );
4521
+ const setPreferredCurrency = (0, import_react26.useCallback)((currency) => {
4522
+ setPreferredCurrencyState(currency.toUpperCase());
4523
+ setSelectedCurrencies({});
4524
+ }, []);
4525
+ const setPlanCurrency = (0, import_react26.useCallback)((planRef, currency) => {
4526
+ setSelectedCurrencies((current) => ({ ...current, [planRef]: currency }));
4527
+ }, []);
4528
+ const selectedCurrency = selectedPlan ? getSelectedOption(selectedPlan).currency : null;
4459
4529
  const autoCurrentAppliedKeyRef = (0, import_react26.useRef)(null);
4460
4530
  (0, import_react26.useEffect)(() => {
4461
4531
  if (autoCurrentAppliedKeyRef.current === productRef) return;
@@ -4485,7 +4555,12 @@ var Root6 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
4485
4555
  isFree,
4486
4556
  isPopular,
4487
4557
  select,
4488
- clearSelection
4558
+ clearSelection,
4559
+ preferredCurrency,
4560
+ setPreferredCurrency,
4561
+ selectedCurrencies,
4562
+ setPlanCurrency,
4563
+ getSelectedOption
4489
4564
  }),
4490
4565
  [
4491
4566
  plans,
@@ -4499,7 +4574,12 @@ var Root6 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
4499
4574
  isFree,
4500
4575
  isPopular,
4501
4576
  select,
4502
- clearSelection
4577
+ clearSelection,
4578
+ preferredCurrency,
4579
+ setPreferredCurrency,
4580
+ selectedCurrencies,
4581
+ setPlanCurrency,
4582
+ getSelectedOption
4503
4583
  ]
4504
4584
  );
4505
4585
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -4511,6 +4591,12 @@ var Root6 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
4511
4591
  setSelectedPlanRef: (ref) => {
4512
4592
  if (ref) select(ref);
4513
4593
  },
4594
+ selectedCurrency,
4595
+ setSelectedCurrency: (currency) => {
4596
+ if (currency) {
4597
+ setPreferredCurrency(currency);
4598
+ }
4599
+ },
4514
4600
  plans,
4515
4601
  loading,
4516
4602
  error
@@ -4535,14 +4621,19 @@ var Grid = (0, import_react26.forwardRef)(function PlanSelectorGrid({ children,
4535
4621
  const isPaygCurrent = isCurrent && isPaygPlan(plan);
4536
4622
  const disabled = isFree || isCurrent && !isPaygCurrent;
4537
4623
  const state = isCurrent && !isPaygCurrent ? "current" : selected ? "selected" : isCurrent ? "current" : isFree ? "disabled" : "idle";
4624
+ const pricingOptions = getPlanPricingOptions(plan);
4625
+ const selectedOption = ctx.getSelectedOption(plan);
4538
4626
  const cardCtx = {
4539
4627
  plan,
4628
+ selectedOption,
4629
+ pricingOptions,
4540
4630
  state,
4541
4631
  isCurrent,
4542
4632
  isFree,
4543
4633
  isPopular,
4544
4634
  disabled,
4545
- select: () => ctx.select(plan.reference)
4635
+ select: () => ctx.select(plan.reference),
4636
+ setCurrency: (currency) => ctx.setPlanCurrency(plan.reference, currency)
4546
4637
  };
4547
4638
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(CardContext.Provider, { value: cardCtx, children }, plan.reference);
4548
4639
  }) });
@@ -4594,26 +4685,20 @@ var CardPrice = (0, import_react26.forwardRef)(function PlanSelectorCardPrice({
4594
4685
  const formatted = (0, import_react26.useMemo)(() => {
4595
4686
  if (card.isFree) return copy.planSelector.freeBadge;
4596
4687
  if (card.plan.type === "usage-based") {
4597
- const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
4598
- const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
4599
- const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
4600
- locale,
4601
- free: ""
4602
- });
4603
- return `${rate} / call`;
4688
+ return copy.planSelector.usageRateLabel;
4604
4689
  }
4605
- return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
4690
+ return formatPrice(card.selectedOption.price ?? 0, card.selectedOption.currency ?? "usd", {
4606
4691
  locale,
4607
4692
  free: copy.interval.free
4608
4693
  });
4609
4694
  }, [
4610
4695
  card.isFree,
4611
4696
  card.plan.type,
4612
- card.plan.price,
4613
- card.plan.currency,
4614
- card.plan.creditsPerUnit,
4697
+ card.selectedOption.price,
4698
+ card.selectedOption.currency,
4615
4699
  locale,
4616
4700
  copy.planSelector.freeBadge,
4701
+ copy.planSelector.usageRateLabel,
4617
4702
  copy.interval.free
4618
4703
  ]);
4619
4704
  const Comp = asChild ? Slot : "span";
@@ -4638,6 +4723,57 @@ function normalizeBillingCycle(cycle) {
4638
4723
  if (lc === "daily" || lc === "day") return "day";
4639
4724
  return cycle;
4640
4725
  }
4726
+ var CurrencySwitcher = (0, import_react26.forwardRef)(function PlanSelectorCurrencySwitcher({ children, onChange, className, ...rest }, forwardedRef) {
4727
+ const ctx = usePlanSelectorContext("CurrencySwitcher");
4728
+ const availableCurrencies = (0, import_react26.useMemo)(() => {
4729
+ const currencies = /* @__PURE__ */ new Set();
4730
+ for (const plan of ctx.plans) {
4731
+ const options = getPlanPricingOptions(plan);
4732
+ if (options.length <= 1) continue;
4733
+ for (const option of options) {
4734
+ currencies.add(option.currency.toUpperCase());
4735
+ }
4736
+ }
4737
+ return [...currencies].sort();
4738
+ }, [ctx.plans]);
4739
+ if (availableCurrencies.length < 2) return null;
4740
+ const effectiveCurrency = ctx.preferredCurrency ?? (ctx.selectedPlan ? ctx.getSelectedOption(ctx.selectedPlan).currency.toUpperCase() : null) ?? availableCurrencies[0];
4741
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4742
+ "select",
4743
+ {
4744
+ ref: forwardedRef,
4745
+ "data-solvapay-plan-selector-currency-switcher": "",
4746
+ className,
4747
+ value: effectiveCurrency,
4748
+ onChange: (event) => {
4749
+ ctx.setPreferredCurrency(event.target.value);
4750
+ onChange?.(event);
4751
+ },
4752
+ ...rest,
4753
+ children: children ?? availableCurrencies.map((currency) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: currency, children: currency }, currency))
4754
+ }
4755
+ );
4756
+ });
4757
+ var CardCurrency = (0, import_react26.forwardRef)(
4758
+ function PlanSelectorCardCurrency({ children, onChange, ...rest }, forwardedRef) {
4759
+ const card = useCardContext("CardCurrency");
4760
+ if (card.pricingOptions.length <= 1) return null;
4761
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4762
+ "select",
4763
+ {
4764
+ ref: forwardedRef,
4765
+ "data-solvapay-plan-selector-card-currency": "",
4766
+ value: card.selectedOption.currency,
4767
+ onChange: (event) => {
4768
+ card.setCurrency(event.target.value);
4769
+ onChange?.(event);
4770
+ },
4771
+ ...rest,
4772
+ children: children ?? card.pricingOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: option.currency, children: option.currency }, option.currency))
4773
+ }
4774
+ );
4775
+ }
4776
+ );
4641
4777
  var CardBadge = (0, import_react26.forwardRef)(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
4642
4778
  const card = useCardContext("CardBadge");
4643
4779
  const copy = useCopy();
@@ -4683,6 +4819,8 @@ var PlanSelector = {
4683
4819
  CardName,
4684
4820
  CardPrice,
4685
4821
  CardInterval,
4822
+ CurrencySwitcher,
4823
+ CardCurrency,
4686
4824
  CardBadge,
4687
4825
  Loading: Loading4,
4688
4826
  Error: ErrorSlot4
@@ -4695,6 +4833,7 @@ function usePlanSelector() {
4695
4833
  var import_jsx_runtime21 = require("react/jsx-runtime");
4696
4834
  var DefaultTree2 = () => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
4697
4835
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlanSelector.Heading, { className: "solvapay-plan-selector-heading" }),
4836
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlanSelector.CurrencySwitcher, { className: "solvapay-plan-selector-currency-switcher" }),
4698
4837
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
4699
4838
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
4700
4839
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
@@ -6921,21 +7060,21 @@ function buildDefaultCheckoutPlanFilter(allPlans) {
6921
7060
  return true;
6922
7061
  };
6923
7062
  }
6924
- function formatContinueLabel(plan, locale) {
7063
+ function formatContinueLabel(plan, locale, pricingOption) {
6925
7064
  if (!plan) return "Continue";
6926
7065
  if (isPayg(plan)) {
6927
7066
  return `Continue with ${plan.name ?? "Pay as you go"}`;
6928
7067
  }
6929
- const currency = (plan.currency ?? "USD").toUpperCase();
6930
- const priceLabel = formatPrice(plan.price ?? 0, currency, { locale });
7068
+ const option = pricingOption ?? resolvePlanPricingOption(plan, null);
7069
+ const currency = option.currency.toUpperCase();
7070
+ const priceLabel = formatPrice(option.price ?? 0, currency, { locale });
6931
7071
  const cycle = plan.billingCycle ? `/${shortCycle(plan.billingCycle)}` : "";
6932
7072
  return `Continue with ${plan.name ?? "Plan"} \u2014 ${priceLabel}${cycle}`;
6933
7073
  }
6934
7074
  function formatPaygRate(plan, locale) {
6935
- const currency = (plan.currency ?? "USD").toUpperCase();
6936
7075
  const creditsPerUnit = plan.creditsPerUnit ?? 1;
6937
- const perCreditMinor = Math.max(1, Math.round(1 / creditsPerUnit));
6938
- return `${formatPrice(perCreditMinor, currency, { locale })} / call`;
7076
+ const creditLabel = creditsPerUnit === 1 ? "credit" : "credits";
7077
+ return `${creditsPerUnit.toLocaleString(locale)} ${creditLabel} / call`;
6939
7078
  }
6940
7079
  function inferIncludedCredits(plan) {
6941
7080
  const price = plan.price ?? 0;
@@ -6970,14 +7109,31 @@ function useCheckoutFlow(opts) {
6970
7109
  onPurchaseSuccessRef.current = opts.onPurchaseSuccess;
6971
7110
  onErrorRef.current = opts.onError;
6972
7111
  const planCtx = usePlanSelector();
7112
+ const planSelection = usePlanSelection();
6973
7113
  const transport = useTransport();
6974
7114
  const locale = useLocale();
6975
7115
  const balance = useBalance();
6976
7116
  const { creditsPerMinorUnit, displayExchangeRate, adjustBalance } = balance;
6977
7117
  const { refetchPurchase } = useSolvaPay();
6978
7118
  const { merchant } = useMerchant();
6979
- const topupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
7119
+ const [topupCurrencyOverride, setTopupCurrencyOverride] = (0, import_react44.useState)(null);
7120
+ const topupCurrencies = (0, import_react44.useMemo)(() => {
7121
+ const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
7122
+ const fallback = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
7123
+ const list = fromMerchant.length > 0 ? fromMerchant : fallback ? [fallback] : [];
7124
+ return Array.from(new Set(list));
7125
+ }, [merchant?.supportedTopupCurrencies, merchant?.defaultCurrency, opts.topupCurrency]);
7126
+ const resolvedDefaultTopupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
7127
+ const planPickerCurrency = planCtx.preferredCurrency?.toUpperCase() ?? null;
7128
+ const topupCurrency = (topupCurrencyOverride && topupCurrencies.includes(topupCurrencyOverride) ? topupCurrencyOverride : null) ?? (planPickerCurrency && topupCurrencies.includes(planPickerCurrency) ? planPickerCurrency : null) ?? resolvedDefaultTopupCurrency;
6980
7129
  const topupCurrencyReady = topupCurrency != null;
7130
+ const setTopupCurrency = (0, import_react44.useCallback)(
7131
+ (code) => {
7132
+ const normalized = code.toUpperCase();
7133
+ setTopupCurrencyOverride(normalized);
7134
+ },
7135
+ []
7136
+ );
6981
7137
  const [step, setStep] = (0, import_react44.useState)(initialStep);
6982
7138
  const [status, setStatus] = (0, import_react44.useState)("idle");
6983
7139
  const [selectedAmountMinor, setSelectedAmountMinor] = (0, import_react44.useState)(initialAmountMinor);
@@ -7075,19 +7231,23 @@ function useCheckoutFlow(opts) {
7075
7231
  );
7076
7232
  const recordRecurringSuccess = (0, import_react44.useCallback)(() => {
7077
7233
  if (!selectedPlanShape) return;
7078
- const currency = (selectedPlanShape.currency ?? "USD").toUpperCase();
7234
+ const pricingOption = selectedPlan ? resolvePlanPricingOption(selectedPlan, planSelection?.selectedCurrency) : {
7235
+ currency: selectedPlanShape.currency ?? "USD",
7236
+ price: selectedPlanShape.price ?? 0
7237
+ };
7238
+ const currency = pricingOption.currency.toUpperCase();
7079
7239
  const meta = {
7080
7240
  branch: "recurring",
7081
7241
  plan: selectedPlanShape,
7082
7242
  creditsIncluded: inferIncludedCredits(selectedPlanShape),
7083
- chargedTodayMinor: selectedPlanShape.price ?? 0,
7243
+ chargedTodayMinor: pricingOption.price ?? 0,
7084
7244
  currency,
7085
7245
  nextRenewalLabel: null
7086
7246
  };
7087
7247
  setSuccessMeta(meta);
7088
7248
  setStep("success");
7089
7249
  onPurchaseSuccessRef.current?.(meta);
7090
- }, [selectedPlanShape]);
7250
+ }, [selectedPlanShape, selectedPlan, planSelection?.selectedCurrency]);
7091
7251
  const advance = (0, import_react44.useCallback)(async () => {
7092
7252
  if (step === "plan") {
7093
7253
  await advanceFromPlan();
@@ -7133,6 +7293,7 @@ function useCheckoutFlow(opts) {
7133
7293
  setSuccessMeta(null);
7134
7294
  setError(null);
7135
7295
  setStatus("idle");
7296
+ setTopupCurrencyOverride(null);
7136
7297
  }, []);
7137
7298
  const retry = (0, import_react44.useCallback)(async () => {
7138
7299
  if (status !== "error") return;
@@ -7162,6 +7323,8 @@ function useCheckoutFlow(opts) {
7162
7323
  branch,
7163
7324
  topupCurrency,
7164
7325
  topupCurrencyReady,
7326
+ topupCurrencies,
7327
+ setTopupCurrency,
7165
7328
  canGoBack,
7166
7329
  selectPlan,
7167
7330
  selectAmount,
@@ -7182,6 +7345,8 @@ function useCheckoutFlow(opts) {
7182
7345
  branch,
7183
7346
  topupCurrency,
7184
7347
  topupCurrencyReady,
7348
+ topupCurrencies,
7349
+ setTopupCurrency,
7185
7350
  canGoBack,
7186
7351
  selectPlan,
7187
7352
  selectAmount,
@@ -8029,6 +8194,28 @@ var CheckoutSteps = {
8029
8194
  Success
8030
8195
  };
8031
8196
 
8197
+ // src/adapters/session-auth.ts
8198
+ var DEFAULT_SENTINEL = "session-authenticated";
8199
+ async function resolveValue(value) {
8200
+ return value;
8201
+ }
8202
+ function createSessionAuthAdapter(options) {
8203
+ const sentinel = options.sentinelToken ?? DEFAULT_SENTINEL;
8204
+ return {
8205
+ async getToken() {
8206
+ if (options.getToken) {
8207
+ return resolveValue(options.getToken());
8208
+ }
8209
+ const userId = await resolveValue(options.getUserId());
8210
+ return userId ? sentinel : null;
8211
+ },
8212
+ async getUserId() {
8213
+ return resolveValue(options.getUserId());
8214
+ },
8215
+ subscribe: options.subscribe
8216
+ };
8217
+ }
8218
+
8032
8219
  // src/transport/types.ts
8033
8220
  var UnsupportedTransportMethodError = class extends Error {
8034
8221
  method;
@@ -8071,6 +8258,7 @@ var UnsupportedTransportMethodError = class extends Error {
8071
8258
  confirmPayment,
8072
8259
  createAnonymousAuthAdapter,
8073
8260
  createHttpTransport,
8261
+ createSessionAuthAdapter,
8074
8262
  defaultAuthAdapter,
8075
8263
  deriveVariant,
8076
8264
  enCopy,