@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/CHANGELOG.md +11 -0
- package/dist/adapters/auth0.cjs +60 -0
- package/dist/adapters/auth0.d.cts +16 -0
- package/dist/adapters/auth0.d.ts +16 -0
- package/dist/adapters/auth0.js +15 -0
- package/dist/adapters/session-auth.cjs +49 -0
- package/dist/adapters/session-auth.d.cts +20 -0
- package/dist/adapters/session-auth.d.ts +20 -0
- package/dist/adapters/session-auth.js +6 -0
- package/dist/{chunk-XRNCDI2T.js → chunk-3UR4QQ5P.js} +1 -1
- package/dist/chunk-JTW665JR.js +25 -0
- package/dist/{chunk-Y2UAJG67.js → chunk-ORMHGGOU.js} +244 -73
- package/dist/{chunk-MYD6KTR2.js → chunk-WGDHMTDO.js} +1 -1
- package/dist/{index-DPoqku6R.d.cts → index-BwwdMBdt.d.ts} +20 -5
- package/dist/{index-Cs-QqpkJ.d.ts → index-kXLvp7EU.d.cts} +20 -5
- package/dist/index.cjs +294 -106
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +8 -3
- package/dist/mcp/index.cjs +328 -70
- package/dist/mcp/index.d.cts +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +131 -34
- package/dist/mcp/styles.css +39 -0
- package/dist/primitives/index.cjs +207 -38
- package/dist/primitives/index.d.cts +22 -5
- package/dist/primitives/index.d.ts +22 -5
- package/dist/primitives/index.js +6 -2
- package/dist/{shared-DpS2yEah.d.cts → shared-BNZAIWkU.d.cts} +196 -7
- package/dist/{shared-CXRbrNFw.d.ts → shared-lIhc8DS3.d.ts} +196 -7
- package/dist/styles.css +25 -0
- package/package.json +12 -7
|
@@ -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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
1855
|
-
import { jsx as
|
|
1856
|
-
var PaymentFormContext =
|
|
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 =
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
4196
|
-
card.
|
|
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
|
|
4430
|
+
import { useState as useState13, useCallback as useCallback14 } from "react";
|
|
4289
4431
|
function usePurchaseActions() {
|
|
4290
4432
|
const ctx = useSolvaPay();
|
|
4291
|
-
const [isCancelling, setIsCancelling] =
|
|
4292
|
-
const [isReactivating, setIsReactivating] =
|
|
4293
|
-
const [isActivating, setIsActivating] =
|
|
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
|
|
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] =
|
|
4662
|
-
const [error, setError] =
|
|
4663
|
-
const [transportLoading, setTransportLoading] =
|
|
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
|
|
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
|
|
4945
|
-
const
|
|
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
|
|
4953
|
-
return `${
|
|
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
|
|
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
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
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
|
|
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:
|
|
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,
|