@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.
@@ -342,7 +342,7 @@ function toMajorUnits(amountMinor, currency) {
342
342
  return fractionDigits === 0 ? amountMinor : amountMinor / 100;
343
343
  }
344
344
  function formatPrice(amountMinor, currency, opts = {}) {
345
- const { locale, interval, intervalCount = 1, free = "Free" } = opts;
345
+ const { locale, interval, intervalCount = 1, free = "Free", currencyDisplay } = opts;
346
346
  if (amountMinor === 0 && free !== "") {
347
347
  return free;
348
348
  }
@@ -351,12 +351,16 @@ function formatPrice(amountMinor, currency, opts = {}) {
351
351
  const isWhole = amountMinor % minorPerMajor === 0;
352
352
  const fractionDigits = isWhole ? 0 : naturalFractionDigits;
353
353
  const major = toMajorUnits(amountMinor, currency);
354
- const formatter = new Intl.NumberFormat(locale, {
354
+ const formatterOptions = {
355
355
  style: "currency",
356
356
  currency: currency.toUpperCase(),
357
357
  minimumFractionDigits: fractionDigits,
358
358
  maximumFractionDigits: fractionDigits
359
- });
359
+ };
360
+ if (currencyDisplay) {
361
+ formatterOptions.currencyDisplay = currencyDisplay;
362
+ }
363
+ const formatter = new Intl.NumberFormat(locale, formatterOptions);
360
364
  const formatted = formatter.format(major);
361
365
  if (!interval) return formatted;
362
366
  const suffix = intervalCount > 1 ? `${intervalCount} ${interval}s` : interval;
@@ -682,6 +686,17 @@ async function buildRequestHeaders(config) {
682
686
  return { headers, userId };
683
687
  }
684
688
 
689
+ // src/utils/readErrorMessage.ts
690
+ async function readErrorMessage(res, fallbackPrefix) {
691
+ let serverMessage;
692
+ try {
693
+ const data = await res.clone().json();
694
+ serverMessage = data?.error;
695
+ } catch {
696
+ }
697
+ return serverMessage || `${fallbackPrefix}: ${res.statusText || res.status}`;
698
+ }
699
+
685
700
  // src/transport/http.ts
686
701
  async function request(config, url, opts) {
687
702
  const { headers } = await buildRequestHeaders(config);
@@ -692,13 +707,8 @@ async function request(config, url, opts) {
692
707
  }
693
708
  const res = await fetchFn(url, init);
694
709
  if (!res.ok) {
695
- let serverMessage;
696
- try {
697
- const data = await res.clone().json();
698
- serverMessage = data?.error;
699
- } catch {
700
- }
701
- const error = new Error(serverMessage || `${opts.errorPrefix}: ${res.statusText || res.status}`);
710
+ const message = await readErrorMessage(res, opts.errorPrefix);
711
+ const error = new Error(message);
702
712
  config?.onError?.(error, opts.onErrorContext);
703
713
  throw error;
704
714
  }
@@ -738,6 +748,7 @@ function createHttpTransport(config) {
738
748
  const body = {};
739
749
  if (params.planRef) body.planRef = params.planRef;
740
750
  if (params.productRef) body.productRef = params.productRef;
751
+ if (params.currency) body.currency = params.currency;
741
752
  if (params.customer && (params.customer.name || params.customer.email)) {
742
753
  body.customer = params.customer;
743
754
  }
@@ -970,6 +981,7 @@ var enCopy = {
970
981
  currentBadge: "Current",
971
982
  popularBadge: "Popular",
972
983
  freeBadge: "Free",
984
+ usageRateLabel: "Usage-based",
973
985
  perIntervalShort: "/{interval}",
974
986
  continueButton: "Continue",
975
987
  backButton: "\u2190 Back to plans",
@@ -1838,7 +1850,8 @@ async function defaultListPlans(productRef, config) {
1838
1850
  const { headers } = await buildRequestHeaders(config);
1839
1851
  const res = await fetchFn(url, { method: "GET", headers });
1840
1852
  if (!res.ok) {
1841
- const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
1853
+ const message = await readErrorMessage(res, "Failed to fetch plans");
1854
+ const error = new Error(message);
1842
1855
  config?.onError?.(error, "listPlans");
1843
1856
  throw error;
1844
1857
  }
@@ -3778,6 +3791,28 @@ function isPaygPlan(plan) {
3778
3791
  return type === "usage-based" || type === "hybrid";
3779
3792
  }
3780
3793
 
3794
+ // src/utils/planPricing.ts
3795
+ function getPlanPricingOptions(plan) {
3796
+ if (plan.pricingOptions && plan.pricingOptions.length > 0) {
3797
+ return plan.pricingOptions;
3798
+ }
3799
+ return [
3800
+ {
3801
+ currency: plan.currency ?? "USD",
3802
+ price: plan.price ?? 0,
3803
+ default: true
3804
+ }
3805
+ ];
3806
+ }
3807
+ function resolvePlanPricingOption(plan, currency) {
3808
+ const options = getPlanPricingOptions(plan);
3809
+ if (currency) {
3810
+ const match = options.find((option) => option.currency.toUpperCase() === currency.toUpperCase());
3811
+ if (match) return match;
3812
+ }
3813
+ return options.find((option) => option.default) ?? options[0];
3814
+ }
3815
+
3781
3816
  // src/primitives/PlanSelector.tsx
3782
3817
  var import_jsx_runtime17 = require("react/jsx-runtime");
3783
3818
  var PlanSelectorContext = (0, import_react26.createContext)(null);
@@ -3858,6 +3893,23 @@ var Root3 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
3858
3893
  setSelectedPlanIndex(-1);
3859
3894
  }, [setSelectedPlanIndex]);
3860
3895
  const selectedPlanRef = selectedPlan?.reference ?? null;
3896
+ const [preferredCurrency, setPreferredCurrencyState] = (0, import_react26.useState)(null);
3897
+ const [selectedCurrencies, setSelectedCurrencies] = (0, import_react26.useState)({});
3898
+ const getSelectedOption = (0, import_react26.useCallback)(
3899
+ (plan) => resolvePlanPricingOption(
3900
+ plan,
3901
+ selectedCurrencies[plan.reference] ?? preferredCurrency ?? null
3902
+ ),
3903
+ [selectedCurrencies, preferredCurrency]
3904
+ );
3905
+ const setPreferredCurrency = (0, import_react26.useCallback)((currency) => {
3906
+ setPreferredCurrencyState(currency.toUpperCase());
3907
+ setSelectedCurrencies({});
3908
+ }, []);
3909
+ const setPlanCurrency = (0, import_react26.useCallback)((planRef, currency) => {
3910
+ setSelectedCurrencies((current) => ({ ...current, [planRef]: currency }));
3911
+ }, []);
3912
+ const selectedCurrency = selectedPlan ? getSelectedOption(selectedPlan).currency : null;
3861
3913
  const autoCurrentAppliedKeyRef = (0, import_react26.useRef)(null);
3862
3914
  (0, import_react26.useEffect)(() => {
3863
3915
  if (autoCurrentAppliedKeyRef.current === productRef) return;
@@ -3887,7 +3939,12 @@ var Root3 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
3887
3939
  isFree,
3888
3940
  isPopular,
3889
3941
  select,
3890
- clearSelection
3942
+ clearSelection,
3943
+ preferredCurrency,
3944
+ setPreferredCurrency,
3945
+ selectedCurrencies,
3946
+ setPlanCurrency,
3947
+ getSelectedOption
3891
3948
  }),
3892
3949
  [
3893
3950
  plans,
@@ -3901,7 +3958,12 @@ var Root3 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
3901
3958
  isFree,
3902
3959
  isPopular,
3903
3960
  select,
3904
- clearSelection
3961
+ clearSelection,
3962
+ preferredCurrency,
3963
+ setPreferredCurrency,
3964
+ selectedCurrencies,
3965
+ setPlanCurrency,
3966
+ getSelectedOption
3905
3967
  ]
3906
3968
  );
3907
3969
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
@@ -3913,6 +3975,12 @@ var Root3 = (0, import_react26.forwardRef)(function PlanSelectorRoot(props, forw
3913
3975
  setSelectedPlanRef: (ref) => {
3914
3976
  if (ref) select(ref);
3915
3977
  },
3978
+ selectedCurrency,
3979
+ setSelectedCurrency: (currency) => {
3980
+ if (currency) {
3981
+ setPreferredCurrency(currency);
3982
+ }
3983
+ },
3916
3984
  plans,
3917
3985
  loading,
3918
3986
  error
@@ -3937,14 +4005,19 @@ var Grid = (0, import_react26.forwardRef)(function PlanSelectorGrid({ children,
3937
4005
  const isPaygCurrent = isCurrent && isPaygPlan(plan);
3938
4006
  const disabled = isFree || isCurrent && !isPaygCurrent;
3939
4007
  const state = isCurrent && !isPaygCurrent ? "current" : selected ? "selected" : isCurrent ? "current" : isFree ? "disabled" : "idle";
4008
+ const pricingOptions = getPlanPricingOptions(plan);
4009
+ const selectedOption = ctx.getSelectedOption(plan);
3940
4010
  const cardCtx = {
3941
4011
  plan,
4012
+ selectedOption,
4013
+ pricingOptions,
3942
4014
  state,
3943
4015
  isCurrent,
3944
4016
  isFree,
3945
4017
  isPopular,
3946
4018
  disabled,
3947
- select: () => ctx.select(plan.reference)
4019
+ select: () => ctx.select(plan.reference),
4020
+ setCurrency: (currency) => ctx.setPlanCurrency(plan.reference, currency)
3948
4021
  };
3949
4022
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CardContext.Provider, { value: cardCtx, children }, plan.reference);
3950
4023
  }) });
@@ -3996,26 +4069,20 @@ var CardPrice = (0, import_react26.forwardRef)(function PlanSelectorCardPrice({
3996
4069
  const formatted = (0, import_react26.useMemo)(() => {
3997
4070
  if (card.isFree) return copy.planSelector.freeBadge;
3998
4071
  if (card.plan.type === "usage-based") {
3999
- const creditsPerUnit = card.plan.creditsPerUnit ?? 1;
4000
- const perCallMinor = Math.max(1, Math.round(1 / creditsPerUnit));
4001
- const rate = formatPrice(perCallMinor, card.plan.currency ?? "usd", {
4002
- locale,
4003
- free: ""
4004
- });
4005
- return `${rate} / call`;
4072
+ return copy.planSelector.usageRateLabel;
4006
4073
  }
4007
- return formatPrice(card.plan.price ?? 0, card.plan.currency ?? "usd", {
4074
+ return formatPrice(card.selectedOption.price ?? 0, card.selectedOption.currency ?? "usd", {
4008
4075
  locale,
4009
4076
  free: copy.interval.free
4010
4077
  });
4011
4078
  }, [
4012
4079
  card.isFree,
4013
4080
  card.plan.type,
4014
- card.plan.price,
4015
- card.plan.currency,
4016
- card.plan.creditsPerUnit,
4081
+ card.selectedOption.price,
4082
+ card.selectedOption.currency,
4017
4083
  locale,
4018
4084
  copy.planSelector.freeBadge,
4085
+ copy.planSelector.usageRateLabel,
4019
4086
  copy.interval.free
4020
4087
  ]);
4021
4088
  const Comp = asChild ? Slot : "span";
@@ -4040,6 +4107,57 @@ function normalizeBillingCycle(cycle) {
4040
4107
  if (lc === "daily" || lc === "day") return "day";
4041
4108
  return cycle;
4042
4109
  }
4110
+ var CurrencySwitcher = (0, import_react26.forwardRef)(function PlanSelectorCurrencySwitcher({ children, onChange, className, ...rest }, forwardedRef) {
4111
+ const ctx = usePlanSelectorContext("CurrencySwitcher");
4112
+ const availableCurrencies = (0, import_react26.useMemo)(() => {
4113
+ const currencies = /* @__PURE__ */ new Set();
4114
+ for (const plan of ctx.plans) {
4115
+ const options = getPlanPricingOptions(plan);
4116
+ if (options.length <= 1) continue;
4117
+ for (const option of options) {
4118
+ currencies.add(option.currency.toUpperCase());
4119
+ }
4120
+ }
4121
+ return [...currencies].sort();
4122
+ }, [ctx.plans]);
4123
+ if (availableCurrencies.length < 2) return null;
4124
+ const effectiveCurrency = ctx.preferredCurrency ?? (ctx.selectedPlan ? ctx.getSelectedOption(ctx.selectedPlan).currency.toUpperCase() : null) ?? availableCurrencies[0];
4125
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4126
+ "select",
4127
+ {
4128
+ ref: forwardedRef,
4129
+ "data-solvapay-plan-selector-currency-switcher": "",
4130
+ className,
4131
+ value: effectiveCurrency,
4132
+ onChange: (event) => {
4133
+ ctx.setPreferredCurrency(event.target.value);
4134
+ onChange?.(event);
4135
+ },
4136
+ ...rest,
4137
+ children: children ?? availableCurrencies.map((currency) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: currency, children: currency }, currency))
4138
+ }
4139
+ );
4140
+ });
4141
+ var CardCurrency = (0, import_react26.forwardRef)(
4142
+ function PlanSelectorCardCurrency({ children, onChange, ...rest }, forwardedRef) {
4143
+ const card = useCardContext("CardCurrency");
4144
+ if (card.pricingOptions.length <= 1) return null;
4145
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
4146
+ "select",
4147
+ {
4148
+ ref: forwardedRef,
4149
+ "data-solvapay-plan-selector-card-currency": "",
4150
+ value: card.selectedOption.currency,
4151
+ onChange: (event) => {
4152
+ card.setCurrency(event.target.value);
4153
+ onChange?.(event);
4154
+ },
4155
+ ...rest,
4156
+ children: children ?? card.pricingOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: option.currency, children: option.currency }, option.currency))
4157
+ }
4158
+ );
4159
+ }
4160
+ );
4043
4161
  var CardBadge = (0, import_react26.forwardRef)(function PlanSelectorCardBadge({ asChild, children, ...rest }, forwardedRef) {
4044
4162
  const card = useCardContext("CardBadge");
4045
4163
  const copy = useCopy();
@@ -4085,6 +4203,8 @@ var PlanSelector = {
4085
4203
  CardName,
4086
4204
  CardPrice,
4087
4205
  CardInterval,
4206
+ CurrencySwitcher,
4207
+ CardCurrency,
4088
4208
  CardBadge,
4089
4209
  Loading: Loading2,
4090
4210
  Error: ErrorSlot
@@ -4107,21 +4227,21 @@ function planSortByPaygFirstThenAsc(a, b) {
4107
4227
  if (!aPayg && bPayg) return 1;
4108
4228
  return (a.price ?? 0) - (b.price ?? 0);
4109
4229
  }
4110
- function formatContinueLabel(plan, locale) {
4230
+ function formatContinueLabel(plan, locale, pricingOption) {
4111
4231
  if (!plan) return "Continue";
4112
4232
  if (isPayg(plan)) {
4113
4233
  return `Continue with ${plan.name ?? "Pay as you go"}`;
4114
4234
  }
4115
- const currency = (plan.currency ?? "USD").toUpperCase();
4116
- const priceLabel = formatPrice(plan.price ?? 0, currency, { locale });
4235
+ const option = pricingOption ?? resolvePlanPricingOption(plan, null);
4236
+ const currency = option.currency.toUpperCase();
4237
+ const priceLabel = formatPrice(option.price ?? 0, currency, { locale });
4117
4238
  const cycle = plan.billingCycle ? `/${shortCycle(plan.billingCycle)}` : "";
4118
4239
  return `Continue with ${plan.name ?? "Plan"} \u2014 ${priceLabel}${cycle}`;
4119
4240
  }
4120
4241
  function formatPaygRate(plan, locale) {
4121
- const currency = (plan.currency ?? "USD").toUpperCase();
4122
4242
  const creditsPerUnit = plan.creditsPerUnit ?? 1;
4123
- const perCreditMinor = Math.max(1, Math.round(1 / creditsPerUnit));
4124
- return `${formatPrice(perCreditMinor, currency, { locale })} / call`;
4243
+ const creditLabel = creditsPerUnit === 1 ? "credit" : "credits";
4244
+ return `${creditsPerUnit.toLocaleString(locale)} ${creditLabel} / call`;
4125
4245
  }
4126
4246
  function inferIncludedCredits(plan) {
4127
4247
  const price = plan.price ?? 0;
@@ -4156,14 +4276,31 @@ function useCheckoutFlow(opts) {
4156
4276
  onPurchaseSuccessRef.current = opts.onPurchaseSuccess;
4157
4277
  onErrorRef.current = opts.onError;
4158
4278
  const planCtx = usePlanSelector();
4279
+ const planSelection = usePlanSelection();
4159
4280
  const transport = useTransport();
4160
4281
  const locale = useLocale();
4161
4282
  const balance = useBalance();
4162
4283
  const { creditsPerMinorUnit, displayExchangeRate, adjustBalance } = balance;
4163
4284
  const { refetchPurchase } = useSolvaPay();
4164
4285
  const { merchant } = useMerchant();
4165
- const topupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
4286
+ const [topupCurrencyOverride, setTopupCurrencyOverride] = (0, import_react27.useState)(null);
4287
+ const topupCurrencies = (0, import_react27.useMemo)(() => {
4288
+ const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
4289
+ const fallback = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
4290
+ const list = fromMerchant.length > 0 ? fromMerchant : fallback ? [fallback] : [];
4291
+ return Array.from(new Set(list));
4292
+ }, [merchant?.supportedTopupCurrencies, merchant?.defaultCurrency, opts.topupCurrency]);
4293
+ const resolvedDefaultTopupCurrency = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
4294
+ const planPickerCurrency = planCtx.preferredCurrency?.toUpperCase() ?? null;
4295
+ const topupCurrency = (topupCurrencyOverride && topupCurrencies.includes(topupCurrencyOverride) ? topupCurrencyOverride : null) ?? (planPickerCurrency && topupCurrencies.includes(planPickerCurrency) ? planPickerCurrency : null) ?? resolvedDefaultTopupCurrency;
4166
4296
  const topupCurrencyReady = topupCurrency != null;
4297
+ const setTopupCurrency = (0, import_react27.useCallback)(
4298
+ (code) => {
4299
+ const normalized = code.toUpperCase();
4300
+ setTopupCurrencyOverride(normalized);
4301
+ },
4302
+ []
4303
+ );
4167
4304
  const [step, setStep] = (0, import_react27.useState)(initialStep);
4168
4305
  const [status, setStatus] = (0, import_react27.useState)("idle");
4169
4306
  const [selectedAmountMinor, setSelectedAmountMinor] = (0, import_react27.useState)(initialAmountMinor);
@@ -4261,19 +4398,23 @@ function useCheckoutFlow(opts) {
4261
4398
  );
4262
4399
  const recordRecurringSuccess = (0, import_react27.useCallback)(() => {
4263
4400
  if (!selectedPlanShape) return;
4264
- const currency = (selectedPlanShape.currency ?? "USD").toUpperCase();
4401
+ const pricingOption = selectedPlan ? resolvePlanPricingOption(selectedPlan, planSelection?.selectedCurrency) : {
4402
+ currency: selectedPlanShape.currency ?? "USD",
4403
+ price: selectedPlanShape.price ?? 0
4404
+ };
4405
+ const currency = pricingOption.currency.toUpperCase();
4265
4406
  const meta = {
4266
4407
  branch: "recurring",
4267
4408
  plan: selectedPlanShape,
4268
4409
  creditsIncluded: inferIncludedCredits(selectedPlanShape),
4269
- chargedTodayMinor: selectedPlanShape.price ?? 0,
4410
+ chargedTodayMinor: pricingOption.price ?? 0,
4270
4411
  currency,
4271
4412
  nextRenewalLabel: null
4272
4413
  };
4273
4414
  setSuccessMeta(meta);
4274
4415
  setStep("success");
4275
4416
  onPurchaseSuccessRef.current?.(meta);
4276
- }, [selectedPlanShape]);
4417
+ }, [selectedPlanShape, selectedPlan, planSelection?.selectedCurrency]);
4277
4418
  const advance = (0, import_react27.useCallback)(async () => {
4278
4419
  if (step === "plan") {
4279
4420
  await advanceFromPlan();
@@ -4319,6 +4460,7 @@ function useCheckoutFlow(opts) {
4319
4460
  setSuccessMeta(null);
4320
4461
  setError(null);
4321
4462
  setStatus("idle");
4463
+ setTopupCurrencyOverride(null);
4322
4464
  }, []);
4323
4465
  const retry = (0, import_react27.useCallback)(async () => {
4324
4466
  if (status !== "error") return;
@@ -4348,6 +4490,8 @@ function useCheckoutFlow(opts) {
4348
4490
  branch,
4349
4491
  topupCurrency,
4350
4492
  topupCurrencyReady,
4493
+ topupCurrencies,
4494
+ setTopupCurrency,
4351
4495
  canGoBack,
4352
4496
  selectPlan,
4353
4497
  selectAmount,
@@ -4368,6 +4512,8 @@ function useCheckoutFlow(opts) {
4368
4512
  branch,
4369
4513
  topupCurrency,
4370
4514
  topupCurrencyReady,
4515
+ topupCurrencies,
4516
+ setTopupCurrency,
4371
4517
  canGoBack,
4372
4518
  selectPlan,
4373
4519
  selectAmount,
@@ -4414,16 +4560,20 @@ var PlanStep = (0, import_react28.memo)(function PlanStep2({
4414
4560
  activationError,
4415
4561
  cx
4416
4562
  }) {
4417
- const { selectedPlan, selectedPlanRef } = usePlanSelector();
4563
+ const { selectedPlan, selectedPlanRef, getSelectedOption } = usePlanSelector();
4418
4564
  const locale = useHostLocale();
4419
4565
  const copy = useCopy();
4420
4566
  const selectedPlanShape = selectedPlan;
4421
- const ctaLabel = formatContinueLabel(selectedPlanShape, locale);
4567
+ const pricingOption = selectedPlan ? getSelectedOption(selectedPlan) : void 0;
4568
+ const ctaLabel = formatContinueLabel(selectedPlanShape, locale, pricingOption);
4422
4569
  const showBanner = fromPaywall && !hideUpgradeBanner;
4423
4570
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
4424
4571
  onBack ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(BackLink, { label: copy.checkout.backToAccount, onClick: onBack }) : null,
4425
4572
  showBanner ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(UpgradeBanner, { kind: paywallKind, cx }) : null,
4426
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { className: cx.heading, children: "Choose a plan" }),
4573
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "solvapay-mcp-plan-step-header", children: [
4574
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { className: cx.heading, children: "Choose a plan" }),
4575
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlanSelector.CurrencySwitcher, { className: "solvapay-plan-selector-currency-switcher" })
4576
+ ] }),
4427
4577
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
4428
4578
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
4429
4579
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
@@ -4748,17 +4898,34 @@ function useAmountPicker() {
4748
4898
  // src/mcp/views/checkout/steps/AmountStep.tsx
4749
4899
  var import_jsx_runtime21 = require("react/jsx-runtime");
4750
4900
  var AmountStep = (0, import_react31.memo)(function AmountStep2({
4751
- plan,
4901
+ topupCurrency,
4902
+ topupCurrencies,
4903
+ onCurrencyChange,
4752
4904
  onBack,
4753
4905
  onContinue,
4754
4906
  cx
4755
4907
  }) {
4756
- const currency = (plan.currency ?? "USD").toUpperCase();
4908
+ const currency = (topupCurrency ?? "USD").toUpperCase();
4757
4909
  const locale = useHostLocale();
4758
4910
  const [stagedAmountMinor, setStagedAmountMinor] = (0, import_react31.useState)(null);
4911
+ const currencies = topupCurrencies ?? [];
4912
+ const showCurrencySwitch = currencies.length > 1 && !!onCurrencyChange;
4913
+ const currencyDisplay = showCurrencySwitch ? "code" : "symbol";
4759
4914
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(import_jsx_runtime21.Fragment, { children: [
4760
4915
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(BackLink, { label: "Back", onClick: onBack }),
4761
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h2", { className: cx.heading, children: "How many credits?" }),
4916
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "solvapay-mcp-step-header", children: [
4917
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h2", { className: cx.heading, children: "How many credits?" }),
4918
+ showCurrencySwitch && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4919
+ "select",
4920
+ {
4921
+ className: "solvapay-mcp-currency-switch",
4922
+ value: currency,
4923
+ onChange: (event) => onCurrencyChange?.(event.target.value),
4924
+ "aria-label": "Topup currency",
4925
+ children: currencies.map((code) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: code, children: code }, code))
4926
+ }
4927
+ )
4928
+ ] }),
4762
4929
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: cx.muted, children: "Top up to start using the tool." }),
4763
4930
  /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4764
4931
  AmountPicker.Root,
@@ -4768,14 +4935,14 @@ var AmountStep = (0, import_react31.memo)(function AmountStep2({
4768
4935
  className: cx.amountPicker,
4769
4936
  onChange: (value) => setStagedAmountMinor(value),
4770
4937
  children: [
4771
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PresetAmountRow, { cx }),
4772
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomAmountRow, { rowClassName: cx.amountCustom }),
4938
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(PresetAmountRow, { cx, currencyDisplay }),
4939
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(CustomAmountRow, { rowClassName: cx.amountCustom, currencyDisplay }),
4773
4940
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4774
4941
  AmountPicker.Confirm,
4775
4942
  {
4776
4943
  className: cx.button,
4777
4944
  onConfirm: (amountMinor) => onContinue(amountMinor),
4778
- children: stagedAmountMinor ? `Continue \u2014 ${formatPrice(stagedAmountMinor, currency, { locale })}` : "Continue"
4945
+ children: stagedAmountMinor ? `Continue \u2014 ${formatPrice(stagedAmountMinor, currency, { locale, currencyDisplay })}` : "Continue"
4779
4946
  }
4780
4947
  )
4781
4948
  ]
@@ -4783,14 +4950,18 @@ var AmountStep = (0, import_react31.memo)(function AmountStep2({
4783
4950
  )
4784
4951
  ] });
4785
4952
  });
4786
- function PresetAmountRow({ cx }) {
4953
+ function PresetAmountRow({
4954
+ cx,
4955
+ currencyDisplay
4956
+ }) {
4787
4957
  const { quickAmounts, currency } = useAmountPicker();
4788
4958
  const locale = useHostLocale();
4789
4959
  const popularIndex = Math.min(1, quickAmounts.length - 1);
4790
4960
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: cx.amountOptions, children: quickAmounts.map((amount, i) => {
4791
4961
  const label = formatPrice(amount * getMinorUnitsPerMajor(currency), currency, {
4792
4962
  locale,
4793
- free: ""
4963
+ free: "",
4964
+ currencyDisplay
4794
4965
  });
4795
4966
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4796
4967
  AmountPicker.Option,
@@ -4798,16 +4969,21 @@ function PresetAmountRow({ cx }) {
4798
4969
  amount,
4799
4970
  className: cx.amountOption,
4800
4971
  "data-popular": i === popularIndex ? "" : void 0,
4801
- "aria-label": `${label}${i === popularIndex ? " (popular)" : ""}`
4972
+ "aria-label": `${label}${i === popularIndex ? " (popular)" : ""}`,
4973
+ children: label
4802
4974
  },
4803
4975
  amount
4804
4976
  );
4805
4977
  }) });
4806
4978
  }
4807
- function CustomAmountRow({ rowClassName }) {
4808
- const { currencySymbol } = useAmountPicker();
4979
+ function CustomAmountRow({
4980
+ rowClassName,
4981
+ currencyDisplay
4982
+ }) {
4983
+ const { currencySymbol, currency } = useAmountPicker();
4984
+ const prefix = currencyDisplay === "code" ? currency.toUpperCase() : currencySymbol;
4809
4985
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: rowClassName, children: [
4810
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "solvapay-mcp-amount-currency-symbol", children: currencySymbol }),
4986
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "solvapay-mcp-amount-currency-symbol", children: prefix }),
4811
4987
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AmountPicker.Custom, { className: "solvapay-mcp-amount-custom-input", placeholder: "0.00" })
4812
4988
  ] });
