@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.
@@ -105,6 +105,17 @@ async function buildRequestHeaders(config) {
105
105
  return { headers, userId };
106
106
  }
107
107
 
108
+ // src/utils/readErrorMessage.ts
109
+ async function readErrorMessage(res, fallbackPrefix) {
110
+ let serverMessage;
111
+ try {
112
+ const data = await res.clone().json();
113
+ serverMessage = data?.error;
114
+ } catch {
115
+ }
116
+ return serverMessage || `${fallbackPrefix}: ${res.statusText || res.status}`;
117
+ }
118
+
108
119
  // src/transport/http.ts
109
120
  async function request(config, url, opts) {
110
121
  const { headers } = await buildRequestHeaders(config);
@@ -115,13 +126,8 @@ async function request(config, url, opts) {
115
126
  }
116
127
  const res = await fetchFn(url, init);
117
128
  if (!res.ok) {
118
- let serverMessage;
119
- try {
120
- const data = await res.clone().json();
121
- serverMessage = data?.error;
122
- } catch {
123
- }
124
- const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
129
+ const message = await readErrorMessage(res, opts.errorPrefix);
130
+ const error = new Error(message);
125
131
  config?.onError?.(error, opts.onErrorContext);
126
132
  throw error;
127
133
  }
@@ -161,6 +167,7 @@ function createHttpTransport(config) {
161
167
  const body = {};
162
168
  if (params.planRef) body.planRef = params.planRef;
163
169
  if (params.productRef) body.productRef = params.productRef;
170
+ if (params.currency) body.currency = params.currency;
164
171
  if (params.customer && (params.customer.name || params.customer.email)) {
165
172
  body.customer = params.customer;
166
173
  }
@@ -390,6 +397,7 @@ var enCopy = {
390
397
  currentBadge: "Current",
391
398
  popularBadge: "Popular",
392
399
  freeBadge: "Free",
400
+ usageRateLabel: "Usage-based",
393
401
  perIntervalShort: "/{interval}",
394
402
  continueButton: "Continue",
395
403
  backButton: "\u2190 Back to plans",
@@ -1226,6 +1234,20 @@ function useSolvaPay() {
1226
1234
  // src/hooks/useCheckout.ts
1227
1235
  import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
1228
1236
  import { loadStripe } from "@stripe/stripe-js";
1237
+
1238
+ // src/components/PlanSelectionContext.tsx
1239
+ import { createContext as createContext3, useContext as useContext3 } from "react";
1240
+ import { jsx as jsx5 } from "react/jsx-runtime";
1241
+ var PlanSelectionContext = createContext3(null);
1242
+ var PlanSelectionProvider = ({
1243
+ value,
1244
+ children
1245
+ }) => /* @__PURE__ */ jsx5(PlanSelectionContext.Provider, { value, children });
1246
+ function usePlanSelection() {
1247
+ return useContext3(PlanSelectionContext);
1248
+ }
1249
+
1250
+ // src/hooks/useCheckout.ts
1229
1251
  var stripePromiseCache = /* @__PURE__ */ new Map();
1230
1252
  function getStripeCacheKey(publishableKey, accountId) {
1231
1253
  return accountId ? `${publishableKey}:${accountId}` : publishableKey;
@@ -1237,7 +1259,11 @@ async function resolvePlanRef(productRef, config) {
1237
1259
  const { headers } = await buildRequestHeaders(config);
1238
1260
  const res = await fetchFn(url, { method: "GET", headers });
1239
1261
  if (!res.ok) {
1240
- throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
1262
+ const message = await readErrorMessage(
1263
+ res,
1264
+ `Failed to fetch plans for product "${productRef}"`
1265
+ );
1266
+ throw new Error(message);
1241
1267
  }
1242
1268
  const data = await res.json();
1243
1269
  const allPlans = data.plans ?? [];
@@ -1259,7 +1285,8 @@ async function resolvePlanRef(productRef, config) {
1259
1285
  );
1260
1286
  }
1261
1287
  function useCheckout(options) {
1262
- const { planRef, productRef, customer } = options;
1288
+ const { planRef, productRef, currency: currencyOverride, customer } = options;
1289
+ const planSelection = usePlanSelection();
1263
1290
  const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
1264
1291
  const [loading, setLoading] = useState2(false);
1265
1292
  const [error, setError] = useState2(null);
@@ -1291,9 +1318,11 @@ function useCheckout(options) {
1291
1318
  if (!effectivePlanRef) {
1292
1319
  throw new Error("Could not determine plan reference for checkout");
1293
1320
  }
1321
+ const selectedCurrency = currencyOverride ?? planSelection?.selectedCurrency ?? void 0;
1294
1322
  const result = await createPayment({
1295
1323
  planRef: effectivePlanRef,
1296
1324
  productRef,
1325
+ ...selectedCurrency && { currency: selectedCurrency },
1297
1326
  customer
1298
1327
  });
1299
1328
  if (!result || typeof result !== "object") {
@@ -1327,7 +1356,17 @@ function useCheckout(options) {
1327
1356
  setLoading(false);
1328
1357
  isStartingRef.current = false;
1329
1358
  }
1330
- }, [planRef, productRef, customer, createPayment, updateCustomerRef, loading, _config]);
1359
+ }, [
1360
+ planRef,
1361
+ productRef,
1362
+ currencyOverride,
1363
+ planSelection?.selectedCurrency,
1364
+ customer,
1365
+ createPayment,
1366
+ updateCustomerRef,
1367
+ loading,
1368
+ _config
1369
+ ]);
1331
1370
  const reset = useCallback2(() => {
1332
1371
  isStartingRef.current = false;
1333
1372
  setLoading(false);
@@ -1368,7 +1407,7 @@ function useCustomer() {
1368
1407
  }
1369
1408
 
1370
1409
  // src/hooks/usePlans.ts
1371
- import { useContext as useContext3, useState as useState3, useEffect as useEffect2, useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
1410
+ import { useContext as useContext4, useState as useState3, useEffect as useEffect2, useCallback as useCallback3, useMemo as useMemo3, useRef as useRef3 } from "react";
1372
1411
 
1373
1412
  // src/transport/list-plans.ts
1374
1413
  async function defaultListPlans(productRef, config) {
@@ -1382,7 +1421,8 @@ async function defaultListPlans(productRef, config) {
1382
1421
  const { headers } = await buildRequestHeaders(config);
1383
1422
  const res = await fetchFn(url, { method: "GET", headers });
1384
1423
  if (!res.ok) {
1385
- const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
1424
+ const message = await readErrorMessage(res, "Failed to fetch plans");
1425
+ const error = new Error(message);
1386
1426
  config?.onError?.(error, "listPlans");
1387
1427
  throw error;
1388
1428
  }
@@ -1420,7 +1460,7 @@ function usePlans(options) {
1420
1460
  initialPlanRef,
1421
1461
  selectionReady = true
1422
1462
  } = options;
1423
- const solvaContext = useContext3(SolvaPayContext);
1463
+ const solvaContext = useContext4(SolvaPayContext);
1424
1464
  const config = solvaContext?._config;
1425
1465
  const effectiveFetcher = useMemo3(
1426
1466
  () => fetcher ?? ((ref) => defaultListPlans(ref, config)),
@@ -1851,11 +1891,11 @@ function useActivation() {
1851
1891
  }
1852
1892
 
1853
1893
  // src/components/PaymentFormContext.tsx
1854
- import { createContext as createContext3, useContext as useContext4 } from "react";
1855
- import { jsx as jsx5 } from "react/jsx-runtime";
1856
- var PaymentFormContext = createContext3(null);
1894
+ import { createContext as createContext4, useContext as useContext5 } from "react";
1895
+ import { jsx as jsx6 } from "react/jsx-runtime";
1896
+ var PaymentFormContext = createContext4(null);
1857
1897
  function usePaymentForm() {
1858
- const ctx = useContext4(PaymentFormContext);
1898
+ const ctx = useContext5(PaymentFormContext);
1859
1899
  if (!ctx) {
1860
1900
  throw new Error(
1861
1901
  "PaymentForm subcomponents must be used inside a <PaymentForm>. Wrap the slots with <PaymentForm planRef=... productRef=...>."
@@ -1863,7 +1903,7 @@ function usePaymentForm() {
1863
1903
  }
1864
1904
  return ctx;
1865
1905
  }
1866
- var PaymentFormProvider = ({ value, children }) => /* @__PURE__ */ jsx5(PaymentFormContext.Provider, { value, children });
1906
+ var PaymentFormProvider = ({ value, children }) => /* @__PURE__ */ jsx6(PaymentFormContext.Provider, { value, children });
1867
1907
 
1868
1908
  // src/utils/format.ts
1869
1909
  var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
@@ -1895,7 +1935,7 @@ function toMajorUnits(amountMinor, currency) {
1895
1935
  return fractionDigits === 0 ? amountMinor : amountMinor / 100;
1896
1936
  }
1897
1937
  function formatPrice(amountMinor, currency, opts = {}) {
1898
- const { locale, interval, intervalCount = 1, free = "Free" } = opts;
1938
+ const { locale, interval, intervalCount = 1, free = "Free", currencyDisplay } = opts;
1899
1939
  if (amountMinor === 0 && free !== "") {
1900
1940
  return free;
1901
1941
  }
@@ -1904,12 +1944,16 @@ function formatPrice(amountMinor, currency, opts = {}) {
1904
1944
  const isWhole = amountMinor % minorPerMajor === 0;
1905
1945
  const fractionDigits = isWhole ? 0 : naturalFractionDigits;
1906
1946
  const major = toMajorUnits(amountMinor, currency);
1907
- const formatter = new Intl.NumberFormat(locale, {
1947
+ const formatterOptions = {
1908
1948
  style: "currency",
1909
1949
  currency: currency.toUpperCase(),
1910
1950
  minimumFractionDigits: fractionDigits,
1911
1951
  maximumFractionDigits: fractionDigits
1912
- });
1952
+ };
1953
+ if (currencyDisplay) {
1954
+ formatterOptions.currencyDisplay = currencyDisplay;
1955
+ }
1956
+ const formatter = new Intl.NumberFormat(locale, formatterOptions);
1913
1957
  const formatted = formatter.format(major);
1914
1958
  if (!interval) return formatted;
1915
1959
  const suffix = intervalCount > 1 ? `${intervalCount} ${interval}s` : interval;
@@ -1928,18 +1972,6 @@ function interpolate(template, vars) {
1928
1972
  // src/primitives/CheckoutSummary.tsx
1929
1973
  import { createContext as createContext5, forwardRef as forwardRef2, useContext as useContext6, useMemo as useMemo4 } from "react";
1930
1974
 
1931
- // src/components/PlanSelectionContext.tsx
1932
- import { createContext as createContext4, useContext as useContext5 } from "react";
1933
- import { jsx as jsx6 } from "react/jsx-runtime";
1934
- var PlanSelectionContext = createContext4(null);
1935
- var PlanSelectionProvider = ({
1936
- value,
1937
- children
1938
- }) => /* @__PURE__ */ jsx6(PlanSelectionContext.Provider, { value, children });
1939
- function usePlanSelection() {
1940
- return useContext5(PlanSelectionContext);
1941
- }
1942
-
1943
1975
  // src/utils/errors.ts
1944
1976
  var DOCS_BASE_URL = "https://solvapay.com/docs";
1945
1977
  var SolvaPayError = class extends Error {
@@ -3949,7 +3981,8 @@ import {
3949
3981
  useContext as useContext12,
3950
3982
  useEffect as useEffect10,
3951
3983
  useMemo as useMemo10,
3952
- useRef as useRef7
3984
+ useRef as useRef7,
3985
+ useState as useState12
3953
3986
  } from "react";
3954
3987
 
3955
3988
  // src/utils/isPayg.ts
@@ -3959,6 +3992,28 @@ function isPaygPlan(plan) {
3959
3992
  return type === "usage-based" || type === "hybrid";
3960
3993
  }
3961
3994
 
3995
+ // src/utils/planPricing.ts
3996
+ function getPlanPricingOptions(plan) {
3997
+ if (plan.pricingOptions && plan.pricingOptions.length > 0) {
3998
+ return plan.pricingOptions;
3999
+ }
4000
+ return [
4001
+ {
4002
+ currency: plan.currency ?? "USD",
4003
+ price: plan.price ?? 0,
4004
+ default: true
4005
+ }
4006
+ ];
4007
+ }
4008
+ function resolvePlanPricingOption(plan, currency) {
4009
+ const options = getPlanPricingOptions(plan);
4010
+ if (currency) {
4011
+ const match = options.find((option) => option.currency.toUpperCase() === currency.toUpperCase());
4012
+ if (match) return match;
4013
+ }
4014
+ return options.find((option) => option.default) ?? options[0];
4015
+ }
4016
+
3962
4017
  // src/primitives/PlanSelector.tsx
3963
4018
  import { jsx as jsx15 } from "react/jsx-runtime";
3964
4019
  var PlanSelectorContext = createContext8(null);
@@ -4039,6 +4094,23 @@ var Root5 = forwardRef8(function PlanSelectorRoot(props, forwardedRef) {
4039
4094
  setSelectedPlanIndex(-1);
4040
4095
  }, [setSelectedPlanIndex]);
4041
4096
  const selectedPlanRef = selectedPlan?.reference ?? null;
4097
+ const [preferredCurrency, setPreferredCurrencyState] = useState12(null);
4098
+ const [selectedCurrencies, setSelectedCurrencies] = useState12({});
4099
+ const getSelectedOption = useCallback13(
4100
+ (plan) => resolvePlanPricingOption(
4101
+ plan,
4102
+ selectedCurrencies[plan.reference] ?? preferredCurrency ?? null
4103
+ ),
4104
+ [selectedCurrencies, preferredCurrency]
4105
+ );
4106
+ const setPreferredCurrency = useCallback13((currency) => {
4107
+ setPreferredCurrencyState(currency.toUpperCase());
4108
+ setSelectedCurrencies({});
4109
+ }, []);
4110
+ const setPlanCurrency = useCallback13((planRef, currency) => {
4111
+ setSelectedCurrencies((current) => ({ ...current, [planRef]: currency }));
4112
+ }, []);
4113
+ const selectedCurrency = selectedPlan ? getSelectedOption(selectedPlan).currency : null;
4042
4114
  const autoCurrentAppliedKeyRef = useRef7(null);
4043
4115
  useEffect10(() => {
4044
4116
  if (autoCurrentAppliedKeyRef.current === productRef) return;
@@ -4068,7 +4140,12 @@ var Root5 = forwardRef8(function PlanSelectorRoot(props, forwardedRef) {
4068
4140
  isFree,
4069
4141
  isPopular,
4070
4142
  select,
4071
- clearSelection
4143
+ clearSelection,
4144
+ preferredCurrency,
4145
+ setPreferredCurrency,
4146
+ selectedCurrencies,
4147
+ setPlanCurrency,
4148
+ getSelectedOption
4072
4149
  }),
4073
4150
  [
4074
4151
  plans,
@@ -4082,7 +4159,12 @@ var Root5 = forwardRef8(function PlanSelectorRoot(props, forwardedRef) {
4082
4159
  isFree,
4083
4160
  isPopular,
4084
4161
  select,
4085
- clearSelection
4162
+ clearSelection,
4163
+ preferredCurrency,
4164
+ setPreferredCurrency,
4165
+ selectedCurrencies,
4166
+ setPlanCurrency,
4167
+ getSelectedOption
4086
4168
  ]
4087
4169
  );
4088
4170
  return /* @__PURE__ */ jsx15(
@@ -4094,6 +4176,12 @@ var Root5 = forwardRef8(function PlanSelectorRoot(props, forwardedRef) {
4094
4176
  setSelectedPlanRef: (ref) => {
4095
4177
  if (ref) select(ref);
4096
4178
  },
4179
+ selectedCurrency,
4180
+ setSelectedCurrency: (currency) => {
4181
+ if (currency) {
4182
+ setPreferredCurrency(currency);
4183
+ }
4184
+ },
4097
4185
  plans,
4098
4186
  loading,
4099
4187
  error
@@ -4118,14 +4206,19 @@ var Grid = forwardRef8(function PlanSelectorGrid({ children, ...rest }, forwarde
4118
4206
  const isPaygCurrent = isCurrent && isPaygPlan(plan);
4119
4207
  const disabled = isFree || isCurrent && !isPaygCurrent;
4120
4208
  const state = isCurrent && !isPaygCurrent ? "current" : selected ? "selected" : isCurrent ? "current" : isFree ? "disabled" : "idle";
4209
+ const pricingOptions = getPlanPricingOptions(plan);
4210
+ const selectedOption = ctx.getSelectedOption(plan);
4121
4211
  const cardCtx = {
4122
4212
  plan,
4213
+ selectedOption,
4214
+ pricingOptions,
4123
4215
  state,
4124
4216
  isCurrent,
4125
4217
  isFree,
4126
4218
  isPopular,
4127
4219
  disabled,
4128
- select: () => ctx.select(plan.reference)
4220
+ select: () => ctx.select(plan.reference),
4221
+ setCurrency: (currency) => ctx.setPlanCurrency(plan.reference, currency)
4129
4222
  };
4130
4223
  return /* @__PURE__ */ jsx15(CardContext.Provider, { value: cardCtx, children }, plan.reference);
4131
4224
  }) });
@@ -4177,26 +4270,20 @@ var CardPrice = forwardRef8(function PlanSelectorCardPrice({ asChild, children,
4177
4270
  const formatted = useMemo10(() => {
4178
4271
  if (card.isFree) return copy.planSelector.freeBadge;
4179
4272
  if (card.plan.type === "usage-based") {
4180
- const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
4181
- const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
4182
- const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
4183
- locale,
4184
- free: ""
4185
- });
4186
- return `${rate} / call`;
4273
+ return copy.planSelector.usageRateLabel;
4187
4274
  }
4188
- return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
4275
+ return formatPrice(card.selectedOption.price ?? 0, card.selectedOption.currency ?? "usd", {
4189
4276
  locale,
4190
4277
  free: copy.interval.free
4191
4278
  });
4192
4279
  }, [
4193
4280
  card.isFree,
4194
4281
  card.plan.type,
4195
- card.plan.price,
4196
- card.plan.currency,
4197
- card.plan.creditsPerUnit,
4282
+ card.selectedOption.price,
4283
+ card.selectedOption.currency,
4198
4284
  locale,
4199
4285
  copy.planSelector.freeBadge,
4286
+ copy.planSelector.usageRateLabel,
4200
4287
  copy.interval.free
4201
4288
  ]);
4202
4289
  const Comp = asChild ? Slot : "span";
@@ -4221,6 +4308,57 @@ function normalizeBillingCycle(cycle) {
4221
4308
  if (lc === "daily" || lc === "day") return "day";
4222
4309
  return cycle;
4223
4310
  }
4311
+ var CurrencySwitcher = forwardRef8(function PlanSelectorCurrencySwitcher({ children, onChange, className, ...rest }, forwardedRef) {
4312
+ const ctx = usePlanSelectorContext("CurrencySwitcher");
4313
+ const availableCurrencies = useMemo10(() => {
4314
+ const currencies = /* @__PURE__ */ new Set();
4315
+ for (const plan of ctx.plans) {
4316
+ const options = getPlanPricingOptions(plan);
4317
+ if (options.length <= 1) continue;
4318
+ for (const option of options) {
4319
+ currencies.add(option.currency.toUpperCase());
4320
+ }
4321
+ }
4322
+ return [...currencies].sort();
4323
+ }, [ctx.plans]);
4324
+ if (availableCurrencies.length < 2) return null;
4325
+ const effectiveCurrency = ctx.preferredCurrency ?? (ctx.selectedPlan ? ctx.getSelectedOption(ctx.selectedPlan).currency.toUpperCase() : null) ?? availableCurrencies[0];
4326
+ return /* @__PURE__ */ jsx15(
4327
+ "select",
4328
+ {
4329
+ ref: forwardedRef,
4330
+ "data-solvapay-plan-selector-currency-switcher": "",
4331
+ className,
4332
+ value: effectiveCurrency,
4333
+ onChange: (event) => {
4334
+ ctx.setPreferredCurrency(event.target.value);
4335
+ onChange?.(event);
4336
+ },
4337
+ ...rest,
4338
+ children: children ?? availableCurrencies.map((currency) => /* @__PURE__ */ jsx15("option", { value: currency, children: currency }, currency))
4339
+ }
4340
+ );
4341
+ });
4342
+ var CardCurrency = forwardRef8(
4343
+ function PlanSelectorCardCurrency({ children, onChange, ...rest }, forwardedRef) {
4344
+ const card = useCardContext("CardCurrency");
4345
+ if (card.pricingOptions.length <= 1) return null;
4346
+ return /* @__PURE__ */ jsx15(
4347
+ "select",
4348
+ {
4349
+ ref: forwardedRef,
4350
+ "data-solvapay-plan-selector-card-currency": "",
4351
+ value: card.selectedOption.currency,
4352
+ onChange: (event) => {
4353
+ card.setCurrency(event.target.value);
4354
+ onChange?.(event);
4355
+ },
4356
+ ...rest,
4357
+ children: children ?? card.pricingOptions.map((option) => /* @__PURE__ */ jsx15("option", { value: option.currency, children: option.currency }, option.currency))
4358
+ }
4359
+ );
4360
+ }
4361
+ );
4224
4362
  var CardBadge = forwardRef8(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
4225
4363
  const card = useCardContext("CardBadge");
4226
4364
  const copy = useCopy();
@@ -4265,6 +4403,8 @@ var PlanSelectorCard2 = Card;
4265
4403
  var PlanSelectorCardName2 = CardName;
4266
4404
  var PlanSelectorCardPrice2 = CardPrice;
4267
4405
  var PlanSelectorCardInterval2 = CardInterval;
4406
+ var PlanSelectorCurrencySwitcher2 = CurrencySwitcher;
4407
+ var PlanSelectorCardCurrency2 = CardCurrency;
4268
4408
  var PlanSelectorCardBadge2 = CardBadge;
4269
4409
  var PlanSelectorLoading2 = Loading3;
4270
4410
  var PlanSelectorError2 = ErrorSlot3;
@@ -4276,6 +4416,8 @@ var PlanSelector = {
4276
4416
  CardName,
4277
4417
  CardPrice,
4278
4418
  CardInterval,
4419
+ CurrencySwitcher,
4420
+ CardCurrency,
4279
4421
  CardBadge,
4280
4422
  Loading: Loading3,
4281
4423
  Error: ErrorSlot3
@@ -4285,12 +4427,12 @@ function usePlanSelector() {
4285
4427
  }
4286
4428
 
4287
4429
  // src/hooks/usePurchaseActions.ts
4288
- import { useState as useState12, useCallback as useCallback14 } from "react";
4430
+ import { useState as useState13, useCallback as useCallback14 } from "react";
4289
4431
  function usePurchaseActions() {
4290
4432
  const ctx = useSolvaPay();
4291
- const [isCancelling, setIsCancelling] = useState12(false);
4292
- const [isReactivating, setIsReactivating] = useState12(false);
4293
- const [isActivating, setIsActivating] = useState12(false);
4433
+ const [isCancelling, setIsCancelling] = useState13(false);
4434
+ const [isReactivating, setIsReactivating] = useState13(false);
4435
+ const [isActivating, setIsActivating] = useState13(false);
4294
4436
  const cancelRenewal = useCallback14(
4295
4437
  async (params) => {
4296
4438
  setIsCancelling(true);
@@ -4633,7 +4775,7 @@ function useTransport() {
4633
4775
  }
4634
4776
 
4635
4777
  // src/hooks/useUsage.ts
4636
- import { useCallback as useCallback18, useEffect as useEffect11, useMemo as useMemo14, useState as useState13 } from "react";
4778
+ import { useCallback as useCallback18, useEffect as useEffect11, useMemo as useMemo14, useState as useState14 } from "react";
4637
4779
  function deriveUsage(purchase) {
4638
4780
  if (!purchase) return null;
4639
4781
  const snap = purchase.planSnapshot;
@@ -4658,9 +4800,9 @@ function deriveUsage(purchase) {
4658
4800
  function useUsage() {
4659
4801
  const { activePurchase, refetch: refetchPurchase, loading: purchaseLoading } = usePurchase();
4660
4802
  const transport = useTransport();
4661
- const [override, setOverride] = useState13(null);
4662
- const [error, setError] = useState13(null);
4663
- const [transportLoading, setTransportLoading] = useState13(false);
4803
+ const [override, setOverride] = useState14(null);
4804
+ const [error, setError] = useState14(null);
4805
+ const [transportLoading, setTransportLoading] = useState14(false);
4664
4806
  const derived = useMemo14(() => deriveUsage(activePurchase ?? null), [activePurchase]);
4665
4807
  const activePurchaseRef = activePurchase?.reference ?? null;
4666
4808
  useEffect11(() => {
@@ -4913,7 +5055,7 @@ function useUsageMeter() {
4913
5055
  }
4914
5056
 
4915
5057
  // src/hooks/useCheckoutFlow.ts
4916
- import { useCallback as useCallback19, useMemo as useMemo16, useRef as useRef8, useState as useState14 } from "react";
5058
+ import { useCallback as useCallback19, useMemo as useMemo16, useRef as useRef8, useState as useState15 } from "react";
4917
5059
 
4918
5060
  // src/primitives/checkout/shared.ts
4919
5061
  function isPayg(plan) {
@@ -4936,21 +5078,21 @@ function buildDefaultCheckoutPlanFilter(allPlans) {
4936
5078
  return true;
4937
5079
  };
4938
5080
  }
4939
- function formatContinueLabel(plan, locale) {
5081
+ function formatContinueLabel(plan, locale, pricingOption) {
4940
5082
  if (!plan) return "Continue";
4941
5083
  if (isPayg(plan)) {
4942
5084
  return `Continue with ${plan.name ?? "Pay as you go"}`;
4943
5085
  }
4944
- const currency = (plan.currency ?? "USD").toUpperCase();
4945
- const priceLabel = formatPrice(plan.price ?? 0, currency, { locale });
5086
+ const option = pricingOption ?? resolvePlanPricingOption(plan, null);
5087
+ const currency = option.currency.toUpperCase();
5088
+ const priceLabel = formatPrice(option.price ?? 0, currency, { locale });
4946
5089
  const cycle = plan.billingCycle ? `/${shortCycle(plan.billingCycle)}` : "";
4947
5090
  return `Continue with ${plan.name ?? "Plan"} \u2014 ${priceLabel}${cycle}`;
4948
5091
  }
4949
5092
  function formatPaygRate(plan, locale) {
4950
- const currency = (plan.currency ?? "USD").toUpperCase();
4951
5093
  const creditsPerUnit = plan.creditsPerUnit ?? 1;
4952
- const perCreditMinor = Math.max(1, Math.round(1 / creditsPerUnit));
4953
- return `${formatPrice(perCreditMinor, currency, { locale })} / call`;
5094
+ const creditLabel = creditsPerUnit === 1 ? "credit" : "credits";
5095
+ return `${creditsPerUnit.toLocaleString(locale)} ${creditLabel} / call`;
4954
5096
  }
4955
5097
  function inferIncludedCredits(plan) {
4956
5098
  const price = plan.price ?? 0;
@@ -4985,19 +5127,36 @@ function useCheckoutFlow(opts) {
4985
5127
  onPurchaseSuccessRef.current = opts.onPurchaseSuccess;
4986
5128
  onErrorRef.current = opts.onError;
4987
5129
  const planCtx = usePlanSelector();
5130
+ const planSelection = usePlanSelection();
4988
5131
  const transport = useTransport();
4989
5132
  const locale = useLocale();
4990
5133
  const balance = useBalance();
4991
5134
  const { creditsPerMinorUnit, displayExchangeRate, adjustBalance } = balance;
4992
5135
  const { refetchPurchase } = useSolvaPay();
4993
5136
  const { merchant } = useMerchant();
4994
- const topupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
5137
+ const [topupCurrencyOverride, setTopupCurrencyOverride] = useState15(null);
5138
+ const topupCurrencies = useMemo16(() => {
5139
+ const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
5140
+ const fallback = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
5141
+ const list = fromMerchant.length > 0 ? fromMerchant : fallback ? [fallback] : [];
5142
+ return Array.from(new Set(list));
5143
+ }, [merchant?.supportedTopupCurrencies, merchant?.defaultCurrency, opts.topupCurrency]);
5144
+ const resolvedDefaultTopupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
5145
+ const planPickerCurrency = planCtx.preferredCurrency?.toUpperCase() ?? null;
5146
+ const topupCurrency = (topupCurrencyOverride && topupCurrencies.includes(topupCurrencyOverride) ? topupCurrencyOverride : null) ?? (planPickerCurrency && topupCurrencies.includes(planPickerCurrency) ? planPickerCurrency : null) ?? resolvedDefaultTopupCurrency;
4995
5147
  const topupCurrencyReady = topupCurrency != null;
4996
- const [step, setStep] = useState14(initialStep);
4997
- const [status, setStatus] = useState14("idle");
4998
- const [selectedAmountMinor, setSelectedAmountMinor] = useState14(initialAmountMinor);
4999
- const [successMeta, setSuccessMeta] = useState14(null);
5000
- const [error, setError] = useState14(null);
5148
+ const setTopupCurrency = useCallback19(
5149
+ (code) => {
5150
+ const normalized = code.toUpperCase();
5151
+ setTopupCurrencyOverride(normalized);
5152
+ },
5153
+ []
5154
+ );
5155
+ const [step, setStep] = useState15(initialStep);
5156
+ const [status, setStatus] = useState15("idle");
5157
+ const [selectedAmountMinor, setSelectedAmountMinor] = useState15(initialAmountMinor);
5158
+ const [successMeta, setSuccessMeta] = useState15(null);
5159
+ const [error, setError] = useState15(null);
5001
5160
  const selectedAmountMinorRef = useRef8(initialAmountMinor);
5002
5161
  const selectedPlan = planCtx.selectedPlan;
5003
5162
  const selectedPlanRef = planCtx.selectedPlanRef;
@@ -5090,19 +5249,23 @@ function useCheckoutFlow(opts) {
5090
5249
  );
5091
5250
  const recordRecurringSuccess = useCallback19(() => {
5092
5251
  if (!selectedPlanShape) return;
5093
- const currency = (selectedPlanShape.currency ?? "USD").toUpperCase();
5252
+ const pricingOption = selectedPlan ? resolvePlanPricingOption(selectedPlan, planSelection?.selectedCurrency) : {
5253
+ currency: selectedPlanShape.currency ?? "USD",
5254
+ price: selectedPlanShape.price ?? 0
5255
+ };
5256
+ const currency = pricingOption.currency.toUpperCase();
5094
5257
  const meta = {
5095
5258
  branch: "recurring",
5096
5259
  plan: selectedPlanShape,
5097
5260
  creditsIncluded: inferIncludedCredits(selectedPlanShape),
5098
- chargedTodayMinor: selectedPlanShape.price ?? 0,
5261
+ chargedTodayMinor: pricingOption.price ?? 0,
5099
5262
  currency,
5100
5263
  nextRenewalLabel: null
5101
5264
  };
5102
5265
  setSuccessMeta(meta);
5103
5266
  setStep("success");
5104
5267
  onPurchaseSuccessRef.current?.(meta);
5105
- }, [selectedPlanShape]);
5268
+ }, [selectedPlanShape, selectedPlan, planSelection?.selectedCurrency]);
5106
5269
  const advance = useCallback19(async () => {
5107
5270
  if (step === "plan") {
5108
5271
  await advanceFromPlan();
@@ -5148,6 +5311,7 @@ function useCheckoutFlow(opts) {
5148
5311
  setSuccessMeta(null);
5149
5312
  setError(null);
5150
5313
  setStatus("idle");
5314
+ setTopupCurrencyOverride(null);
5151
5315
  }, []);
5152
5316
  const retry = useCallback19(async () => {
5153
5317
  if (status !== "error") return;
@@ -5177,6 +5341,8 @@ function useCheckoutFlow(opts) {
5177
5341
  branch,
5178
5342
  topupCurrency,
5179
5343
  topupCurrencyReady,
5344
+ topupCurrencies,
5345
+ setTopupCurrency,
5180
5346
  canGoBack,
5181
5347
  selectPlan,
5182
5348
  selectAmount,
@@ -5197,6 +5363,8 @@ function useCheckoutFlow(opts) {
5197
5363
  branch,
5198
5364
  topupCurrency,
5199
5365
  topupCurrencyReady,
5366
+ topupCurrencies,
5367
+ setTopupCurrency,
5200
5368
  canGoBack,
5201
5369
  selectPlan,
5202
5370
  selectAmount,
@@ -5237,6 +5405,7 @@ export {
5237
5405
  MissingProviderError,
5238
5406
  MissingProductRefError,
5239
5407
  useSolvaPay,
5408
+ usePlanSelection,
5240
5409
  useCheckout,
5241
5410
  usePurchase,
5242
5411
  useCustomer,
@@ -5248,7 +5417,6 @@ export {
5248
5417
  productCache,
5249
5418
  useProduct,
5250
5419
  useActivation,
5251
- usePlanSelection,
5252
5420
  PaymentFormContext,
5253
5421
  usePaymentForm,
5254
5422
  PaymentFormProvider,
@@ -5304,6 +5472,7 @@ export {
5304
5472
  useTopupForm,
5305
5473
  BalanceBadge,
5306
5474
  isPaygPlan,
5475
+ resolvePlanPricingOption,
5307
5476
  PlanSelectorRoot2 as PlanSelectorRoot,
5308
5477
  PlanSelectorHeading2 as PlanSelectorHeading,
5309
5478
  PlanSelectorGrid2 as PlanSelectorGrid,
@@ -5311,6 +5480,8 @@ export {
5311
5480
  PlanSelectorCardName2 as PlanSelectorCardName,
5312
5481
  PlanSelectorCardPrice2 as PlanSelectorCardPrice,
5313
5482
  PlanSelectorCardInterval2 as PlanSelectorCardInterval,
5483
+ PlanSelectorCurrencySwitcher2 as PlanSelectorCurrencySwitcher,
5484
+ PlanSelectorCardCurrency2 as PlanSelectorCardCurrency,
5314
5485
  PlanSelectorCardBadge2 as PlanSelectorCardBadge,
5315
5486
  PlanSelectorLoading2 as PlanSelectorLoading,
5316
5487
  PlanSelectorError2 as PlanSelectorError,
@@ -30,7 +30,7 @@ import {
30
30
  useProduct,
31
31
  usePurchase,
32
32
  useTopupAmountSelector
33
- } from "./chunk-Y2UAJG67.js";
33
+ } from "./chunk-ORMHGGOU.js";
34
34
 
35
35
  // src/TopupForm.tsx
36
36
  import { jsx, jsxs } from "react/jsx-runtime";