@solvapay/react 1.0.9-preview.1 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,820 +1,145 @@
1
+ import {
2
+ CurrentPlanCard,
3
+ LaunchCustomerPortalButton,
4
+ UpdatePaymentMethodButton,
5
+ usePaymentMethod
6
+ } from "./chunk-HWVJL5X6.js";
7
+ import {
8
+ ActivationFlow,
9
+ CreditGate,
10
+ PlanBadge,
11
+ ProductBadge,
12
+ PurchaseGate,
13
+ TopupForm,
14
+ useActivationFlow,
15
+ useCreditGate
16
+ } from "./chunk-R2ZPZ7VM.js";
17
+ import {
18
+ AmountPicker,
19
+ BalanceBadge,
20
+ CancelPlanButton,
21
+ CancelledPlanNotice,
22
+ CheckoutSummary2 as CheckoutSummary,
23
+ CopyContext,
24
+ CopyProvider,
25
+ DEFAULT_ROUTES,
26
+ MandateText,
27
+ PaymentForm,
28
+ PaymentFormCardElement,
29
+ PaymentFormContext,
30
+ PaymentFormCustomerFields,
31
+ PaymentFormError,
32
+ PaymentFormLoading,
33
+ PaymentFormMandateText,
34
+ PaymentFormPaymentElement,
35
+ PaymentFormProvider,
36
+ PaymentFormSubmitButton,
37
+ PaymentFormSummary,
38
+ PaymentFormTermsCheckbox,
39
+ PlanSelector,
40
+ SolvaPayContext,
41
+ SolvaPayProvider,
42
+ Spinner,
43
+ confirmPayment,
44
+ createHttpTransport,
45
+ defaultListPlans,
46
+ deriveVariant,
47
+ enCopy,
48
+ filterPurchases,
49
+ formatPrice,
50
+ getActivePurchases,
51
+ getCancelledPurchasesWithEndDate,
52
+ getMinorUnitsPerMajor,
53
+ getMostRecentPurchase,
54
+ getPrimaryPurchase,
55
+ interpolate,
56
+ isPaidPurchase,
57
+ isPlanPurchase,
58
+ isTopupPurchase,
59
+ mergeCopy,
60
+ resolveCta,
61
+ toMajorUnits,
62
+ useActivation,
63
+ useAmountPicker,
64
+ useAmountPickerCopy,
65
+ useBalance,
66
+ useCheckout,
67
+ useCopy,
68
+ useCustomer,
69
+ useLocale,
70
+ useMerchant,
71
+ usePaymentForm,
72
+ usePaywallResolver,
73
+ usePlan,
74
+ usePlans,
75
+ useProduct,
76
+ usePurchase,
77
+ usePurchaseActions,
78
+ usePurchaseStatus,
79
+ useSolvaPay,
80
+ useTopup,
81
+ useTopupAmountSelector,
82
+ useTransport,
83
+ useUsage
84
+ } from "./chunk-37R5NZGF.js";
1
85
  import {
2
86
  defaultAuthAdapter
3
87
  } from "./chunk-OUSEQRCT.js";