4813
4989
  }
@@ -5543,14 +5719,15 @@ var TopupForm = {
5543
5719
  // src/mcp/views/checkout/steps/PaygPaymentStep.tsx
5544
5720
  var import_jsx_runtime26 = require("react/jsx-runtime");
5545
5721
  var PaygPaymentStep = (0, import_react37.memo)(function PaygPaymentStep2({
5546
- plan,
5722
+ plan: _plan,
5547
5723
  amountMinor,
5724
+ topupCurrency,
5548
5725
  returnUrl,
5549
5726
  onBack,
5550
5727
  onSuccess,
5551
5728
  cx
5552
5729
  }) {
5553
- const currency = (plan.currency ?? "USD").toUpperCase();
5730
+ const currency = (topupCurrency ?? "USD").toUpperCase();
5554
5731
  const locale = useHostLocale();
5555
5732
  const { creditsPerMinorUnit, displayExchangeRate } = useBalance();
5556
5733
  const creditsAdded = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 ? Math.floor(amountMinor / (displayExchangeRate ?? 1) * creditsPerMinorUnit) : null;
@@ -5612,7 +5789,11 @@ async function resolvePlanRef(productRef, config) {
5612
5789
  const { headers } = await buildRequestHeaders(config);
5613
5790
  const res = await fetchFn(url, { method: "GET", headers });
5614
5791
  if (!res.ok) {
5615
- throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
5792
+ const message = await readErrorMessage(
5793
+ res,
5794
+ `Failed to fetch plans for product "${productRef}"`
5795
+ );
5796
+ throw new Error(message);
5616
5797
  }
5617
5798
  const data = await res.json();
5618
5799
  const allPlans = data.plans ?? [];
@@ -5634,7 +5815,8 @@ async function resolvePlanRef(productRef, config) {
5634
5815
  );
5635
5816
  }
5636
5817
  function useCheckout(options) {
5637
- const { planRef, productRef, customer } = options;
5818
+ const { planRef, productRef, currency: currencyOverride, customer } = options;
5819
+ const planSelection = usePlanSelection();
5638
5820
  const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
5639
5821
  const [loading, setLoading] = (0, import_react38.useState)(false);
5640
5822
  const [error, setError] = (0, import_react38.useState)(null);
@@ -5666,9 +5848,11 @@ function useCheckout(options) {
5666
5848
  if (!effectivePlanRef) {
5667
5849
  throw new Error("Could not determine plan reference for checkout");
5668
5850
  }
5851
+ const selectedCurrency = currencyOverride ?? planSelection?.selectedCurrency ?? void 0;
5669
5852
  const result = await createPayment({
5670
5853
  planRef: effectivePlanRef,
5671
5854
  productRef,
5855
+ ...selectedCurrency && { currency: selectedCurrency },
5672
5856
  customer
5673
5857
  });
5674
5858
  if (!result || typeof result !== "object") {
@@ -5702,7 +5886,17 @@ function useCheckout(options) {
5702
5886
  setLoading(false);
5703
5887
  isStartingRef.current = false;
5704
5888
  }
5705
- }, [planRef, productRef, customer, createPayment, updateCustomerRef, loading, _config]);
5889
+ }, [
5890
+ planRef,
5891
+ productRef,
5892
+ currencyOverride,
5893
+ planSelection?.selectedCurrency,
5894
+ customer,
5895
+ createPayment,
5896
+ updateCustomerRef,
5897
+ loading,
5898
+ _config
5899
+ ]);
5706
5900
  const reset = (0, import_react38.useCallback)(() => {
5707
5901
  isStartingRef.current = false;
5708
5902
  setLoading(false);
@@ -6701,9 +6895,14 @@ var RecurringPaymentStep = (0, import_react43.memo)(function RecurringPaymentSte
6701
6895
  onSuccess,
6702
6896
  cx
6703
6897
  }) {
6704
- const currency = (plan.currency ?? "USD").toUpperCase();
6898
+ const planSelection = usePlanSelection();
6899
+ const pricingOption = resolvePlanPricingOption(
6900
+ plan,
6901
+ planSelection?.selectedCurrency
6902
+ );
6903
+ const currency = pricingOption.currency.toUpperCase();
6705
6904
  const locale = useHostLocale();
6706
- const amountMinor = plan.price ?? 0;
6905
+ const amountMinor = pricingOption.price ?? 0;
6707
6906
  const cycle = plan.billingCycle ?? "monthly";
6708
6907
  const credits = inferIncludedCredits(plan);
6709
6908
  const planName = plan.name ?? "Plan";
@@ -6960,6 +7159,9 @@ function McpCheckoutBody({
6960
7159
  AmountStep,
6961
7160
  {
6962
7161
  plan: selectedPlanShape,
7162
+ topupCurrency: flow.topupCurrency,
7163
+ topupCurrencies: flow.topupCurrencies,
7164
+ onCurrencyChange: flow.setTopupCurrency,
6963
7165
  onBack: () => flow.back(),
6964
7166
  onContinue: (amountMinor) => {
6965
7167
  flow.selectAmount(amountMinor);
@@ -6976,6 +7178,7 @@ function McpCheckoutBody({
6976
7178
  {
6977
7179
  plan: selectedPlanShape,
6978
7180
  amountMinor: flow.selectedAmountMinor,
7181
+ topupCurrency: flow.topupCurrency,
6979
7182
  returnUrl,
6980
7183
  onBack: () => flow.back(),
6981
7184
  onSuccess: (extras) => flow.notifyPaymentSuccess(void 0, extras),
@@ -7358,12 +7561,17 @@ function McpTopupView({
7358
7561
  }
7359
7562
  if (probe === "blocked")
7360
7563
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(HostedTopupFallback, { cx, onBack });
7361
- const currency = merchant?.defaultCurrency?.toUpperCase() ?? FALLBACK_TOPUP_CURRENCY;
7564
+ const defaultCurrency = merchant?.defaultCurrency?.toUpperCase() ?? FALLBACK_TOPUP_CURRENCY;
7565
+ const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
7566
+ const topupCurrencies = Array.from(
7567
+ new Set(fromMerchant.length > 0 ? fromMerchant : [defaultCurrency])
7568
+ );
7362
7569
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
7363
7570
  EmbeddedTopup,
7364
7571
  {
7365
7572
  returnUrl,
7366
- currency,
7573
+ defaultCurrency,
7574
+ topupCurrencies,
7367
7575
  onTopupSuccess,
7368
7576
  onBack,
7369
7577
  cx
@@ -7372,16 +7580,25 @@ function McpTopupView({
7372
7580
  }
7373
7581
  function EmbeddedTopup({
7374
7582
  returnUrl,
7375
- currency,
7583
+ defaultCurrency,
7584
+ topupCurrencies,
7376
7585
  onTopupSuccess,
7377
7586
  onBack,
7378
7587
  cx
7379
7588
  }) {
7380
7589
  const [committedAmountMinor, setCommittedAmountMinor] = (0, import_react47.useState)(null);
7381
7590
  const [justPaidMinor, setJustPaidMinor] = (0, import_react47.useState)(null);
7382
- const { adjustBalance, credits, creditsPerMinorUnit, displayExchangeRate } = useBalance();
7591
+ const [selectedCurrency, setSelectedCurrency] = (0, import_react47.useState)(defaultCurrency);
7592
+ const currency = selectedCurrency;
7593
+ const showCurrencySwitch = topupCurrencies.length > 1;
7594
+ const { adjustBalance, credits, creditsPerMinorUnit, displayCurrency, displayExchangeRate } = useBalance();
7383
7595
  const locale = useHostLocale();
7384
7596
  const { notifyModelContext, notifySuccess } = useMcpBridge();
7597
+ const handleCurrencyChange = (code) => {
7598
+ setSelectedCurrency(code.toUpperCase());
7599
+ setCommittedAmountMinor(null);
7600
+ setJustPaidMinor(null);
7601
+ };
7385
7602
  if (justPaidMinor != null) {
7386
7603
  const displayAmount = formatPrice(justPaidMinor, currency, { locale, free: "" });
7387
7604
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: cx.card, children: [
@@ -7415,7 +7632,8 @@ function EmbeddedTopup({
7415
7632
  }
7416
7633
  if (committedAmountMinor != null && committedAmountMinor > 0) {
7417
7634
  const displayAmount = formatPrice(committedAmountMinor, currency, { locale, free: "" });
7418
- const creditsAdded = creditsPerMinorUnit != null && creditsPerMinorUnit > 0 ? Math.floor(
7635
+ const rateAppliesToCurrency = displayCurrency != null && currency.toUpperCase() === displayCurrency.toUpperCase();
7636
+ const creditsAdded = rateAppliesToCurrency && creditsPerMinorUnit != null && creditsPerMinorUnit > 0 ? Math.floor(
7419
7637
  committedAmountMinor / (displayExchangeRate ?? 1) * creditsPerMinorUnit
7420
7638
  ) : null;
7421
7639
  const formattedBalance = credits != null ? new Intl.NumberFormat(locale).format(credits) : null;
@@ -7481,9 +7699,36 @@ function EmbeddedTopup({
7481
7699
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("h2", { className: cx.heading, children: "Add credits" }),
7482
7700
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(BalanceBadge, {})
7483
7701
  ] }),
7702
+ showCurrencySwitch ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "solvapay-mcp-step-header", children: [
7703
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: cx.muted, children: "Pay in" }),
7704
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
7705
+ "select",
7706
+ {
7707
+ className: "solvapay-mcp-currency-switch",
7708
+ value: currency,
7709
+ onChange: (event) => handleCurrencyChange(event.target.value),
7710
+ "aria-label": "Topup currency",
7711
+ children: topupCurrencies.map((code) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("option", { value: code, children: code }, code))
7712
+ }
7713
+ )
7714
+ ] }) : null,
7484
7715
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(AmountPicker.Root, { currency, emit: "minor", className: cx.amountPicker, children: [
7485
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(QuickAmountOptions, { className: cx.amountOptions, optionClassName: cx.amountOption }),
7486
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(CustomAmountRow2, { rowClassName: cx.amountCustom }),
7716
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
7717
+ QuickAmountOptions,
7718
+ {
7719
+ className: cx.amountOptions,
7720
+ optionClassName: cx.amountOption,
7721
+ currencyDisplay: showCurrencySwitch ? "code" : "symbol",
7722
+ locale
7723
+ }
7724
+ ),
7725
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
7726
+ CustomAmountRow2,
7727
+ {
7728
+ rowClassName: cx.amountCustom,
7729
+ currencyDisplay: showCurrencySwitch ? "code" : "symbol"
7730
+ }
7731
+ ),
7487
7732
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
7488
7733
  AmountPicker.Confirm,
7489
7734
  {
@@ -7505,15 +7750,28 @@ function EmbeddedTopup({
7505
7750
  }
7506
7751
  function QuickAmountOptions({
7507
7752
  className,
7508
- optionClassName
7753
+ optionClassName,
7754
+ currencyDisplay,
7755
+ locale
7509
7756
  }) {
7510
- const { quickAmounts } = useAmountPicker();
7511
- return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className, children: quickAmounts.map((amount) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(AmountPicker.Option, { amount, className: optionClassName }, amount)) });
7757
+ const { quickAmounts, currency } = useAmountPicker();
7758
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className, children: quickAmounts.map((amount) => {
7759
+ const label = formatPrice(amount * getMinorUnitsPerMajor(currency), currency, {
7760
+ locale,
7761
+ free: "",
7762
+ currencyDisplay
7763
+ });
7764
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(AmountPicker.Option, { amount, className: optionClassName, children: label }, amount);
7765
+ }) });
7512
7766
  }
7513
- function CustomAmountRow2({ rowClassName }) {
7514
- const { currencySymbol } = useAmountPicker();
7767
+ function CustomAmountRow2({
7768
+ rowClassName,
7769
+ currencyDisplay
7770
+ }) {
7771
+ const { currencySymbol, currency } = useAmountPicker();
7772
+ const prefix = currencyDisplay === "code" ? currency.toUpperCase() : currencySymbol;
7515
7773
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: rowClassName, children: [
7516
- /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "solvapay-mcp-amount-currency-symbol", children: currencySymbol }),
7774
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "solvapay-mcp-amount-currency-symbol", children: prefix }),
7517
7775
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(AmountPicker.Custom, { className: "solvapay-mcp-amount-custom-input", placeholder: "0.00" })
7518
7776
  ] });
7519
7777
  }