4
-
5
- // src/SolvaPayProvider.tsx
6
- import { createContext, useState, useEffect, useCallback, useMemo, useRef } from "react";
7
-
8
- // src/utils/purchases.ts
9
- function filterPurchases(purchases) {
10
- return purchases.filter((purchase) => purchase.status === "active");
11
- }
12
- function getActivePurchases(purchases) {
13
- return purchases.filter((purchase) => purchase.status === "active");
14
- }
15
- function getCancelledPurchasesWithEndDate(purchases) {
16
- const now = /* @__PURE__ */ new Date();
17
- return purchases.filter((purchase) => {
18
- return purchase.status === "active" && purchase.cancelledAt && purchase.endDate && new Date(purchase.endDate) > now;
19
- });
20
- }
21
- function getMostRecentPurchase(purchases) {
22
- if (purchases.length === 0) return null;
23
- return purchases.reduce((latest, current) => {
24
- return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
25
- });
26
- }
27
- function getPrimaryPurchase(purchases) {
28
- const filtered = filterPurchases(purchases);
29
- if (filtered.length > 0) {
30
- return getMostRecentPurchase(filtered);
31
- }
32
- return null;
33
- }
34
- function isPaidPurchase(purchase) {
35
- return (purchase.amount ?? 0) > 0;
36
- }
37
-
38
- // src/utils/headers.ts
39
- var CUSTOMER_REF_KEY = "solvapay_customerRef";
40
- var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
41
- var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
42
- function getAuthAdapter(config) {
43
- if (config?.auth?.adapter) {
44
- return config.auth.adapter;
45
- }
46
- if (config?.auth?.getToken || config?.auth?.getUserId) {
47
- return {
48
- async getToken() {
49
- return config?.auth?.getToken?.() || null;
50
- },
51
- async getUserId() {
52
- return config?.auth?.getUserId?.() || null;
53
- }
54
- };
55
- }
56
- return defaultAuthAdapter;
57
- }
58
- function getCachedCustomerRef(userId) {
59
- if (typeof window === "undefined") return null;
60
- const cached = localStorage.getItem(CUSTOMER_REF_KEY);
61
- const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
62
- const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
63
- if (!cached || !expiry) return null;
64
- if (Date.now() > parseInt(expiry)) {
65
- localStorage.removeItem(CUSTOMER_REF_KEY);
66
- localStorage.removeItem(CUSTOMER_REF_EXPIRY);
67
- localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
68
- return null;
69
- }
70
- if (userId !== void 0 && userId !== null) {
71
- if (cachedUserId !== userId) {
72
- clearCachedCustomerRef();
73
- return null;
74
- }
75
- }
76
- return cached;
77
- }
78
- var CACHE_DURATION = 24 * 60 * 60 * 1e3;
79
- function setCachedCustomerRef(customerRef, userId) {
80
- if (typeof window === "undefined") return;
81
- if (userId === void 0 || userId === null) {
82
- return;
83
- }
84
- localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
85
- localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
86
- localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
87
- }
88
- function clearCachedCustomerRef() {
89
- if (typeof window === "undefined") return;
90
- localStorage.removeItem(CUSTOMER_REF_KEY);
91
- localStorage.removeItem(CUSTOMER_REF_EXPIRY);
92
- localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
93
- }
94
- async function buildRequestHeaders(config) {
95
- const adapter = getAuthAdapter(config);
96
- const token = await adapter.getToken();
97
- const userId = await adapter.getUserId();
98
- const cachedRef = getCachedCustomerRef(userId);
99
- const headers = {
100
- "Content-Type": "application/json"
101
- };
102
- if (token) {
103
- headers["Authorization"] = `Bearer ${token}`;
104
- }
105
- if (cachedRef) {
106
- headers["x-solvapay-customer-ref"] = cachedRef;
107
- }
108
- if (config?.headers) {
109
- const custom = typeof config.headers === "function" ? await config.headers() : config.headers;
110
- Object.assign(headers, custom);
111
- }
112
- return { headers, userId };
113
- }
114
-
115
- // src/SolvaPayProvider.tsx
116
- import { jsx } from "react/jsx-runtime";
117
- var SolvaPayContext = createContext(null);
118
- var SolvaPayProvider = ({
119
- config,
120
- createPayment: customCreatePayment,
121
- checkPurchase: customCheckPurchase,
122
- processPayment: customProcessPayment,
123
- createTopupPayment: customCreateTopupPayment,
124
- children
125
- }) => {
126
- const [purchaseData, setPurchaseData] = useState({
127
- purchases: []
128
- });
129
- const [loading, setLoading] = useState(false);
130
- const [isRefetching, setIsRefetching] = useState(false);
131
- const [purchaseError, setPurchaseError] = useState(null);
132
- const [internalCustomerRef, setInternalCustomerRef] = useState(void 0);
133
- const [userId, setUserId] = useState(null);
134
- const [isAuthenticated, setIsAuthenticated] = useState(false);
135
- const [creditsValue, setCreditsValue] = useState(null);
136
- const [displayCurrencyValue, setDisplayCurrencyValue] = useState(null);
137
- const [creditsPerMinorUnitValue, setCreditsPerMinorUnitValue] = useState(null);
138
- const [displayExchangeRateValue, setDisplayExchangeRateValue] = useState(null);
139
- const [balanceLoading, setBalanceLoading] = useState(false);
140
- const balanceInFlightRef = useRef(false);
141
- const balanceLoadedRef = useRef(false);
142
- const optimisticUntilRef = useRef(0);
143
- const optimisticTimerRef = useRef(null);
144
- const fetchBalanceRef = useRef(null);
145
- const inFlightRef = useRef(null);
146
- const checkPurchaseRef = useRef(null);
147
- const createPaymentRef = useRef(null);
148
- const processPaymentRef = useRef(null);
149
- const createTopupPaymentRef = useRef(null);
150
- const configRef = useRef(config);
151
- const buildDefaultCheckPurchaseRef = useRef(
152
- null
153
- );
154
- useEffect(() => {
155
- configRef.current = config;
156
- }, [config]);
157
- useEffect(() => {
158
- checkPurchaseRef.current = customCheckPurchase || null;
159
- }, [customCheckPurchase]);
160
- useEffect(() => {
161
- createPaymentRef.current = customCreatePayment || null;
162
- }, [customCreatePayment]);
163
- useEffect(() => {
164
- processPaymentRef.current = customProcessPayment || null;
165
- }, [customProcessPayment]);
166
- useEffect(() => {
167
- createTopupPaymentRef.current = customCreateTopupPayment || null;
168
- }, [customCreateTopupPayment]);
169
- const buildDefaultCheckPurchase = useCallback(async () => {
170
- const currentConfig = configRef.current;
171
- const { headers } = await buildRequestHeaders(currentConfig);
172
- const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
173
- const fetchFn = currentConfig?.fetch || fetch;
174
- const res = await fetchFn(route, { method: "GET", headers });
175
- if (!res.ok) {
176
- const error = new Error(`Failed to check purchase: ${res.statusText}`);
177
- currentConfig?.onError?.(error, "checkPurchase");
178
- throw error;
179
- }
180
- return res.json();
181
- }, []);
182
- useEffect(() => {
183
- buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
184
- }, [buildDefaultCheckPurchase]);
185
- const buildDefaultCreatePayment = useCallback(
186
- async (params) => {
187
- const currentConfig = configRef.current;
188
- const { headers } = await buildRequestHeaders(currentConfig);
189
- const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
190
- const fetchFn = currentConfig?.fetch || fetch;
191
- const body = {};
192
- if (params.planRef) {
193
- body.planRef = params.planRef;
194
- }
195
- if (params.productRef) {
196
- body.productRef = params.productRef;
197
- }
198
- const res = await fetchFn(route, {
199
- method: "POST",
200
- headers,
201
- body: JSON.stringify(body)
202
- });
203
- if (!res.ok) {
204
- const error = new Error(`Failed to create payment: ${res.statusText}`);
205
- currentConfig?.onError?.(error, "createPayment");
206
- throw error;
207
- }
208
- return res.json();
209
- },
210
- []
211
- );
212
- const buildDefaultProcessPayment = useCallback(
213
- async (params) => {
214
- const currentConfig = configRef.current;
215
- const { headers } = await buildRequestHeaders(currentConfig);
216
- const route = currentConfig?.api?.processPayment || "/api/process-payment";
217
- const fetchFn = currentConfig?.fetch || fetch;
218
- const res = await fetchFn(route, {
219
- method: "POST",
220
- headers,
221
- body: JSON.stringify(params)
222
- });
223
- if (!res.ok) {
224
- const error = new Error(`Failed to process payment: ${res.statusText}`);
225
- currentConfig?.onError?.(error, "processPayment");
226
- throw error;
227
- }
228
- return res.json();
229
- },
230
- []
231
- );
232
- const buildDefaultCreateTopupPayment = useCallback(
233
- async (params) => {
234
- const currentConfig = configRef.current;
235
- const { headers } = await buildRequestHeaders(currentConfig);
236
- const route = currentConfig?.api?.createTopupPayment || "/api/create-topup-payment-intent";
237
- const fetchFn = currentConfig?.fetch || fetch;
238
- const res = await fetchFn(route, {
239
- method: "POST",
240
- headers,
241
- body: JSON.stringify({
242
- amount: params.amount,
243
- currency: params.currency
244
- })
245
- });
246
- if (!res.ok) {
247
- const error = new Error(`Failed to create topup payment: ${res.statusText}`);
248
- currentConfig?.onError?.(error, "createTopupPayment");
249
- throw error;
250
- }
251
- return res.json();
252
- },
253
- []
254
- );
255
- const fetchBalanceImpl = useCallback(async () => {
256
- if (optimisticUntilRef.current > Date.now()) return;
257
- if (!isAuthenticated && !internalCustomerRef) {
258
- setCreditsValue(null);
259
- setDisplayCurrencyValue(null);
260
- setCreditsPerMinorUnitValue(null);
261
- setDisplayExchangeRateValue(null);
262
- setBalanceLoading(false);
263
- balanceLoadedRef.current = false;
264
- return;
265
- }
266
- if (balanceInFlightRef.current) return;
267
- balanceInFlightRef.current = true;
268
- if (!balanceLoadedRef.current) {
269
- setBalanceLoading(true);
270
- }
271
- try {
272
- const currentConfig = configRef.current;
273
- const { headers } = await buildRequestHeaders(currentConfig);
274
- const route = currentConfig?.api?.customerBalance || "/api/customer-balance";
275
- const fetchFn = currentConfig?.fetch || fetch;
276
- const res = await fetchFn(route, { method: "GET", headers });
277
- if (!res.ok) {
278
- console.error("[SolvaPayProvider] Failed to fetch balance:", res.statusText);
279
- return;
280
- }
281
- const data = await res.json();
282
- setCreditsValue(data.credits ?? null);
283
- setDisplayCurrencyValue(data.displayCurrency ?? null);
284
- setCreditsPerMinorUnitValue(data.creditsPerMinorUnit ?? null);
285
- setDisplayExchangeRateValue(data.displayExchangeRate ?? null);
286
- balanceLoadedRef.current = true;
287
- } catch (error) {
288
- console.error("[SolvaPayProvider] Failed to fetch balance:", error);
289
- } finally {
290
- setBalanceLoading(false);
291
- balanceInFlightRef.current = false;
292
- }
293
- }, [isAuthenticated, internalCustomerRef]);
294
- useEffect(() => {
295
- fetchBalanceRef.current = fetchBalanceImpl;
296
- }, [fetchBalanceImpl]);
297
- useEffect(() => {
298
- return () => {
299
- if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
300
- };
301
- }, []);
302
- const OPTIMISTIC_GRACE_MS = 8e3;
303
- const adjustBalanceImpl = useCallback((credits) => {
304
- setCreditsValue((prev) => (prev ?? 0) + credits);
305
- optimisticUntilRef.current = Date.now() + OPTIMISTIC_GRACE_MS;
306
- if (optimisticTimerRef.current) clearTimeout(optimisticTimerRef.current);
307
- optimisticTimerRef.current = setTimeout(() => {
308
- optimisticUntilRef.current = 0;
309
- fetchBalanceRef.current?.();
310
- }, OPTIMISTIC_GRACE_MS);
311
- }, []);
312
- const _checkPurchase = useCallback(async () => {
313
- if (checkPurchaseRef.current) {
314
- return checkPurchaseRef.current();
315
- }
316
- if (buildDefaultCheckPurchaseRef.current) {
317
- return buildDefaultCheckPurchaseRef.current();
318
- }
319
- return buildDefaultCheckPurchase();
320
- }, [buildDefaultCheckPurchase]);
321
- const createPayment = useCallback(
322
- async (params) => {
323
- if (createPaymentRef.current) {
324
- return createPaymentRef.current(params);
325
- }
326
- return buildDefaultCreatePayment(params);
327
- },
328
- [buildDefaultCreatePayment]
329
- );
330
- const processPayment = useCallback(
331
- async (params) => {
332
- if (processPaymentRef.current) {
333
- return processPaymentRef.current(params);
334
- }
335
- return buildDefaultProcessPayment(params);
336
- },
337
- [buildDefaultProcessPayment]
338
- );
339
- const createTopupPayment = useCallback(
340
- async (params) => {
341
- if (createTopupPaymentRef.current) {
342
- return createTopupPaymentRef.current(params);
343
- }
344
- return buildDefaultCreateTopupPayment(params);
345
- },
346
- [buildDefaultCreateTopupPayment]
347
- );
348
- useEffect(() => {
349
- const detectAuth = async () => {
350
- const currentConfig = configRef.current;
351
- const adapter = getAuthAdapter(currentConfig);
352
- const token = await adapter.getToken();
353
- const detectedUserId = await adapter.getUserId();
354
- const prevUserId = userId;
355
- setIsAuthenticated(!!token);
356
- setUserId(detectedUserId);
357
- if (prevUserId !== null && detectedUserId !== prevUserId) {
358
- clearCachedCustomerRef();
359
- setInternalCustomerRef(void 0);
360
- return;
361
- }
362
- const cachedRef = getCachedCustomerRef(detectedUserId);
363
- if (cachedRef && token) {
364
- setInternalCustomerRef(cachedRef);
365
- } else if (!token) {
366
- clearCachedCustomerRef();
367
- setInternalCustomerRef(void 0);
368
- } else if (token && !cachedRef) {
369
- setInternalCustomerRef(void 0);
370
- }
371
- };
372
- detectAuth();
373
- const interval = setInterval(detectAuth, 3e4);
374
- return () => clearInterval(interval);
375
- }, [userId]);
376
- const fetchPurchase = useCallback(
377
- async (force = false) => {
378
- if (!isAuthenticated && !internalCustomerRef) {
379
- setPurchaseData({ purchases: [] });
380
- setPurchaseError(null);
381
- setLoading(false);
382
- setIsRefetching(false);
383
- inFlightRef.current = null;
384
- return;
385
- }
386
- const cacheKey = internalCustomerRef || userId || "anonymous";
387
- if (inFlightRef.current === cacheKey && !force) {
388
- return;
389
- }
390
- inFlightRef.current = cacheKey;
391
- const hasExistingData = purchaseData.purchases.length > 0;
392
- if (hasExistingData) {
393
- setIsRefetching(true);
394
- } else {
395
- setLoading(true);
396
- }
397
- try {
398
- const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
399
- if (!checkFn) {
400
- throw new Error("checkPurchase function not available");
401
- }
402
- const data = await checkFn();
403
- if (data.customerRef) {
404
- setInternalCustomerRef(data.customerRef);
405
- const currentAdapter = getAuthAdapter(configRef.current);
406
- const currentUserId = await currentAdapter.getUserId();
407
- setCachedCustomerRef(data.customerRef, currentUserId);
408
- }
409
- if (inFlightRef.current === cacheKey) {
410
- const filteredData = {
411
- ...data,
412
- purchases: filterPurchases(data.purchases || [])
413
- };
414
- setPurchaseData(filteredData);
415
- setPurchaseError(null);
416
- }
417
- } catch (err) {
418
- console.error("[SolvaPayProvider] Failed to fetch purchase:", err);
419
- if (inFlightRef.current === cacheKey) {
420
- setPurchaseError(err instanceof Error ? err : new Error(String(err)));
421
- }
422
- } finally {
423
- if (inFlightRef.current === cacheKey) {
424
- setLoading(false);
425
- setIsRefetching(false);
426
- inFlightRef.current = null;
427
- }
428
- }
429
- },
430
- [isAuthenticated, internalCustomerRef, userId, purchaseData.purchases.length]
431
- );
432
- const fetchPurchaseRef = useRef(fetchPurchase);
433
- useEffect(() => {
434
- fetchPurchaseRef.current = fetchPurchase;
435
- }, [fetchPurchase]);
436
- const refetchPurchase = useCallback(async () => {
437
- inFlightRef.current = null;
438
- await fetchPurchaseRef.current(true);
439
- }, []);
440
- const cancelRenewal = useCallback(
441
- async (params) => {
442
- const currentConfig = configRef.current;
443
- const { headers } = await buildRequestHeaders(currentConfig);
444
- const route = currentConfig?.api?.cancelRenewal || "/api/cancel-renewal";
445
- const fetchFn = currentConfig?.fetch || fetch;
446
- const res = await fetchFn(route, {
447
- method: "POST",
448
- headers,
449
- body: JSON.stringify(params)
450
- });
451
- if (!res.ok) {
452
- const data = await res.json().catch(() => ({}));
453
- const error = new Error(data.error || `Failed to cancel renewal: ${res.statusText}`);
454
- currentConfig?.onError?.(error, "cancelRenewal");
455
- throw error;
456
- }
457
- const result = await res.json();
458
- await refetchPurchase();
459
- return result;
460
- },
461
- [refetchPurchase]
462
- );
463
- const reactivateRenewal = useCallback(
464
- async (params) => {
465
- const currentConfig = configRef.current;
466
- const { headers } = await buildRequestHeaders(currentConfig);
467
- const route = currentConfig?.api?.reactivateRenewal || "/api/reactivate-renewal";
468
- const fetchFn = currentConfig?.fetch || fetch;
469
- const res = await fetchFn(route, {
470
- method: "POST",
471
- headers,
472
- body: JSON.stringify(params)
473
- });
474
- if (!res.ok) {
475
- const data = await res.json().catch(() => ({}));
476
- const error = new Error(data.error || `Failed to reactivate renewal: ${res.statusText}`);
477
- currentConfig?.onError?.(error, "reactivateRenewal");
478
- throw error;
479
- }
480
- const result = await res.json();
481
- await refetchPurchase();
482
- return result;
483
- },
484
- [refetchPurchase]
485
- );
486
- const activatePlan = useCallback(
487
- async (params) => {
488
- const currentConfig = configRef.current;
489
- const { headers } = await buildRequestHeaders(currentConfig);
490
- const route = currentConfig?.api?.activatePlan || "/api/activate-plan";
491
- const fetchFn = currentConfig?.fetch || fetch;
492
- const res = await fetchFn(route, {
493
- method: "POST",
494
- headers,
495
- body: JSON.stringify(params)
496
- });
497
- if (!res.ok) {
498
- const data = await res.json().catch(() => ({}));
499
- const error = new Error(data.error || `Failed to activate plan: ${res.statusText}`);
500
- currentConfig?.onError?.(error, "activatePlan");
501
- throw error;
502
- }
503
- const result = await res.json();
504
- if (result.status === "activated" || result.status === "already_active") {
505
- await refetchPurchase();
506
- }
507
- return result;
508
- },
509
- [refetchPurchase]
510
- );
511
- useEffect(() => {
512
- inFlightRef.current = null;
513
- if (isAuthenticated || internalCustomerRef) {
514
- fetchPurchase();
515
- } else {
516
- setPurchaseData({ purchases: [] });
517
- setPurchaseError(null);
518
- setLoading(false);
519
- setIsRefetching(false);
520
- }
521
- }, [isAuthenticated, internalCustomerRef, userId]);
522
- const updateCustomerRef = useCallback(
523
- (newCustomerRef) => {
524
- setInternalCustomerRef(newCustomerRef);
525
- setCachedCustomerRef(newCustomerRef, userId);
526
- fetchPurchase(true);
527
- },
528
- [fetchPurchase, userId]
529
- );
530
- const purchase = useMemo(() => {
531
- const activePurchase = getPrimaryPurchase(purchaseData.purchases);
532
- const activePaidPurchases = purchaseData.purchases.filter(
533
- (p) => p.status === "active" && isPaidPurchase(p)
534
- );
535
- const activePaidPurchase = activePaidPurchases.sort(
536
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
537
- )[0] || null;
538
- return {
539
- loading,
540
- isRefetching,
541
- error: purchaseError,
542
- customerRef: purchaseData.customerRef || internalCustomerRef,
543
- email: purchaseData.email,
544
- name: purchaseData.name,
545
- purchases: purchaseData.purchases,
546
- hasProduct: (productName) => {
547
- return purchaseData.purchases.some(
548
- (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
549
- );
550
- },
551
- hasPlan: (productName) => {
552
- return purchaseData.purchases.some(
553
- (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
554
- );
555
- },
556
- activePurchase,
557
- hasPaidPurchase: activePaidPurchases.length > 0,
558
- activePaidPurchase
559
- };
560
- }, [loading, isRefetching, purchaseError, purchaseData, internalCustomerRef]);
561
- const balance = useMemo(
562
- () => ({
563
- loading: balanceLoading,
564
- credits: creditsValue,
565
- displayCurrency: displayCurrencyValue,
566
- creditsPerMinorUnit: creditsPerMinorUnitValue,
567
- displayExchangeRate: displayExchangeRateValue,
568
- refetch: fetchBalanceImpl,
569
- adjustBalance: adjustBalanceImpl
570
- }),
571
- [balanceLoading, creditsValue, displayCurrencyValue, creditsPerMinorUnitValue, displayExchangeRateValue, fetchBalanceImpl, adjustBalanceImpl]
572
- );
573
- const contextValue = useMemo(
574
- () => ({
575
- purchase,
576
- refetchPurchase,
577
- createPayment,
578
- processPayment,
579
- createTopupPayment,
580
- cancelRenewal,
581
- reactivateRenewal,
582
- activatePlan,
583
- customerRef: purchaseData.customerRef || internalCustomerRef,
584
- updateCustomerRef,
585
- balance,
586
- _config: configRef.current
587
- }),
588
- [
589
- purchase,
590
- refetchPurchase,
591
- createPayment,
592
- processPayment,
593
- createTopupPayment,
594
- cancelRenewal,
595
- reactivateRenewal,
596
- activatePlan,
597
- purchaseData.customerRef,
598
- internalCustomerRef,
599
- updateCustomerRef,
600
- balance
601
- ]
602
- );
603
- return /* @__PURE__ */ jsx(SolvaPayContext.Provider, { value: contextValue, children });
604
- };
88
+ import "./chunk-MLKGABMK.js";
605
89
 
606
90
  // src/PaymentForm.tsx
607
- import { useEffect as useEffect2, useCallback as useCallback3, useRef as useRef3, useMemo as useMemo2, useState as useState4 } from "react";
608
- import { Elements } from "@stripe/react-stripe-js";
609
-
610
- // src/hooks/useCheckout.ts
611
- import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
612
- import { loadStripe } from "@stripe/stripe-js";
613
-
614
- // src/hooks/useSolvaPay.ts
615
- import { useContext } from "react";
616
- function useSolvaPay() {
617
- const context = useContext(SolvaPayContext);
618
- if (!context) {
619
- throw new Error(
620
- "useSolvaPay must be used within a SolvaPayProvider. Wrap your component tree with <SolvaPayProvider> to use this hook."
621
- );
622
- }
623
- return context;
624
- }
625
-
626
- // src/hooks/useCheckout.ts
627
- var stripePromiseCache = /* @__PURE__ */ new Map();
628
- function getStripeCacheKey(publishableKey, accountId) {
629
- return accountId ? `${publishableKey}:${accountId}` : publishableKey;
630
- }
631
- async function resolvePlanRef(productRef, fetchFn, headers, listPlansRoute) {
632
- const url = `${listPlansRoute}?productRef=${encodeURIComponent(productRef)}`;
633
- const res = await fetchFn(url, { method: "GET", headers });
634
- if (!res.ok) {
635
- throw new Error(`Failed to fetch plans for product "${productRef}": ${res.statusText}`);
636
- }
637
- const data = await res.json();
638
- const allPlans = data.plans ?? [];
639
- const activePlans = allPlans.filter((p) => p.isActive !== false && p.status !== "inactive");
640
- if (activePlans.length === 0) {
641
- throw new Error(
642
- `No active plans found for product "${productRef}". Configure at least one plan in the SolvaPay Console.`
643
- );
644
- }
645
- if (activePlans.length === 1) {
646
- return activePlans[0].reference;
647
- }
648
- const defaultPlan = activePlans.find((p) => p.default === true);
649
- if (defaultPlan) {
650
- return defaultPlan.reference;
651
- }
652
- throw new Error(
653
- `Product "${productRef}" has ${activePlans.length} active plans but none is marked as default. Either pass planRef explicitly, use <PricingSelector> for user selection, or mark one plan as default in the SolvaPay Console.`
654
- );
655
- }
656
- function useCheckout(options) {
657
- const { planRef, productRef } = options;
658
- const { createPayment, customerRef, updateCustomerRef, _config } = useSolvaPay();
659
- const [loading, setLoading] = useState2(false);
660
- const [error, setError] = useState2(null);
661
- const [stripePromise, setStripePromise] = useState2(null);
662
- const [clientSecret, setClientSecret] = useState2(null);
663
- const [resolvedPlanRef, setResolvedPlanRef] = useState2(planRef || null);
664
- const isStartingRef = useRef2(false);
665
- const startCheckout = useCallback2(async () => {
666
- if (isStartingRef.current || loading) {
667
- return;
668
- }
669
- if (!planRef && !productRef) {
670
- setError(
671
- new Error(
672
- "useCheckout: either planRef or productRef is required. Pass planRef directly, or pass productRef to auto-resolve the plan."
673
- )
674
- );
675
- return;
676
- }
677
- isStartingRef.current = true;
678
- setLoading(true);
679
- setError(null);
680
- try {
681
- let effectivePlanRef = planRef;
682
- if (!effectivePlanRef && productRef) {
683
- const listPlansRoute = _config?.api?.listPlans || "/api/list-plans";
684
- effectivePlanRef = await resolvePlanRef(productRef, fetch, {}, listPlansRoute);
685
- setResolvedPlanRef(effectivePlanRef);
686
- }
687
- if (!effectivePlanRef) {
688
- throw new Error("Could not determine plan reference for checkout");
689
- }
690
- const result = await createPayment({ planRef: effectivePlanRef, productRef });
691
- if (!result || typeof result !== "object") {
692
- throw new Error("Invalid payment intent response from server");
693
- }
694
- if (!result.clientSecret || typeof result.clientSecret !== "string") {
695
- throw new Error("Invalid client secret in payment intent response");
696
- }
697
- if (!result.publishableKey || typeof result.publishableKey !== "string") {
698
- throw new Error("Invalid publishable key in payment intent response");
699
- }
700
- if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
701
- updateCustomerRef(result.customerRef);
702
- }
703
- const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
704
- const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
705
- let stripe = stripePromiseCache.get(cacheKey);
706
- if (!stripe) {
707
- stripe = loadStripe(result.publishableKey, stripeOptions);
708
- stripePromiseCache.set(cacheKey, stripe);
709
- }
710
- setStripePromise(stripe);
711
- setClientSecret(result.clientSecret);
712
- } catch (err) {
713
- const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
714
- setError(error2);
715
- } finally {
716
- setLoading(false);
717
- isStartingRef.current = false;
718
- }
719
- }, [planRef, productRef, createPayment, updateCustomerRef, loading, _config]);
720
- const reset = useCallback2(() => {
721
- isStartingRef.current = false;
722
- setLoading(false);
723
- setError(null);
724
- setStripePromise(null);
725
- setClientSecret(null);
726
- setResolvedPlanRef(planRef || null);
727
- }, [planRef]);
728
- return {
729
- loading,
730
- error,
731
- stripePromise,
732
- clientSecret,
733
- resolvedPlanRef,
734
- startCheckout,
735
- reset
736
- };
737
- }
738
-
739
- // src/hooks/usePurchase.ts
740
- function usePurchase() {
741
- const { purchase, refetchPurchase } = useSolvaPay();
742
- return {
743
- ...purchase,
744
- refetch: refetchPurchase
745
- };
746
- }
747
-
748
- // src/components/Spinner.tsx
749
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
750
- var Spinner = ({
751
- className = "",
752
- size = "md"
753
- }) => {
754
- const sizeClasses = {
755
- sm: "h-4 w-4",
756
- md: "h-5 w-5",
757
- lg: "h-6 w-6"
758
- };
759
- return /* @__PURE__ */ jsxs(
760
- "svg",
761
- {
762
- className: `animate-spin ${sizeClasses[size]} ${className}`,
763
- xmlns: "http://www.w3.org/2000/svg",
764
- fill: "none",
765
- viewBox: "0 0 24 24",
766
- role: "status",
767
- "aria-busy": "true",
768
- children: [
769
- /* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
770
- /* @__PURE__ */ jsx2(
771
- "path",
772
- {
773
- className: "opacity-75",
774
- fill: "currentColor",
775
- d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
776
- }
777
- )
778
- ]
779
- }
780
- );
91
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
92
+ var DefaultTree = ({
93
+ requireTermsAcceptance
94
+ }) => /* @__PURE__ */ jsxs(Fragment, { children: [
95
+ /* @__PURE__ */ jsx(PaymentForm.Summary, {}),
96
+ /* @__PURE__ */ jsx(PaymentForm.CustomerFields, {}),
97
+ /* @__PURE__ */ jsx(PaymentForm.PaymentElement, {}),
98
+ /* @__PURE__ */ jsx(PaymentForm.Error, {}),
99
+ /* @__PURE__ */ jsx(PaymentForm.MandateText, {}),
100
+ requireTermsAcceptance && /* @__PURE__ */ jsx(PaymentForm.TermsCheckbox, {}),
101
+ /* @__PURE__ */ jsx(PaymentForm.SubmitButton, {})
102
+ ] });
103
+ var PaymentFormBase = (props) => {
104
+ const { children, requireTermsAcceptance = false } = props;
105
+ return /* @__PURE__ */ jsx(PaymentForm.Root, { ...props, children: children ?? /* @__PURE__ */ jsx(DefaultTree, { requireTermsAcceptance }) });
781
106
  };
107
+ var PaymentForm2 = Object.assign(PaymentFormBase, {
108
+ Summary: PaymentFormSummary,
109
+ CustomerFields: PaymentFormCustomerFields,
110
+ PaymentElement: PaymentFormPaymentElement,
111
+ CardElement: PaymentFormCardElement,
112
+ MandateText: PaymentFormMandateText,
113
+ TermsCheckbox: PaymentFormTermsCheckbox,
114
+ SubmitButton: PaymentFormSubmitButton,
115
+ Loading: PaymentFormLoading,
116
+ Error: PaymentFormError
117
+ });
782
118
 
783
119
  // src/components/StripePaymentFormWrapper.tsx
784
- import { useState as useState3 } from "react";
120
+ import { useState } from "react";
785
121
  import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
786
-
787
- // src/hooks/useCustomer.ts
788
- function useCustomer() {
789
- const { purchase, customerRef } = useSolvaPay();
790
- return {
791
- customerRef: purchase.customerRef || customerRef,
792
- email: purchase.email,
793
- name: purchase.name,
794
- loading: purchase.loading
795
- };
796
- }
797
-
798
- // src/components/StripePaymentFormWrapper.tsx
799
- import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
122
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
800
123
  var StripePaymentFormWrapper = ({
801
124
  onSuccess,
802
125
  onError,
803
126
  returnUrl: _returnUrl,
804
- submitButtonText = "Pay Now",
127
+ submitButtonText,
805
128
  buttonClassName,
806
129
  clientSecret
807
130
  }) => {
808
131
  const stripe = useStripe();
809
132
  const elements = useElements();
810
133
  const customer = useCustomer();
811
- const [isProcessing, setIsProcessing] = useState3(false);
812
- const [message, setMessage] = useState3(null);
813
- const [cardComplete, setCardComplete] = useState3(false);
134
+ const copy = useCopy();
135
+ const effectiveSubmitText = submitButtonText ?? copy.cta.payNow;
136
+ const [isProcessing, setIsProcessing] = useState(false);
137
+ const [message, setMessage] = useState(null);
138
+ const [cardComplete, setCardComplete] = useState(false);
814
139
  const handleSubmit = async (event) => {
815
140
  event.preventDefault();
816
141
  if (!stripe || !elements) {
817
- const errorMessage = "Stripe is not available. Please refresh the page.";
142
+ const errorMessage = copy.errors.stripeUnavailable;
818
143
  setMessage(errorMessage);
819
144
  if (onError) {
820
145
  onError(new Error(errorMessage));
@@ -822,7 +147,7 @@ var StripePaymentFormWrapper = ({
822
147
  return;
823
148
  }
824
149
  if (!clientSecret) {
825
- const errorMessage = "Payment intent not available. Please refresh the page.";
150
+ const errorMessage = copy.errors.paymentIntentUnavailable;
826
151
  setMessage(errorMessage);
827
152
  if (onError) {
828
153
  onError(new Error(errorMessage));
@@ -834,7 +159,7 @@ var StripePaymentFormWrapper = ({
834
159
  try {
835
160
  const cardElement = elements.getElement(CardElement);
836
161
  if (!cardElement) {
837
- const errorMessage = "Card element not found";
162
+ const errorMessage = copy.errors.cardElementMissing;
838
163
  setMessage(errorMessage);
839
164
  setIsProcessing(false);
840
165
  if (onError) {
@@ -852,7 +177,7 @@ var StripePaymentFormWrapper = ({
852
177
  }
853
178
  });
854
179
  if (error) {
855
- const errorMessage = error.message || "An unexpected error occurred.";
180
+ const errorMessage = error.message || copy.errors.paymentUnexpected;
856
181
  setMessage(errorMessage);
857
182
  setIsProcessing(false);
858
183
  if (onError) {
@@ -865,8 +190,8 @@ var StripePaymentFormWrapper = ({
865
190
  await onSuccess(paymentIntent);
866
191
  setMessage("Payment successful!");
867
192
  } catch (err) {
868
- const error2 = err instanceof Error ? err : new Error("Payment processing failed");
869
- setMessage("Payment processing failed. Please try again or contact support.");
193
+ const error2 = err instanceof Error ? err : new Error(copy.errors.paymentProcessingFailed);
194
+ setMessage(copy.errors.paymentProcessingFailed);
870
195
  setIsProcessing(false);
871
196
  if (onError) {
872
197
  onError(error2);
@@ -877,14 +202,18 @@ var StripePaymentFormWrapper = ({
877
202
  setMessage("Payment successful!");
878
203
  }
879
204
  } else if (paymentIntent && paymentIntent.status === "requires_action") {
880
- setMessage("Payment requires additional authentication. Please complete the verification.");
205
+ setMessage(copy.errors.paymentRequires3ds);
881
206
  setIsProcessing(false);
882
207
  } else if (paymentIntent) {
883
- setMessage(`Payment status: ${paymentIntent.status || "processing"}`);
208
+ setMessage(
209
+ interpolate(copy.errors.paymentStatusPrefix, {
210
+ status: paymentIntent.status || "processing"
211
+ })
212
+ );
884
213
  setIsProcessing(false);
885
214
  }
886
215
  } catch (err) {
887
- const error = err instanceof Error ? err : new Error("Unknown error occurred");
216
+ const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
888
217
  setMessage(error.message);
889
218
  if (onError) {
890
219
  onError(error);
@@ -912,8 +241,8 @@ var StripePaymentFormWrapper = ({
912
241
  },
913
242
  hidePostalCode: true
914
243
  };
915
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
916
- isReady ? /* @__PURE__ */ jsx3(
244
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
245
+ isReady ? /* @__PURE__ */ jsx2(
917
246
  CardElement,
918
247
  {
919
248
  options: cardElementOptions,
@@ -927,9 +256,9 @@ var StripePaymentFormWrapper = ({
927
256
  }
928
257
  }
929
258
  }
930
- ) : /* @__PURE__ */ jsx3("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ jsx3(Spinner, { size: "sm" }) }),
931
- message && !isSuccess && /* @__PURE__ */ jsx3("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
932
- /* @__PURE__ */ jsx3(
259
+ ) : /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ jsx2(Spinner, { size: "sm" }) }),
260
+ message && !isSuccess && /* @__PURE__ */ jsx2("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
261
+ /* @__PURE__ */ jsx2(
933
262
  "button",
934
263
  {
935
264
  type: "submit",
@@ -938,940 +267,544 @@ var StripePaymentFormWrapper = ({
938
267
  "aria-busy": isProcessing,
939
268
  "aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
940
269
  onClick: handleSubmit,
941
- children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
942
- /* @__PURE__ */ jsx3(Spinner, { size: "sm" }),
943
- /* @__PURE__ */ jsx3("span", { children: "Processing..." })
944
- ] }) : submitButtonText
270
+ children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
271
+ /* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
272
+ /* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
273
+ ] }) : effectiveSubmitText
945
274
  }
946
275
  )
947
276
  ] });
948
277
  };
949
278
 
950
- // src/PaymentForm.tsx
951
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
952
- var PaymentForm = ({
953
- planRef,
954
- productRef,
955
- onSuccess,
956
- onError,
957
- returnUrl,
958
- submitButtonText = "Pay Now",
959
- className,
960
- buttonClassName
961
- }) => {
962
- const {
963
- loading: checkoutLoading,
964
- error: checkoutError,
965
- clientSecret,
966
- startCheckout,
967
- stripePromise,
968
- resolvedPlanRef
969
- } = useCheckout({ planRef, productRef });
970
- const { refetch } = usePurchase();
971
- const { processPayment } = useSolvaPay();
972
- const hasInitializedRef = useRef3(false);
973
- const hasPlanOrProduct = !!(planRef || productRef);
974
- useEffect2(() => {
975
- if (!hasInitializedRef.current && hasPlanOrProduct && !checkoutLoading && !checkoutError && !clientSecret) {
976
- hasInitializedRef.current = true;
977
- startCheckout().catch(() => {
978
- hasInitializedRef.current = false;
979
- });
980
- }
981
- if (hasPlanOrProduct && clientSecret) {
982
- hasInitializedRef.current = true;
983
- }
984
- }, [hasPlanOrProduct, checkoutLoading, checkoutError, clientSecret, startCheckout]);
985
- const effectivePlanRef = planRef || resolvedPlanRef;
986
- const handleSuccess = useCallback3(
987
- async (paymentIntent) => {
988
- const paymentIntentAny = paymentIntent;
989
- if (processPayment && productRef) {
990
- try {
991
- const result = await processPayment({
992
- paymentIntentId: paymentIntentAny.id,
993
- productRef,
994
- planRef: effectivePlanRef || void 0
995
- });
996
- const isTimeout = result?.status === "timeout";
997
- if (isTimeout) {
998
- for (let attempt = 1; attempt <= 5; attempt++) {
999
- await new Promise((resolve) => setTimeout(resolve, attempt * 1e3));
1000
- await refetch();
1001
- }
1002
- const err = new Error("Payment processing timed out \u2014 webhooks may not be configured");
1003
- onError?.(err);
1004
- return;
1005
- }
1006
- await refetch();
1007
- } catch (error) {
1008
- console.error("[PaymentForm] Failed to process payment:", error);
1009
- onError?.(error instanceof Error ? error : new Error(String(error)));
1010
- return;
1011
- }
1012
- } else {
1013
- await refetch();
1014
- }
1015
- onSuccess?.(paymentIntentAny);
1016
- },
1017
- [processPayment, productRef, effectivePlanRef, refetch, onSuccess, onError]
1018
- );
1019
- const handleError = useCallback3(
1020
- (err) => {
1021
- if (onError) {
1022
- onError(err);
1023
- }
1024
- },
1025
- [onError]
1026
- );
1027
- const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
1028
- const hasError = !!checkoutError;
1029
- const hasStripeData = !!(stripePromise && clientSecret);
1030
- const elementsOptions = useMemo2(() => {
1031
- if (!clientSecret) return void 0;
1032
- return { clientSecret };
1033
- }, [clientSecret]);
1034
- const [hasMountedElements, setHasMountedElements] = useState4(false);
1035
- useEffect2(() => {
1036
- if (hasStripeData) {
1037
- setHasMountedElements(true);
1038
- }
1039
- }, [hasStripeData]);
1040
- const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
1041
- return /* @__PURE__ */ jsx4("div", { className, children: !hasPlanOrProduct ? /* @__PURE__ */ jsx4("div", { children: "PaymentForm: either planRef or productRef is required" }) : hasError ? /* @__PURE__ */ jsxs3("div", { children: [
1042
- /* @__PURE__ */ jsx4("div", { children: "Payment initialization failed" }),
1043
- /* @__PURE__ */ jsx4("div", { children: checkoutError?.message || "Unknown error" })
1044
- ] }) : shouldRenderElements && elementsOptions ? /* @__PURE__ */ jsx4(
1045
- Elements,
1046
- {
1047
- stripe: stripePromise,
1048
- options: elementsOptions,
1049
- children: /* @__PURE__ */ jsx4(
1050
- StripePaymentFormWrapper,
1051
- {
1052
- onSuccess: handleSuccess,
1053
- onError: handleError,
1054
- returnUrl: finalReturnUrl,
1055
- submitButtonText,
1056
- buttonClassName,
1057
- clientSecret
1058
- }
1059
- )
1060
- },
1061
- clientSecret
1062
- ) : /* @__PURE__ */ jsxs3("div", { children: [
279
+ // src/components/CheckoutLayout.tsx
280
+ import { useCallback, useContext, useEffect, useMemo, useRef, useState as useState2 } from "react";
281
+
282
+ // src/components/PlanSelector.tsx
283
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
284
+ var DefaultTree2 = () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
285
+ /* @__PURE__ */ jsx3(PlanSelector.Heading, { className: "solvapay-plan-selector-heading" }),
286
+ /* @__PURE__ */ jsx3(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ jsxs3(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
287
+ /* @__PURE__ */ jsx3(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
288
+ /* @__PURE__ */ jsx3(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
289
+ /* @__PURE__ */ jsx3(PlanSelector.CardPrice, { className: "solvapay-plan-selector-card-price" }),
290
+ /* @__PURE__ */ jsx3(PlanSelector.CardInterval, { className: "solvapay-plan-selector-card-interval" })
291
+ ] }) }),
292
+ /* @__PURE__ */ jsx3(PlanSelector.Loading, { className: "solvapay-plan-selector-loading" }),
293
+ /* @__PURE__ */ jsx3(PlanSelector.Error, { className: "solvapay-plan-selector-error" })
294
+ ] });
295
+ var PlanSelector2 = (props) => {
296
+ const { children, className, ...rootProps } = props;
297
+ const rootClass = ["solvapay-plan-selector", className].filter(Boolean).join(" ");
298
+ return /* @__PURE__ */ jsx3(PlanSelector.Root, { ...rootProps, className: rootClass, children: children ?? /* @__PURE__ */ jsx3(DefaultTree2, {}) });
299
+ };
300
+
301
+ // src/components/ActivationFlow.tsx
302
+ import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
303
+ var ActivationFlow2 = (props) => {
304
+ const { className, onBack, ...rootProps } = props;
305
+ const rootClass = ["solvapay-activation-flow", className].filter(Boolean).join(" ");
306
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Root, { ...rootProps, className: rootClass, children: [
307
+ /* @__PURE__ */ jsx4(SummaryStep, {}),
308
+ /* @__PURE__ */ jsx4(AmountStep, {}),
309
+ /* @__PURE__ */ jsx4(TopupPaymentStep, {}),
310
+ /* @__PURE__ */ jsx4(RetryingStep, {}),
311
+ /* @__PURE__ */ jsx4(ActivatedStep, {}),
312
+ /* @__PURE__ */ jsx4(ErrorStep, {}),
313
+ onBack && /* @__PURE__ */ jsx4(BackButton, { onBack })
314
+ ] });
315
+ };
316
+ var SummaryStep = () => {
317
+ const copy = useCopy();
318
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Summary, { className: "solvapay-activation-flow-summary", children: [
319
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.heading }),
320
+ /* @__PURE__ */ jsx4(CheckoutSummary, {}),
321
+ /* @__PURE__ */ jsx4(ActivationFlow.ActivateButton, { className: "solvapay-activation-flow-activate" })
322
+ ] });
323
+ };
324
+ var AmountStep = () => {
325
+ const copy = useCopy();
326
+ return /* @__PURE__ */ jsxs4(ActivationFlow.AmountPicker, { children: [
327
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-topup-heading", children: copy.activationFlow.topupHeading }),
328
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-activation-flow-topup-subheading", children: copy.activationFlow.topupSubheading }),
329
+ /* @__PURE__ */ jsx4(AmountPickerBody, {}),
330
+ /* @__PURE__ */ jsx4(ActivationFlow.ContinueButton, { className: "solvapay-activation-flow-continue" })
331
+ ] });
332
+ };
333
+ var AmountPickerBody = () => {
334
+ const ctx = useAmountPicker();
335
+ const { selectAmountLabel, customAmountLabel } = useAmountPickerCopy();
336
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
337
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
338
+ /* @__PURE__ */ jsx4("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx4(
339
+ AmountPicker.Option,
340
+ {
341
+ amount,
342
+ className: "solvapay-amount-picker-pill"
343
+ },
344
+ amount
345
+ )) }),
346
+ /* @__PURE__ */ jsxs4("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
347
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
348
+ /* @__PURE__ */ jsx4(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
349
+ ] })
350
+ ] });
351
+ };
352
+ var TopupPaymentStep = () => {
353
+ const ctx = useActivationFlow();
354
+ const copy = useCopy();
355
+ if (ctx.step !== "topupPayment") return null;
356
+ return /* @__PURE__ */ jsxs4("div", { "data-solvapay-activation-flow-topup-payment": "", children: [
1063
357
  /* @__PURE__ */ jsx4(
1064
- "div",
358
+ "button",
1065
359
  {
1066
- style: {
1067
- minHeight: "40px",
1068
- display: "flex",
1069
- alignItems: "center",
1070
- justifyContent: "center"
1071
- },
1072
- children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
360
+ type: "button",
361
+ onClick: ctx.backToSelectAmount,
362
+ className: "solvapay-activation-flow-change-amount",
363
+ children: copy.activationFlow.changeAmountButton
1073
364
  }
1074
365
  ),
1075
366
  /* @__PURE__ */ jsx4(
1076
- "button",
367
+ TopupForm,
1077
368
  {
1078
- type: "submit",
1079
- disabled: true,
1080
- className: buttonClassName,
1081
- "aria-busy": "false",
1082
- "aria-disabled": "true",
1083
- children: submitButtonText
369
+ amount: ctx.amountMinor,
370
+ currency: ctx.currency,
371
+ onSuccess: ctx.onTopupSuccess,
372
+ onError: (err) => ctx.onError?.(err instanceof Error ? err : new Error(String(err)))
1084
373
  }
1085
374
  )
1086
- ] }) });
375
+ ] });
1087
376
  };
1088
-
1089
- // src/TopupForm.tsx
1090
- import { useEffect as useEffect3, useCallback as useCallback5, useRef as useRef5, useMemo as useMemo3 } from "react";
1091
- import { Elements as Elements2 } from "@stripe/react-stripe-js";
1092
-
1093
- // src/hooks/useTopup.ts
1094
- import { useState as useState5, useCallback as useCallback4, useRef as useRef4 } from "react";
1095
- import { loadStripe as loadStripe2 } from "@stripe/stripe-js";
1096
- var stripePromiseCache2 = /* @__PURE__ */ new Map();
1097
- function getStripeCacheKey2(publishableKey, accountId) {
1098
- return accountId ? `${publishableKey}:${accountId}` : publishableKey;
1099
- }
1100
- function useTopup(options) {
1101
- const { amount, currency } = options;
1102
- const { createTopupPayment, customerRef, updateCustomerRef } = useSolvaPay();
1103
- const [loading, setLoading] = useState5(false);
1104
- const [error, setError] = useState5(null);
1105
- const [stripePromise, setStripePromise] = useState5(null);
1106
- const [clientSecret, setClientSecret] = useState5(null);
1107
- const isStartingRef = useRef4(false);
1108
- const startTopup = useCallback4(async () => {
1109
- if (isStartingRef.current || loading) {
1110
- return;
1111
- }
1112
- if (!amount || amount <= 0) {
1113
- setError(new Error("useTopup: amount must be a positive number"));
1114
- return;
1115
- }
1116
- isStartingRef.current = true;
1117
- setLoading(true);
1118
- setError(null);
1119
- try {
1120
- const result = await createTopupPayment({ amount, currency });
1121
- if (!result || typeof result !== "object") {
1122
- throw new Error("Invalid topup payment intent response from server");
1123
- }
1124
- if (!result.clientSecret || typeof result.clientSecret !== "string") {
1125
- throw new Error("Invalid client secret in topup payment intent response");
1126
- }
1127
- if (!result.publishableKey || typeof result.publishableKey !== "string") {
1128
- throw new Error("Invalid publishable key in topup payment intent response");
1129
- }
1130
- if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
1131
- updateCustomerRef(result.customerRef);
1132
- }
1133
- const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
1134
- const cacheKey = getStripeCacheKey2(result.publishableKey, result.accountId);
1135
- let stripe = stripePromiseCache2.get(cacheKey);
1136
- if (!stripe) {
1137
- stripe = loadStripe2(result.publishableKey, stripeOptions);
1138
- stripePromiseCache2.set(cacheKey, stripe);
377
+ var RetryingStep = () => {
378
+ const copy = useCopy();
379
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Retrying, { className: "solvapay-activation-flow-retrying", children: [
380
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.retryingHeading }),
381
+ /* @__PURE__ */ jsx4("p", { children: copy.activationFlow.retryingSubheading })
382
+ ] });
383
+ };
384
+ var ActivatedStep = () => {
385
+ const copy = useCopy();
386
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Activated, { className: "solvapay-activation-flow-activated", children: [
387
+ /* @__PURE__ */ jsx4("h3", { children: copy.activationFlow.activatedHeading }),
388
+ /* @__PURE__ */ jsx4("p", { children: copy.activationFlow.activatedSubheading })
389
+ ] });
390
+ };
391
+ var ErrorStep = () => {
392
+ const ctx = useActivationFlow();
393
+ const copy = useCopy();
394
+ if (ctx.step !== "error") return null;
395
+ return /* @__PURE__ */ jsxs4("div", { className: "solvapay-activation-flow-error", role: "alert", children: [
396
+ /* @__PURE__ */ jsx4("p", { children: ctx.error }),
397
+ /* @__PURE__ */ jsx4(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ onClick: ctx.reset,
402
+ className: "solvapay-activation-flow-try-again",
403
+ children: copy.activationFlow.tryAgainButton
1139
404
  }
1140
- setStripePromise(stripe);
1141
- setClientSecret(result.clientSecret);
1142
- } catch (err) {
1143
- const error2 = err instanceof Error ? err : new Error("Failed to start topup");
1144
- setError(error2);
1145
- } finally {
1146
- setLoading(false);
1147
- isStartingRef.current = false;
405
+ )
406
+ ] });
407
+ };
408
+ var BackButton = ({ onBack }) => {
409
+ const ctx = useActivationFlow();
410
+ const copy = useCopy();
411
+ if (ctx.step !== "summary") return null;
412
+ return /* @__PURE__ */ jsx4(
413
+ "button",
414
+ {
415
+ type: "button",
416
+ onClick: onBack,
417
+ className: "solvapay-activation-flow-back",
418
+ children: copy.activationFlow.backButton
1148
419
  }
1149
- }, [amount, currency, createTopupPayment, customerRef, updateCustomerRef, loading]);
1150
- const reset = useCallback4(() => {
1151
- isStartingRef.current = false;
1152
- setLoading(false);
1153
- setError(null);
1154
- setStripePromise(null);
1155
- setClientSecret(null);
1156
- }, []);
1157
- return {
1158
- loading,
1159
- error,
1160
- stripePromise,
1161
- clientSecret,
1162
- startTopup,
1163
- reset
1164
- };
1165
- }
420
+ );
421
+ };
1166
422
 
1167
- // src/TopupForm.tsx
1168
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1169
- var TopupForm = ({
1170
- amount,
1171
- currency,
423
+ // src/components/CheckoutLayout.tsx
424
+ import { Fragment as Fragment5, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
425
+ var BREAKPOINTS = {
426
+ chat: 480,
427
+ desktop: 768
428
+ };
429
+ function widthToSize(width) {
430
+ if (width < BREAKPOINTS.chat) return "chat";
431
+ if (width < BREAKPOINTS.desktop) return "mobile";
432
+ return "desktop";
433
+ }
434
+ var CheckoutLayout = ({
435
+ planRef,
436
+ productRef,
437
+ prefillCustomer,
438
+ size = "auto",
439
+ requireTermsAcceptance,
1172
440
  onSuccess,
441
+ onResult,
442
+ onFreePlan,
1173
443
  onError,
1174
- returnUrl,
1175
- submitButtonText = "Top Up",
1176
- className,
1177
- buttonClassName
444
+ initialPlanRef,
445
+ onPlanSelect,
446
+ planSelector,
447
+ showBackButton = true,
448
+ submitButtonText,
449
+ returnUrl
1178
450
  }) => {
1179
- const {
1180
- loading: topupLoading,
1181
- error: topupError,
1182
- clientSecret,
1183
- startTopup,
1184
- stripePromise
1185
- } = useTopup({ amount, currency });
1186
- const hasInitializedRef = useRef5(false);
1187
- const hasAmount = amount > 0;
1188
- useEffect3(() => {
1189
- if (!hasInitializedRef.current && hasAmount && !topupLoading && !topupError && !clientSecret) {
1190
- hasInitializedRef.current = true;
1191
- startTopup().catch(() => {
1192
- hasInitializedRef.current = false;
1193
- });
1194
- }
1195
- if (hasAmount && clientSecret) {
1196
- hasInitializedRef.current = true;
1197
- }
1198
- }, [hasAmount, topupLoading, topupError, clientSecret, startTopup]);
1199
- const handleSuccess = useCallback5(
1200
- async (paymentIntent) => {
1201
- if (onSuccess) {
1202
- await onSuccess(paymentIntent);
1203
- }
1204
- },
1205
- [onSuccess]
1206
- );
1207
- const handleError = useCallback5(
1208
- (err) => {
1209
- if (onError) {
1210
- onError(err);
451
+ const rootRef = useRef(null);
452
+ const [autoSize, setAutoSize] = useState2("desktop");
453
+ const copy = useCopy();
454
+ useEffect(() => {
455
+ if (size !== "auto") return;
456
+ const el = rootRef.current;
457
+ if (!el || typeof ResizeObserver === "undefined") return;
458
+ const ro = new ResizeObserver((entries) => {
459
+ for (const entry of entries) {
460
+ setAutoSize(widthToSize(entry.contentRect.width));
1211
461
  }
462
+ });
463
+ ro.observe(el);
464
+ return () => ro.disconnect();
465
+ }, [size]);
466
+ const resolvedSize = size === "auto" ? autoSize : size;
467
+ const [selectedPlanRef, setSelectedPlanRef] = useState2(planRef ?? null);
468
+ const [userStep, setUserStep] = useState2(planRef ? "pay" : "select");
469
+ const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
470
+ const { plan: resolvedPlan } = usePlan({
471
+ planRef: effectivePlanRef,
472
+ productRef
473
+ });
474
+ const isUsageBased = resolvedPlan?.type === "usage-based";
475
+ const step = useMemo(() => {
476
+ if (userStep === "pay" && isUsageBased) return "activate";
477
+ if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
478
+ return userStep;
479
+ }, [userStep, isUsageBased, resolvedPlan]);
480
+ const handlePlanSelect = useCallback(
481
+ (ref, plan) => {
482
+ setSelectedPlanRef(ref);
483
+ onPlanSelect?.(ref, plan);
484
+ setUserStep(plan.type === "usage-based" ? "activate" : "pay");
1212
485
  },
1213
- [onError]
486
+ [onPlanSelect]
1214
487
  );
1215
- const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
1216
- const hasError = !!topupError;
1217
- const hasStripeData = !!(stripePromise && clientSecret);
1218
- const elementsOptions = useMemo3(() => {
1219
- if (!clientSecret) return void 0;
1220
- return { clientSecret };
1221
- }, [clientSecret]);
1222
- return /* @__PURE__ */ jsx5("div", { className, children: !hasAmount ? /* @__PURE__ */ jsx5("div", { children: "TopupForm: amount must be a positive number" }) : hasError ? /* @__PURE__ */ jsxs4("div", { children: [
1223
- /* @__PURE__ */ jsx5("div", { children: "Top-up initialization failed" }),
1224
- /* @__PURE__ */ jsx5("div", { children: topupError?.message || "Unknown error" })
1225
- ] }) : hasStripeData && elementsOptions ? /* @__PURE__ */ jsx5(
1226
- Elements2,
488
+ const handleBack = useCallback(() => {
489
+ if (planRef) return;
490
+ setUserStep("select");
491
+ }, [planRef]);
492
+ return /* @__PURE__ */ jsxs5(
493
+ "div",
1227
494
  {
1228
- stripe: stripePromise,
1229
- options: elementsOptions,
1230
- children: /* @__PURE__ */ jsx5(
1231
- StripePaymentFormWrapper,
1232
- {
1233
- onSuccess: handleSuccess,
1234
- onError: handleError,
1235
- returnUrl: finalReturnUrl,
1236
- submitButtonText,
1237
- buttonClassName,
1238
- clientSecret
1239
- }
1240
- )
1241
- },
1242
- clientSecret
1243
- ) : /* @__PURE__ */ jsxs4("div", { children: [
495
+ ref: rootRef,
496
+ "data-solvapay-checkout-layout": resolvedSize,
497
+ "data-solvapay-step": step,
498
+ className: "solvapay-checkout-layout",
499
+ children: [
500
+ !planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
501
+ "button",
502
+ {
503
+ type: "button",
504
+ onClick: handleBack,
505
+ "data-solvapay-checkout-back": "",
506
+ className: "solvapay-back",
507
+ children: copy.planSelector.backButton
508
+ }
509
+ ),
510
+ step === "select" && /* @__PURE__ */ jsx5(
511
+ SelectStep,
512
+ {
513
+ productRef: productRef ?? "",
514
+ initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
515
+ filter: planSelector?.filter,
516
+ sortBy: planSelector?.sortBy,
517
+ popularPlanRef: planSelector?.popularPlanRef,
518
+ onContinue: handlePlanSelect
519
+ }
520
+ ),
521
+ step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
522
+ ActivationFlow2,
523
+ {
524
+ productRef,
525
+ planRef: effectivePlanRef,
526
+ onBack: planRef ? void 0 : handleBack,
527
+ onSuccess: (result) => onResult?.(result),
528
+ onError
529
+ }
530
+ ),
531
+ step === "pay" && /* @__PURE__ */ jsx5(
532
+ PaymentForm2,
533
+ {
534
+ planRef: effectivePlanRef,
535
+ productRef,
536
+ prefillCustomer,
537
+ requireTermsAcceptance,
538
+ onSuccess,
539
+ onResult,
540
+ onFreePlan,
541
+ onError,
542
+ submitButtonText,
543
+ returnUrl
544
+ }
545
+ )
546
+ ]
547
+ }
548
+ );
549
+ };
550
+ var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
551
+ const copy = useCopy();
552
+ const solva = useContext(SolvaPayContext);
553
+ const config = solva?._config;
554
+ const fetcher = useCallback(
555
+ (ref) => defaultListPlans(ref, config),
556
+ [config]
557
+ );
558
+ const { plans, selectedPlan } = usePlans({
559
+ productRef,
560
+ fetcher,
561
+ filter,
562
+ sortBy,
563
+ autoSelectFirstPaid: true,
564
+ initialPlanRef
565
+ });
566
+ const [userSelected, setUserSelected] = useState2(null);
567
+ const selected = userSelected ?? (selectedPlan && selectedPlan.requiresPayment !== false ? { ref: selectedPlan.reference, plan: selectedPlan } : null);
568
+ const autoSkipRef = useRef(false);
569
+ useEffect(() => {
570
+ if (autoSkipRef.current) return;
571
+ const selectable = plans.filter((p) => p.requiresPayment !== false);
572
+ if (plans.length === 1 && selectable.length === 1) {
573
+ autoSkipRef.current = true;
574
+ onContinue(selectable[0].reference, selectable[0]);
575
+ }
576
+ }, [plans, onContinue]);
577
+ const handleSelect = useCallback((ref, plan) => {
578
+ setUserSelected({ ref, plan });
579
+ }, []);
580
+ const continueLabel = useMemo(() => copy.planSelector.continueButton, [copy]);
581
+ return /* @__PURE__ */ jsxs5(Fragment5, { children: [
1244
582
  /* @__PURE__ */ jsx5(
1245
- "div",
583
+ PlanSelector2,
1246
584
  {
1247
- style: {
1248
- minHeight: "40px",
1249
- display: "flex",
1250
- alignItems: "center",
1251
- justifyContent: "center"
1252
- },
1253
- children: /* @__PURE__ */ jsx5(Spinner, { size: "md" })
585
+ productRef,
586
+ initialPlanRef,
587
+ filter,
588
+ sortBy,
589
+ popularPlanRef,
590
+ autoSelectFirstPaid: true,
591
+ onSelect: handleSelect
1254
592
  }
1255
593
  ),
1256
594
  /* @__PURE__ */ jsx5(
1257
595
  "button",
1258
596
  {
1259
- type: "submit",
1260
- disabled: true,
1261
- className: buttonClassName,
1262
- "aria-busy": "false",
1263
- "aria-disabled": "true",
1264
- children: submitButtonText
597
+ type: "button",
598
+ className: "solvapay-continue",
599
+ "data-solvapay-checkout-continue": "",
600
+ "data-state": selected ? "idle" : "disabled",
601
+ disabled: !selected,
602
+ onClick: () => {
603
+ if (selected) onContinue(selected.ref, selected.plan);
604
+ },
605
+ children: continueLabel
1265
606
  }
1266
607
  )
1267
- ] }) });
608
+ ] });
1268
609
  };
1269
610
 
1270
- // src/components/ProductBadge.tsx
1271
- import React5 from "react";
1272
- import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1273
- var ProductBadge = ({
1274
- children,
1275
- as: Component = "div",
611
+ // src/components/AmountPicker.tsx
612
+ import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
613
+ var AmountPicker2 = ({
614
+ currency,
615
+ minAmount,
616
+ maxAmount,
617
+ showCreditEstimate = true,
618
+ onChange,
1276
619
  className
1277
620
  }) => {
1278
- const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
1279
- const [hasLoadedOnce, setHasLoadedOnce] = React5.useState(false);
1280
- React5.useEffect(() => {
1281
- if (!loading) {
1282
- setHasLoadedOnce(true);
1283
- }
1284
- }, [loading]);
1285
- const planToDisplay = activePurchase?.productName || null;
1286
- const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
1287
- if (children) {
1288
- return /* @__PURE__ */ jsx6(Fragment2, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
1289
- }
1290
- if (!shouldShow) {
1291
- return null;
1292
- }
1293
- const computedClassName = typeof className === "function" ? className({ purchases }) : className;
621
+ const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
1294
622
  return /* @__PURE__ */ jsx6(
1295
- Component,
623
+ AmountPicker.Root,
1296
624
  {
1297
- className: computedClassName,
1298
- "data-loading": loading,
1299
- "data-has-purchase": !!activePurchase,
1300
- "data-has-paid-purchase": hasPaidPurchase,
1301
- role: "status",
1302
- "aria-live": "polite",
1303
- "aria-busy": loading,
1304
- "aria-label": `Current product: ${planToDisplay}`,
1305
- children: planToDisplay
625
+ currency,
626
+ minAmount,
627
+ maxAmount,
628
+ onChange,
629
+ className: rootClass,
630
+ children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
1306
631
  }
1307
632
  );
1308
633
  };
1309
- var PlanBadge = ProductBadge;
1310
-
1311
- // src/components/PurchaseGate.tsx
1312
- import { Fragment as Fragment3, jsx as jsx7 } from "react/jsx-runtime";
1313
- var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
1314
- const { purchases, loading, hasProduct } = usePurchase();
1315
- const productToCheck = requireProduct || requirePlan;
1316
- const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
1317
- return /* @__PURE__ */ jsx7(Fragment3, { children: children({
1318
- hasAccess,
1319
- purchases,
1320
- loading
1321
- }) });
634
+ var DefaultTree3 = ({ showCreditEstimate }) => {
635
+ const ctx = useAmountPicker();
636
+ const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
637
+ return /* @__PURE__ */ jsxs6(Fragment6, { children: [
638
+ /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
639
+ /* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
640
+ AmountPicker.Option,
641
+ {
642
+ amount,
643
+ className: "solvapay-amount-picker-pill"
644
+ },
645
+ amount
646
+ )) }),
647
+ /* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
648
+ /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
649
+ /* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-row", children: [
650
+ /* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
651
+ /* @__PURE__ */ jsx6(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
652
+ ] })
653
+ ] }),
654
+ showCreditEstimate && ctx.estimatedCredits != null && /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-credit-estimate", children: creditEstimate(ctx.estimatedCredits) }),
655
+ ctx.error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "solvapay-amount-picker-error", children: ctx.error })
656
+ ] });
1322
657
  };
1323
658
 
1324
- // src/components/PricingSelector.tsx
1325
- import { useCallback as useCallback7, useMemo as useMemo5 } from "react";
1326
-
1327
- // src/hooks/usePlans.ts
1328
- import { useState as useState6, useEffect as useEffect4, useCallback as useCallback6, useMemo as useMemo4, useRef as useRef6 } from "react";
1329
- var plansCache = /* @__PURE__ */ new Map();
1330
- var CACHE_DURATION2 = 5 * 60 * 1e3;
1331
- function processPlans(raw, filter, sortBy) {
1332
- let result = sortBy ? [...raw].sort(sortBy) : raw;
1333
- if (filter) result = result.filter(filter);
1334
- return result;
1335
- }
1336
- function computeInitialIndex(plans, initialPlanRef, autoSelectFirstPaid) {
1337
- if (plans.length === 0) return 0;
1338
- if (initialPlanRef) {
1339
- const idx = plans.findIndex((p) => p.reference === initialPlanRef);
1340
- if (idx >= 0) return idx;
1341
- }
1342
- if (autoSelectFirstPaid) {
1343
- const idx = plans.findIndex((p) => p.requiresPayment !== false);
1344
- return idx >= 0 ? idx : 0;
1345
- }
1346
- return 0;
1347
- }
1348
- function usePlans(options) {
1349
- const {
1350
- fetcher,
1351
- productRef,
1352
- filter,
1353
- sortBy,
1354
- autoSelectFirstPaid = false,
1355
- initialPlanRef,
1356
- selectionReady = true
1357
- } = options;
1358
- const fetcherRef = useRef6(fetcher);
1359
- const filterRef = useRef6(filter);
1360
- const sortByRef = useRef6(sortBy);
1361
- const autoSelectFirstPaidRef = useRef6(autoSelectFirstPaid);
1362
- const initialPlanRefRef = useRef6(initialPlanRef);
1363
- const selectionReadyRef = useRef6(selectionReady);
1364
- const hasAppliedInitialRef = useRef6(false);
1365
- const userHasSelectedRef = useRef6(false);
1366
- const [selectedPlanIndex, setSelectedPlanIndexState] = useState6(() => {
1367
- if (!selectionReady || !productRef) return 0;
1368
- const cached = plansCache.get(productRef);
1369
- if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
1370
- return 0;
1371
- }
1372
- const processed = processPlans(cached.plans, filter, sortBy);
1373
- if (processed.length === 0) return 0;
1374
- const idx = computeInitialIndex(processed, initialPlanRef, autoSelectFirstPaid);
1375
- hasAppliedInitialRef.current = true;
1376
- return idx;
1377
- });
1378
- const [plans, setPlans] = useState6(() => {
1379
- if (!productRef) return [];
1380
- const cached = plansCache.get(productRef);
1381
- if (!cached || Date.now() - cached.timestamp >= CACHE_DURATION2 || cached.plans.length === 0) {
1382
- return [];
659
+ // src/components/CancelledPlanNotice.tsx
660
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
661
+ var CancelledPlanNotice2 = ({
662
+ onReactivated,
663
+ onError,
664
+ className
665
+ }) => {
666
+ const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
667
+ return /* @__PURE__ */ jsxs7(
668
+ CancelledPlanNotice.Root,
669
+ {
670
+ onReactivated,
671
+ onError,
672
+ className: rootClass,
673
+ children: [
674
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
675
+ /* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-details", children: [
676
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
677
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
678
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
679
+ ] }),
680
+ /* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-footer", children: [
681
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
682
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
683
+ ] }),
684
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
685
+ ]
1383
686
  }
1384
- return processPlans(cached.plans, filter, sortBy);
1385
- });
1386
- const [loading, setLoading] = useState6(() => plans.length === 0);
1387
- const [error, setError] = useState6(null);
1388
- useEffect4(() => {
1389
- fetcherRef.current = fetcher;
1390
- }, [fetcher]);
1391
- useEffect4(() => {
1392
- filterRef.current = filter;
1393
- }, [filter]);
1394
- useEffect4(() => {
1395
- sortByRef.current = sortBy;
1396
- }, [sortBy]);
1397
- useEffect4(() => {
1398
- autoSelectFirstPaidRef.current = autoSelectFirstPaid;
1399
- }, [autoSelectFirstPaid]);
1400
- useEffect4(() => {
1401
- initialPlanRefRef.current = initialPlanRef;
1402
- }, [initialPlanRef]);
1403
- useEffect4(() => {
1404
- selectionReadyRef.current = selectionReady;
1405
- }, [selectionReady]);
1406
- const setSelectedPlanIndex = useCallback6((index) => {
1407
- userHasSelectedRef.current = true;
1408
- setSelectedPlanIndexState(index);
1409
- }, []);
1410
- const applyInitialSelection = useCallback6((processedPlans) => {
1411
- if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
1412
- if (!selectionReadyRef.current || processedPlans.length === 0) return;
1413
- hasAppliedInitialRef.current = true;
1414
- const idx = computeInitialIndex(
1415
- processedPlans,
1416
- initialPlanRefRef.current,
1417
- autoSelectFirstPaidRef.current
1418
- );
1419
- setSelectedPlanIndexState(idx);
1420
- }, []);
1421
- const fetchPlans = useCallback6(
1422
- async (force = false) => {
1423
- if (!productRef) {
1424
- setError(new Error("Product reference not configured"));
1425
- setLoading(false);
1426
- return;
1427
- }
1428
- const cached = plansCache.get(productRef);
1429
- const now = Date.now();
1430
- if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
1431
- const processedPlans = processPlans(
1432
- cached.plans,
1433
- filterRef.current,
1434
- sortByRef.current
1435
- );
1436
- setPlans(processedPlans);
1437
- setLoading(false);
1438
- setError(null);
1439
- applyInitialSelection(processedPlans);
1440
- return;
1441
- }
1442
- if (cached?.promise) {
1443
- try {
1444
- setLoading(true);
1445
- const fetchedPlans = await cached.promise;
1446
- const processedPlans = processPlans(
1447
- fetchedPlans,
1448
- filterRef.current,
1449
- sortByRef.current
1450
- );
1451
- setPlans(processedPlans);
1452
- setError(null);
1453
- applyInitialSelection(processedPlans);
1454
- } catch (err) {
1455
- setError(err instanceof Error ? err : new Error("Failed to load plans"));
1456
- } finally {
1457
- setLoading(false);
1458
- }
1459
- return;
1460
- }
1461
- try {
1462
- setLoading(true);
1463
- setError(null);
1464
- const fetchPromise = fetcherRef.current(productRef);
1465
- plansCache.set(productRef, { plans: [], timestamp: now, promise: fetchPromise });
1466
- const fetchedPlans = await fetchPromise;
1467
- plansCache.set(productRef, { plans: fetchedPlans, timestamp: now, promise: null });
1468
- const processedPlans = processPlans(
1469
- fetchedPlans,
1470
- filterRef.current,
1471
- sortByRef.current
1472
- );
1473
- setPlans(processedPlans);
1474
- applyInitialSelection(processedPlans);
1475
- } catch (err) {
1476
- plansCache.delete(productRef);
1477
- setError(err instanceof Error ? err : new Error("Failed to load plans"));
1478
- } finally {
1479
- setLoading(false);
1480
- }
1481
- },
1482
- [productRef, applyInitialSelection]
1483
- );
1484
- useEffect4(() => {
1485
- fetchPlans();
1486
- }, [fetchPlans]);
1487
- useEffect4(() => {
1488
- if (hasAppliedInitialRef.current || userHasSelectedRef.current) return;
1489
- if (!selectionReady || plans.length === 0) return;
1490
- applyInitialSelection(plans);
1491
- }, [selectionReady, plans, applyInitialSelection]);
1492
- const selectedPlan = useMemo4(() => plans[selectedPlanIndex] || null, [plans, selectedPlanIndex]);
1493
- const selectPlan = useCallback6(
1494
- (planRef) => {
1495
- const index = plans.findIndex((p) => p.reference === planRef);
1496
- if (index >= 0) {
1497
- setSelectedPlanIndex(index);
1498
- }
1499
- },
1500
- [plans, setSelectedPlanIndex]
1501
687
  );
1502
- return {
1503
- plans,
1504
- loading,
1505
- error,
1506
- selectedPlanIndex,
1507
- selectedPlan,
1508
- setSelectedPlanIndex,
1509
- selectPlan,
1510
- refetch: () => fetchPlans(true),
1511
- isSelectionReady: hasAppliedInitialRef.current
1512
- };
1513
- }
688
+ };
1514
689
 
1515
- // src/components/PricingSelector.tsx
1516
- import { Fragment as Fragment4, jsx as jsx8 } from "react/jsx-runtime";
1517
- var PricingSelector = ({
690
+ // src/components/CreditGate.tsx
691
+ import { Fragment as Fragment7, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
692
+ var AllowedContent = ({ children }) => {
693
+ const ctx = useCreditGate();
694
+ if (ctx.state !== "allowed") return null;
695
+ return /* @__PURE__ */ jsx8(Fragment7, { children });
696
+ };
697
+ var DefaultBlockedTree = ({ fallback }) => {
698
+ const ctx = useCreditGate();
699
+ if (ctx.state !== "blocked") return null;
700
+ if (fallback) return /* @__PURE__ */ jsx8(Fragment7, { children: fallback });
701
+ return /* @__PURE__ */ jsxs8(Fragment7, { children: [
702
+ /* @__PURE__ */ jsx8(CreditGate.Heading, { className: "solvapay-credit-gate-heading" }),
703
+ /* @__PURE__ */ jsx8(CreditGate.Subheading, { className: "solvapay-credit-gate-subheading" }),
704
+ /* @__PURE__ */ jsx8(CreditGate.Topup, {})
705
+ ] });
706
+ };
707
+ var CreditGate2 = ({
708
+ minCredits,
1518
709
  productRef,
1519
- fetcher,
1520
- filter,
1521
- sortBy,
1522
- autoSelectFirstPaid,
710
+ topupAmount,
711
+ topupCurrency,
712
+ fallback,
713
+ className,
1523
714
  children
1524
715
  }) => {
1525
- const { purchases } = usePurchase();
1526
- const plansHook = usePlans({
1527
- productRef,
1528
- fetcher,
1529
- filter,
1530
- sortBy,
1531
- autoSelectFirstPaid
1532
- });
1533
- const { plans } = plansHook;
1534
- const isPaidPlan = useCallback7(
1535
- (planRef) => {
1536
- const plan = plans.find((p) => p.reference === planRef);
1537
- return plan ? plan.requiresPayment !== false : true;
1538
- },
1539
- [plans]
1540
- );
1541
- const activePurchase = useMemo5(() => {
1542
- const activePurchases = purchases.filter((p) => p.status === "active");
1543
- return activePurchases.sort(
1544
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
1545
- )[0] || null;
1546
- }, [purchases]);
1547
- const isCurrentPlan = useCallback7(
1548
- (planRef) => {
1549
- return activePurchase?.planSnapshot?.reference === planRef;
1550
- },
1551
- [activePurchase]
716
+ const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
717
+ return /* @__PURE__ */ jsxs8(
718
+ CreditGate.Root,
719
+ {
720
+ minCredits,
721
+ productRef,
722
+ topupAmount,
723
+ topupCurrency,
724
+ className: rootClass,
725
+ children: [
726
+ /* @__PURE__ */ jsx8(AllowedContent, { children }),
727
+ /* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
728
+ ]
729
+ }
1552
730
  );
1553
- return /* @__PURE__ */ jsx8(Fragment4, { children: children({
1554
- ...plansHook,
1555
- purchases,
1556
- isPaidPlan,
1557
- isCurrentPlan
1558
- }) });
1559
731
  };
1560
- var PlanSelector = PricingSelector;
1561
-
1562
- // src/hooks/useBalance.ts
1563
- import { useEffect as useEffect5 } from "react";
1564
- function useBalance() {
1565
- const { balance } = useSolvaPay();
1566
- useEffect5(() => {
1567
- balance.refetch();
1568
- }, [balance.refetch]);
1569
- return balance;
1570
- }
1571
732
 
1572
- // src/components/BalanceBadge.tsx
1573
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1574
- function BalanceBadge({ className, numberOnly, children }) {
1575
- const { credits, displayCurrency, creditsPerMinorUnit, displayExchangeRate, loading } = useBalance();
1576
- if (children) {
1577
- return /* @__PURE__ */ jsx9(Fragment5, { children: children({ credits, loading, displayCurrency, creditsPerMinorUnit }) });
1578
- }
1579
- if (loading) {
1580
- return /* @__PURE__ */ jsx9("span", { className, "aria-busy": "true" });
1581
- }
1582
- if (credits == null) {
1583
- return null;
1584
- }
1585
- const formattedCredits = new Intl.NumberFormat().format(credits);
1586
- if (numberOnly) {
1587
- return /* @__PURE__ */ jsx9("span", { className, children: formattedCredits });
1588
- }
1589
- let currencyEquivalent = "";
1590
- if (displayCurrency && creditsPerMinorUnit) {
1591
- const usdCents = credits / creditsPerMinorUnit;
1592
- const displayMajorUnits = usdCents * (displayExchangeRate ?? 1) / 100;
1593
- currencyEquivalent = ` (~${new Intl.NumberFormat(void 0, {
1594
- style: "currency",
1595
- currency: displayCurrency,
1596
- minimumFractionDigits: 2
1597
- }).format(displayMajorUnits)})`;
733
+ // src/transport/types.ts
734
+ var UnsupportedTransportMethodError = class extends Error {
735
+ method;
736
+ constructor(method) {
737
+ super(`SolvaPay transport does not implement "${method}"`);
738
+ this.name = "UnsupportedTransportMethodError";
739
+ this.method = method;
1598
740
  }
1599
- return /* @__PURE__ */ jsxs5("span", { className, children: [
1600
- formattedCredits,
1601
- " credits",
1602
- currencyEquivalent
1603
- ] });
1604
- }
1605
-
1606
- // src/hooks/usePurchaseStatus.ts
1607
- import { useMemo as useMemo6, useCallback as useCallback8 } from "react";
1608
- function usePurchaseStatus() {
1609
- const { purchases } = usePurchase();
1610
- const isPaidPurchase2 = useCallback8((p) => {
1611
- return (p.amount ?? 0) > 0;
1612
- }, []);
1613
- const purchaseData = useMemo6(() => {
1614
- const cancelledPaidPurchases = purchases.filter((p) => {
1615
- return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
1616
- });
1617
- const cancelledPurchase = cancelledPaidPurchases.sort(
1618
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
1619
- )[0] || null;
1620
- const shouldShowCancelledNotice = !!cancelledPurchase;
1621
- return {
1622
- cancelledPurchase,
1623
- shouldShowCancelledNotice
1624
- };
1625
- }, [purchases, isPaidPurchase2]);
1626
- const formatDate = useCallback8((dateString) => {
1627
- if (!dateString) return null;
1628
- return new Date(dateString).toLocaleDateString("en-US", {
1629
- year: "numeric",
1630
- month: "long",
1631
- day: "numeric"
1632
- });
1633
- }, []);
1634
- const getDaysUntilExpiration = useCallback8((endDate) => {
1635
- if (!endDate) return null;
1636
- const now = /* @__PURE__ */ new Date();
1637
- const expiration = new Date(endDate);
1638
- const diffTime = expiration.getTime() - now.getTime();
1639
- const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
1640
- return diffDays > 0 ? diffDays : 0;
1641
- }, []);
1642
- return {
1643
- cancelledPurchase: purchaseData.cancelledPurchase,
1644
- shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
1645
- formatDate,
1646
- getDaysUntilExpiration
1647
- };
1648
- }
1649
-
1650
- // src/hooks/usePurchaseActions.ts
1651
- import { useState as useState7, useCallback as useCallback9 } from "react";
1652
- function usePurchaseActions() {
1653
- const ctx = useSolvaPay();
1654
- const [isCancelling, setIsCancelling] = useState7(false);
1655
- const [isReactivating, setIsReactivating] = useState7(false);
1656
- const [isActivating, setIsActivating] = useState7(false);
1657
- const cancelRenewal = useCallback9(
1658
- async (params) => {
1659
- setIsCancelling(true);
1660
- try {
1661
- return await ctx.cancelRenewal(params);
1662
- } finally {
1663
- setIsCancelling(false);
1664
- }
1665
- },
1666
- [ctx.cancelRenewal]
1667
- );
1668
- const reactivateRenewal = useCallback9(
1669
- async (params) => {
1670
- setIsReactivating(true);
1671
- try {
1672
- return await ctx.reactivateRenewal(params);
1673
- } finally {
1674
- setIsReactivating(false);
1675
- }
1676
- },
1677
- [ctx.reactivateRenewal]
1678
- );
1679
- const activatePlan = useCallback9(
1680
- async (params) => {
1681
- setIsActivating(true);
1682
- try {
1683
- return await ctx.activatePlan(params);
1684
- } finally {
1685
- setIsActivating(false);
1686
- }
1687
- },
1688
- [ctx.activatePlan]
1689
- );
1690
- return {
1691
- cancelRenewal,
1692
- reactivateRenewal,
1693
- activatePlan,
1694
- isCancelling,
1695
- isReactivating,
1696
- isActivating
1697
- };
1698
- }
1699
-
1700
- // src/hooks/useActivation.ts
1701
- import { useState as useState8, useCallback as useCallback10 } from "react";
1702
- function useActivation() {
1703
- const { activatePlan } = useSolvaPay();
1704
- const [state, setState] = useState8("idle");
1705
- const [error, setError] = useState8(null);
1706
- const [result, setResult] = useState8(null);
1707
- const activate = useCallback10(
1708
- async (params) => {
1709
- setState("activating");
1710
- setError(null);
1711
- setResult(null);
1712
- try {
1713
- const data = await activatePlan(params);
1714
- setResult(data);
1715
- switch (data.status) {
1716
- case "activated":
1717
- case "already_active":
1718
- setState("activated");
1719
- break;
1720
- case "topup_required":
1721
- setState("topup_required");
1722
- break;
1723
- case "payment_required":
1724
- setError("This plan requires payment. Please select a different plan.");
1725
- setState("payment_required");
1726
- break;
1727
- case "invalid":
1728
- setError(data.message || "Invalid plan configuration.");
1729
- setState("error");
1730
- break;
1731
- default:
1732
- setError("Unexpected response from server.");
1733
- setState("error");
1734
- }
1735
- } catch (err) {
1736
- setError(err instanceof Error ? err.message : "Activation failed");
1737
- setState("error");
1738
- }
1739
- },
1740
- [activatePlan]
1741
- );
1742
- const reset = useCallback10(() => {
1743
- setState("idle");
1744
- setError(null);
1745
- setResult(null);
1746
- }, []);
1747
- return { activate, state, error, result, reset };
1748
- }
1749
-
1750
- // src/hooks/useTopupAmountSelector.ts
1751
- import { useState as useState9, useCallback as useCallback11, useMemo as useMemo7 } from "react";
1752
- function getQuickAmounts(currency) {
1753
- switch (currency.toUpperCase()) {
1754
- case "SEK":
1755
- case "NOK":
1756
- case "DKK":
1757
- return [100, 500, 1e3, 5e3];
1758
- case "JPY":
1759
- return [1e3, 5e3, 1e4, 5e4];
1760
- case "KRW":
1761
- return [1e4, 5e4, 1e5, 5e5];
1762
- case "ISK":
1763
- case "HUF":
1764
- return [1e3, 5e3, 1e4, 5e4];
1765
- default:
1766
- return [10, 50, 100, 500];
1767
- }
1768
- }
1769
- function getCurrencySymbol(currency) {
1770
- try {
1771
- const parts = new Intl.NumberFormat("en", { style: "currency", currency }).formatToParts(0);
1772
- const sym = parts.find((p) => p.type === "currency");
1773
- return sym?.value || currency;
1774
- } catch {
1775
- return currency;
1776
- }
1777
- }
1778
- var DEFAULT_MIN = 1;
1779
- var DEFAULT_MAX = 1e5;
1780
- function useTopupAmountSelector(options) {
1781
- const { currency, minAmount = DEFAULT_MIN, maxAmount = DEFAULT_MAX } = options;
1782
- const [selectedAmount, setSelectedAmount] = useState9(null);
1783
- const [customAmount, setCustomAmountRaw] = useState9("");
1784
- const [error, setError] = useState9(null);
1785
- const quickAmounts = useMemo7(() => getQuickAmounts(currency), [currency]);
1786
- const currencySymbol = useMemo7(() => getCurrencySymbol(currency), [currency]);
1787
- const resolvedAmount = useMemo7(() => {
1788
- if (customAmount) {
1789
- const parsed = parseFloat(customAmount);
1790
- return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
1791
- }
1792
- return selectedAmount;
1793
- }, [selectedAmount, customAmount]);
1794
- const selectQuickAmount = useCallback11(
1795
- (amount) => {
1796
- setSelectedAmount(amount);
1797
- setCustomAmountRaw("");
1798
- setError(null);
1799
- },
1800
- []
1801
- );
1802
- const setCustomAmount = useCallback11(
1803
- (value) => {
1804
- const sanitized = value.replace(/[^0-9.]/g, "");
1805
- const dotIndex = sanitized.indexOf(".");
1806
- const cleaned = dotIndex === -1 ? sanitized : sanitized.slice(0, dotIndex + 1) + sanitized.slice(dotIndex + 1).replace(/\./g, "");
1807
- setCustomAmountRaw(cleaned);
1808
- setSelectedAmount(null);
1809
- setError(null);
1810
- },
1811
- []
1812
- );
1813
- const validate = useCallback11(() => {
1814
- if (resolvedAmount == null) {
1815
- setError("Please select or enter an amount");
1816
- return false;
1817
- }
1818
- if (resolvedAmount < minAmount) {
1819
- setError(`Minimum amount is ${currencySymbol}${minAmount}`);
1820
- return false;
1821
- }
1822
- if (resolvedAmount > maxAmount) {
1823
- setError(`Maximum amount is ${currencySymbol}${maxAmount.toLocaleString()}`);
1824
- return false;
1825
- }
1826
- setError(null);
1827
- return true;
1828
- }, [resolvedAmount, minAmount, maxAmount, currencySymbol]);
1829
- const reset = useCallback11(() => {
1830
- setSelectedAmount(null);
1831
- setCustomAmountRaw("");
1832
- setError(null);
1833
- }, []);
1834
- return {
1835
- quickAmounts,
1836
- selectedAmount,
1837
- customAmount,
1838
- resolvedAmount,
1839
- selectQuickAmount,
1840
- setCustomAmount,
1841
- error,
1842
- validate,
1843
- reset,
1844
- currencySymbol
1845
- };
1846
- }
741
+ };
1847
742
  export {
743
+ ActivationFlow2 as ActivationFlow,
744
+ AmountPicker2 as AmountPicker,
1848
745
  BalanceBadge,
1849
- PaymentForm,
746
+ CancelPlanButton,
747
+ CancelledPlanNotice2 as CancelledPlanNotice,
748
+ CheckoutLayout,
749
+ CheckoutSummary,
750
+ CopyContext,
751
+ CopyProvider,
752
+ CreditGate2 as CreditGate,
753
+ CurrentPlanCard,
754
+ DEFAULT_ROUTES,
755
+ LaunchCustomerPortalButton,
756
+ MandateText,
757
+ PaymentForm2 as PaymentForm,
758
+ PaymentFormContext,
759
+ PaymentFormProvider,
1850
760
  PlanBadge,
1851
- PlanSelector,
1852
- PricingSelector,
761
+ PlanSelector2 as PlanSelector,
1853
762
  ProductBadge,
1854
763
  PurchaseGate,
1855
764
  SolvaPayProvider,
1856
765
  Spinner,
1857
766
  StripePaymentFormWrapper,
1858
767
  TopupForm,
768
+ UnsupportedTransportMethodError,
769
+ UpdatePaymentMethodButton,
770
+ confirmPayment,
771
+ createHttpTransport,
1859
772
  defaultAuthAdapter,
773
+ deriveVariant,
774
+ enCopy,
1860
775
  filterPurchases,
776
+ formatPrice,
1861
777
  getActivePurchases,
1862
778
  getCancelledPurchasesWithEndDate,
779
+ getMinorUnitsPerMajor,
1863
780
  getMostRecentPurchase,
1864
781
  getPrimaryPurchase,
782
+ interpolate,
1865
783
  isPaidPurchase,
784
+ isPlanPurchase,
785
+ isTopupPurchase,
786
+ mergeCopy,
787
+ resolveCta,
788
+ toMajorUnits,
1866
789
  useActivation,
1867
790
  useBalance,
1868
791
  useCheckout,
792
+ useCopy,
1869
793
  useCustomer,
794
+ useLocale,
795
+ useMerchant,
796
+ usePaymentForm,
797
+ usePaymentMethod,
798
+ usePaywallResolver,
799
+ usePlan,
1870
800
  usePlans,
801
+ useProduct,
1871
802
  usePurchase,
1872
803
  usePurchaseActions,
1873
804
  usePurchaseStatus,
1874
805
  useSolvaPay,
1875
806
  useTopup,
1876
- useTopupAmountSelector
807
+ useTopupAmountSelector,
808
+ useTransport,
809
+ useUsage
1877
810
  };