@solvapay/react 1.0.7 → 1.0.8-preview.10

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,127 @@
1
+ import {
2
+ ActivationFlow,
3
+ AmountPicker,
4
+ BalanceBadge,
5
+ CancelPlanButton,
6
+ CancelledPlanNotice,
7
+ CheckoutSummary2 as CheckoutSummary,
8
+ CopyContext,
9
+ CopyProvider,
10
+ CreditGate,
11
+ MandateText,
12
+ PaymentForm,
13
+ PaymentFormCardElement,
14
+ PaymentFormContext,
15
+ PaymentFormCustomerFields,
16
+ PaymentFormError,
17
+ PaymentFormLoading,
18
+ PaymentFormMandateText,
19
+ PaymentFormPaymentElement,
20
+ PaymentFormProvider,
21
+ PaymentFormSubmitButton,
22
+ PaymentFormSummary,
23
+ PaymentFormTermsCheckbox,
24
+ PlanBadge,
25
+ PlanSelector,
26
+ ProductBadge,
27
+ PurchaseGate,
28
+ SolvaPayContext,
29
+ SolvaPayProvider,
30
+ Spinner,
31
+ TopupForm2 as TopupForm,
32
+ buildRequestHeaders,
33
+ confirmPayment,
34
+ deriveVariant,
35
+ enCopy,
36
+ filterPurchases,
37
+ formatPrice,
38
+ getActivePurchases,
39
+ getCancelledPurchasesWithEndDate,
40
+ getMostRecentPurchase,
41
+ getPrimaryPurchase,
42
+ interpolate,
43
+ isPaidPurchase,
44
+ mergeCopy,
45
+ resolveCta,
46
+ useActivation,
47
+ useActivationFlow,
48
+ useAmountPicker,
49
+ useAmountPickerCopy,
50
+ useBalance,
51
+ useCheckout,
52
+ useCopy,
53
+ useCreditGate,
54
+ useCustomer,
55
+ useLocale,
56
+ useMerchant,
57
+ usePaymentForm,
58
+ usePlan,
59
+ usePlans,
60
+ useProduct,
61
+ usePurchase,
62
+ usePurchaseActions,
63
+ usePurchaseStatus,
64
+ useSolvaPay,
65
+ useTopup,
66
+ useTopupAmountSelector
67
+ } from "./chunk-MOP3ZBGC.js";
1
68
  import {
2
69
  defaultAuthAdapter
3
70
  } from "./chunk-OUSEQRCT.js";
4
71
 
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
- };
605
-
606
72
  // 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
- );
73
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
74
+ var DefaultTree = ({
75
+ requireTermsAcceptance
76
+ }) => /* @__PURE__ */ jsxs(Fragment, { children: [
77
+ /* @__PURE__ */ jsx(PaymentForm.Summary, {}),
78
+ /* @__PURE__ */ jsx(PaymentForm.CustomerFields, {}),
79
+ /* @__PURE__ */ jsx(PaymentForm.PaymentElement, {}),
80
+ /* @__PURE__ */ jsx(PaymentForm.Error, {}),
81
+ /* @__PURE__ */ jsx(PaymentForm.MandateText, {}),
82
+ requireTermsAcceptance && /* @__PURE__ */ jsx(PaymentForm.TermsCheckbox, {}),
83
+ /* @__PURE__ */ jsx(PaymentForm.SubmitButton, {})
84
+ ] });
85
+ var PaymentFormBase = (props) => {
86
+ const { children, requireTermsAcceptance = false } = props;
87
+ return /* @__PURE__ */ jsx(PaymentForm.Root, { ...props, children: children ?? /* @__PURE__ */ jsx(DefaultTree, { requireTermsAcceptance }) });
781
88
  };
89
+ var PaymentForm2 = Object.assign(PaymentFormBase, {
90
+ Summary: PaymentFormSummary,
91
+ CustomerFields: PaymentFormCustomerFields,
92
+ PaymentElement: PaymentFormPaymentElement,
93
+ CardElement: PaymentFormCardElement,
94
+ MandateText: PaymentFormMandateText,
95
+ TermsCheckbox: PaymentFormTermsCheckbox,
96
+ SubmitButton: PaymentFormSubmitButton,
97
+ Loading: PaymentFormLoading,
98
+ Error: PaymentFormError
99
+ });
782
100
 
783
101
  // src/components/StripePaymentFormWrapper.tsx
784
- import { useState as useState3 } from "react";
102
+ import { useState } from "react";
785
103
  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";
104
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
800
105
  var StripePaymentFormWrapper = ({
801
106
  onSuccess,
802
107
  onError,
803
108
  returnUrl: _returnUrl,
804
- submitButtonText = "Pay Now",
109
+ submitButtonText,
805
110
  buttonClassName,
806
111
  clientSecret
807
112
  }) => {
808
113
  const stripe = useStripe();
809
114
  const elements = useElements();
810
115
  const customer = useCustomer();
811
- const [isProcessing, setIsProcessing] = useState3(false);
812
- const [message, setMessage] = useState3(null);
813
- const [cardComplete, setCardComplete] = useState3(false);
116
+ const copy = useCopy();
117
+ const effectiveSubmitText = submitButtonText ?? copy.cta.payNow;
118
+ const [isProcessing, setIsProcessing] = useState(false);
119
+ const [message, setMessage] = useState(null);
120
+ const [cardComplete, setCardComplete] = useState(false);
814
121
  const handleSubmit = async (event) => {
815
122
  event.preventDefault();
816
123
  if (!stripe || !elements) {
817
- const errorMessage = "Stripe is not available. Please refresh the page.";
124
+ const errorMessage = copy.errors.stripeUnavailable;
818
125
  setMessage(errorMessage);
819
126
  if (onError) {
820
127
  onError(new Error(errorMessage));
@@ -822,7 +129,7 @@ var StripePaymentFormWrapper = ({
822
129
  return;
823
130
  }
824
131
  if (!clientSecret) {
825
- const errorMessage = "Payment intent not available. Please refresh the page.";
132
+ const errorMessage = copy.errors.paymentIntentUnavailable;
826
133
  setMessage(errorMessage);
827
134
  if (onError) {
828
135
  onError(new Error(errorMessage));
@@ -834,7 +141,7 @@ var StripePaymentFormWrapper = ({
834
141
  try {
835
142
  const cardElement = elements.getElement(CardElement);
836
143
  if (!cardElement) {
837
- const errorMessage = "Card element not found";
144
+ const errorMessage = copy.errors.cardElementMissing;
838
145
  setMessage(errorMessage);
839
146
  setIsProcessing(false);
840
147
  if (onError) {
@@ -852,7 +159,7 @@ var StripePaymentFormWrapper = ({
852
159
  }
853
160
  });
854
161
  if (error) {
855
- const errorMessage = error.message || "An unexpected error occurred.";
162
+ const errorMessage = error.message || copy.errors.paymentUnexpected;
856
163
  setMessage(errorMessage);
857
164
  setIsProcessing(false);
858
165
  if (onError) {
@@ -865,8 +172,8 @@ var StripePaymentFormWrapper = ({
865
172
  await onSuccess(paymentIntent);
866
173
  setMessage("Payment successful!");
867
174
  } 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.");
175
+ const error2 = err instanceof Error ? err : new Error(copy.errors.paymentProcessingFailed);
176
+ setMessage(copy.errors.paymentProcessingFailed);
870
177
  setIsProcessing(false);
871
178
  if (onError) {
872
179
  onError(error2);
@@ -877,14 +184,18 @@ var StripePaymentFormWrapper = ({
877
184
  setMessage("Payment successful!");
878
185
  }
879
186
  } else if (paymentIntent && paymentIntent.status === "requires_action") {
880
- setMessage("Payment requires additional authentication. Please complete the verification.");
187
+ setMessage(copy.errors.paymentRequires3ds);
881
188
  setIsProcessing(false);
882
189
  } else if (paymentIntent) {
883
- setMessage(`Payment status: ${paymentIntent.status || "processing"}`);
190
+ setMessage(
191
+ interpolate(copy.errors.paymentStatusPrefix, {
192
+ status: paymentIntent.status || "processing"
193
+ })
194
+ );
884
195
  setIsProcessing(false);
885
196
  }
886
197
  } catch (err) {
887
- const error = err instanceof Error ? err : new Error("Unknown error occurred");
198
+ const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
888
199
  setMessage(error.message);
889
200
  if (onError) {
890
201
  onError(error);
@@ -912,8 +223,8 @@ var StripePaymentFormWrapper = ({
912
223
  },
913
224
  hidePostalCode: true
914
225
  };
915
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
916
- isReady ? /* @__PURE__ */ jsx3(
226
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
227
+ isReady ? /* @__PURE__ */ jsx2(
917
228
  CardElement,
918
229
  {
919
230
  options: cardElementOptions,
@@ -927,9 +238,9 @@ var StripePaymentFormWrapper = ({
927
238
  }
928
239
  }
929
240
  }
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(
241
+ ) : /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", minHeight: "52px" }, children: /* @__PURE__ */ jsx2(Spinner, { size: "sm" }) }),
242
+ message && !isSuccess && /* @__PURE__ */ jsx2("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
243
+ /* @__PURE__ */ jsx2(
933
244
  "button",
934
245
  {
935
246
  type: "submit",
@@ -938,936 +249,530 @@ var StripePaymentFormWrapper = ({
938
249
  "aria-busy": isProcessing,
939
250
  "aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
940
251
  onClick: handleSubmit,
941
- children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
942
- /* @__PURE__ */ jsx3(Spinner, { size: "sm" }),
943
- /* @__PURE__ */ jsx3("span", { children: "Processing..." })
944
- ] }) : submitButtonText
252
+ children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
253
+ /* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
254
+ /* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
255
+ ] }) : effectiveSubmitText
945
256
  }
946
257
  )
947
258
  ] });
948
259
  };
949
260
 
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: [
261
+ // src/components/CheckoutLayout.tsx
262
+ import { useCallback, useContext, useEffect, useMemo, useRef, useState as useState2 } from "react";
263
+
264
+ // src/components/PlanSelector.tsx
265
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
266
+ var DefaultTree2 = () => /* @__PURE__ */ jsxs3(Fragment3, { children: [
267
+ /* @__PURE__ */ jsx3(PlanSelector.Heading, { className: "solvapay-plan-selector-heading" }),
268
+ /* @__PURE__ */ jsx3(PlanSelector.Grid, { className: "solvapay-plan-selector-grid", children: /* @__PURE__ */ jsxs3(PlanSelector.Card, { className: "solvapay-plan-selector-card", children: [
269
+ /* @__PURE__ */ jsx3(PlanSelector.CardBadge, { className: "solvapay-plan-selector-card-badge" }),
270
+ /* @__PURE__ */ jsx3(PlanSelector.CardName, { className: "solvapay-plan-selector-card-name" }),
271
+ /* @__PURE__ */ jsx3(PlanSelector.CardPrice, { className: "solvapay-plan-selector-card-price" }),
272
+ /* @__PURE__ */ jsx3(PlanSelector.CardInterval, { className: "solvapay-plan-selector-card-interval" })
273
+ ] }) }),
274
+ /* @__PURE__ */ jsx3(PlanSelector.Loading, { className: "solvapay-plan-selector-loading" }),
275
+ /* @__PURE__ */ jsx3(PlanSelector.Error, { className: "solvapay-plan-selector-error" })
276
+ ] });
277
+ var PlanSelector2 = (props) => {
278
+ const { children, className, ...rootProps } = props;
279
+ const rootClass = ["solvapay-plan-selector", className].filter(Boolean).join(" ");
280
+ return /* @__PURE__ */ jsx3(PlanSelector.Root, { ...rootProps, className: rootClass, children: children ?? /* @__PURE__ */ jsx3(DefaultTree2, {}) });
281
+ };
282
+
283
+ // src/components/ActivationFlow.tsx
284
+ import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
285
+ var ActivationFlow2 = (props) => {
286
+ const { className, onBack, ...rootProps } = props;
287
+ const rootClass = ["solvapay-activation-flow", className].filter(Boolean).join(" ");
288
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Root, { ...rootProps, className: rootClass, children: [
289
+ /* @__PURE__ */ jsx4(SummaryStep, {}),
290
+ /* @__PURE__ */ jsx4(AmountStep, {}),
291
+ /* @__PURE__ */ jsx4(TopupPaymentStep, {}),
292
+ /* @__PURE__ */ jsx4(RetryingStep, {}),
293
+ /* @__PURE__ */ jsx4(ActivatedStep, {}),
294
+ /* @__PURE__ */ jsx4(ErrorStep, {}),
295
+ onBack && /* @__PURE__ */ jsx4(BackButton, { onBack })
296
+ ] });
297
+ };
298
+ var SummaryStep = () => {
299
+ const copy = useCopy();
300
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Summary, { className: "solvapay-activation-flow-summary", children: [
301
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.heading }),
302
+ /* @__PURE__ */ jsx4(CheckoutSummary, {}),
303
+ /* @__PURE__ */ jsx4(ActivationFlow.ActivateButton, { className: "solvapay-activation-flow-activate" })
304
+ ] });
305
+ };
306
+ var AmountStep = () => {
307
+ const copy = useCopy();
308
+ return /* @__PURE__ */ jsxs4(ActivationFlow.AmountPicker, { children: [
309
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-topup-heading", children: copy.activationFlow.topupHeading }),
310
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-activation-flow-topup-subheading", children: copy.activationFlow.topupSubheading }),
311
+ /* @__PURE__ */ jsx4(AmountPickerBody, {}),
312
+ /* @__PURE__ */ jsx4(ActivationFlow.ContinueButton, { className: "solvapay-activation-flow-continue" })
313
+ ] });
314
+ };
315
+ var AmountPickerBody = () => {
316
+ const ctx = useAmountPicker();
317
+ const { selectAmountLabel, customAmountLabel } = useAmountPickerCopy();
318
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
319
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
320
+ /* @__PURE__ */ jsx4("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx4(
321
+ AmountPicker.Option,
322
+ {
323
+ amount,
324
+ className: "solvapay-amount-picker-pill"
325
+ },
326
+ amount
327
+ )) }),
328
+ /* @__PURE__ */ jsxs4("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
329
+ /* @__PURE__ */ jsx4("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
330
+ /* @__PURE__ */ jsx4(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
331
+ ] })
332
+ ] });
333
+ };
334
+ var TopupPaymentStep = () => {
335
+ const ctx = useActivationFlow();
336
+ const copy = useCopy();
337
+ if (ctx.step !== "topupPayment") return null;
338
+ return /* @__PURE__ */ jsxs4("div", { "data-solvapay-activation-flow-topup-payment": "", children: [
1063
339
  /* @__PURE__ */ jsx4(
1064
- "div",
340
+ "button",
1065
341
  {
1066
- style: {
1067
- minHeight: "40px",
1068
- display: "flex",
1069
- alignItems: "center",
1070
- justifyContent: "center"
1071
- },
1072
- children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
342
+ type: "button",
343
+ onClick: ctx.backToSelectAmount,
344
+ className: "solvapay-activation-flow-change-amount",
345
+ children: copy.activationFlow.changeAmountButton
1073
346
  }
1074
347
  ),
1075
348
  /* @__PURE__ */ jsx4(
1076
- "button",
349
+ TopupForm,
1077
350
  {
1078
- type: "submit",
1079
- disabled: true,
1080
- className: buttonClassName,
1081
- "aria-busy": "false",
1082
- "aria-disabled": "true",
1083
- children: submitButtonText
351
+ amount: ctx.amountCents,
352
+ currency: ctx.currency,
353
+ onSuccess: ctx.onTopupSuccess,
354
+ onError: (err) => ctx.onError?.(err instanceof Error ? err : new Error(String(err)))
1084
355
  }
1085
356
  )
1086
- ] }) });
357
+ ] });
1087
358
  };
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);
359
+ var RetryingStep = () => {
360
+ const copy = useCopy();
361
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Retrying, { className: "solvapay-activation-flow-retrying", children: [
362
+ /* @__PURE__ */ jsx4("h3", { className: "solvapay-activation-flow-heading", children: copy.activationFlow.retryingHeading }),
363
+ /* @__PURE__ */ jsx4("p", { children: copy.activationFlow.retryingSubheading })
364
+ ] });
365
+ };
366
+ var ActivatedStep = () => {
367
+ const copy = useCopy();
368
+ return /* @__PURE__ */ jsxs4(ActivationFlow.Activated, { className: "solvapay-activation-flow-activated", children: [
369
+ /* @__PURE__ */ jsx4("h3", { children: copy.activationFlow.activatedHeading }),
370
+ /* @__PURE__ */ jsx4("p", { children: copy.activationFlow.activatedSubheading })
371
+ ] });
372
+ };
373
+ var ErrorStep = () => {
374
+ const ctx = useActivationFlow();
375
+ const copy = useCopy();
376
+ if (ctx.step !== "error") return null;
377
+ return /* @__PURE__ */ jsxs4("div", { className: "solvapay-activation-flow-error", role: "alert", children: [
378
+ /* @__PURE__ */ jsx4("p", { children: ctx.error }),
379
+ /* @__PURE__ */ jsx4(
380
+ "button",
381
+ {
382
+ type: "button",
383
+ onClick: ctx.reset,
384
+ className: "solvapay-activation-flow-try-again",
385
+ children: copy.activationFlow.tryAgainButton
1139
386
  }
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;
387
+ )
388
+ ] });
389
+ };
390
+ var BackButton = ({ onBack }) => {
391
+ const ctx = useActivationFlow();
392
+ const copy = useCopy();
393
+ if (ctx.step !== "summary") return null;
394
+ return /* @__PURE__ */ jsx4(
395
+ "button",
396
+ {
397
+ type: "button",
398
+ onClick: onBack,
399
+ className: "solvapay-activation-flow-back",
400
+ children: copy.activationFlow.backButton
1148
401
  }
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
- }
402
+ );
403
+ };
1166
404
 
1167
- // src/TopupForm.tsx
1168
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1169
- var TopupForm = ({
1170
- amount,
1171
- currency,
405
+ // src/components/CheckoutLayout.tsx
406
+ import { Fragment as Fragment5, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
407
+ var BREAKPOINTS = {
408
+ chat: 480,
409
+ desktop: 768
410
+ };
411
+ function widthToSize(width) {
412
+ if (width < BREAKPOINTS.chat) return "chat";
413
+ if (width < BREAKPOINTS.desktop) return "mobile";
414
+ return "desktop";
415
+ }
416
+ var CheckoutLayout = ({
417
+ planRef,
418
+ productRef,
419
+ prefillCustomer,
420
+ size = "auto",
421
+ requireTermsAcceptance,
1172
422
  onSuccess,
423
+ onResult,
424
+ onFreePlan,
1173
425
  onError,
1174
- returnUrl,
1175
- submitButtonText = "Top Up",
1176
- className,
1177
- buttonClassName
426
+ initialPlanRef,
427
+ onPlanSelect,
428
+ planSelector,
429
+ showBackButton = true,
430
+ submitButtonText,
431
+ returnUrl
1178
432
  }) => {
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);
433
+ const rootRef = useRef(null);
434
+ const [autoSize, setAutoSize] = useState2("desktop");
435
+ const copy = useCopy();
436
+ useEffect(() => {
437
+ if (size !== "auto") return;
438
+ const el = rootRef.current;
439
+ if (!el || typeof ResizeObserver === "undefined") return;
440
+ const ro = new ResizeObserver((entries) => {
441
+ for (const entry of entries) {
442
+ setAutoSize(widthToSize(entry.contentRect.width));
1211
443
  }
444
+ });
445
+ ro.observe(el);
446
+ return () => ro.disconnect();
447
+ }, [size]);
448
+ const resolvedSize = size === "auto" ? autoSize : size;
449
+ const [selectedPlanRef, setSelectedPlanRef] = useState2(planRef ?? null);
450
+ const [userStep, setUserStep] = useState2(planRef ? "pay" : "select");
451
+ const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
452
+ const { plan: resolvedPlan } = usePlan({
453
+ planRef: effectivePlanRef,
454
+ productRef
455
+ });
456
+ const isUsageBased = resolvedPlan?.type === "usage-based";
457
+ const step = useMemo(() => {
458
+ if (userStep === "pay" && isUsageBased) return "activate";
459
+ if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
460
+ return userStep;
461
+ }, [userStep, isUsageBased, resolvedPlan]);
462
+ const handlePlanSelect = useCallback(
463
+ (ref, plan) => {
464
+ setSelectedPlanRef(ref);
465
+ onPlanSelect?.(ref, plan);
466
+ setUserStep(plan.type === "usage-based" ? "activate" : "pay");
1212
467
  },
1213
- [onError]
468
+ [onPlanSelect]
1214
469
  );
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,
470
+ const handleBack = useCallback(() => {
471
+ if (planRef) return;
472
+ setUserStep("select");
473
+ }, [planRef]);
474
+ return /* @__PURE__ */ jsxs5(
475
+ "div",
1227
476
  {
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: [
477
+ ref: rootRef,
478
+ "data-solvapay-checkout-layout": resolvedSize,
479
+ "data-solvapay-step": step,
480
+ className: "solvapay-checkout-layout",
481
+ children: [
482
+ !planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
483
+ "button",
484
+ {
485
+ type: "button",
486
+ onClick: handleBack,
487
+ "data-solvapay-checkout-back": "",
488
+ className: "solvapay-back",
489
+ children: copy.planSelector.backButton
490
+ }
491
+ ),
492
+ step === "select" && /* @__PURE__ */ jsx5(
493
+ SelectStep,
494
+ {
495
+ productRef: productRef ?? "",
496
+ initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
497
+ filter: planSelector?.filter,
498
+ sortBy: planSelector?.sortBy,
499
+ popularPlanRef: planSelector?.popularPlanRef,
500
+ onContinue: handlePlanSelect
501
+ }
502
+ ),
503
+ step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
504
+ ActivationFlow2,
505
+ {
506
+ productRef,
507
+ planRef: effectivePlanRef,
508
+ onBack: planRef ? void 0 : handleBack,
509
+ onSuccess: (result) => onResult?.(result),
510
+ onError
511
+ }
512
+ ),
513
+ step === "pay" && /* @__PURE__ */ jsx5(
514
+ PaymentForm2,
515
+ {
516
+ planRef: effectivePlanRef,
517
+ productRef,
518
+ prefillCustomer,
519
+ requireTermsAcceptance,
520
+ onSuccess,
521
+ onResult,
522
+ onFreePlan,
523
+ onError,
524
+ submitButtonText,
525
+ returnUrl
526
+ }
527
+ )
528
+ ]
529
+ }
530
+ );
531
+ };
532
+ async function defaultListPlans(productRef, config) {
533
+ const base = config?.api?.listPlans || "/api/list-plans";
534
+ const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
535
+ const fetchFn = config?.fetch || fetch;
536
+ const { headers } = await buildRequestHeaders(config);
537
+ const res = await fetchFn(url, { method: "GET", headers });
538
+ if (!res.ok) {
539
+ const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
540
+ config?.onError?.(error, "listPlans");
541
+ throw error;
542
+ }
543
+ const data = await res.json();
544
+ return data.plans ?? [];
545
+ }
546
+ var SelectStep = ({ productRef, initialPlanRef, filter, sortBy, popularPlanRef, onContinue }) => {
547
+ const copy = useCopy();
548
+ const solva = useContext(SolvaPayContext);
549
+ const config = solva?._config;
550
+ const fetcher = useCallback(
551
+ (ref) => defaultListPlans(ref, config),
552
+ [config]
553
+ );
554
+ const { plans, selectedPlan } = usePlans({
555
+ productRef,
556
+ fetcher,
557
+ filter,
558
+ sortBy,
559
+ autoSelectFirstPaid: true,
560
+ initialPlanRef
561
+ });
562
+ const [userSelected, setUserSelected] = useState2(null);
563
+ const selected = userSelected ?? (selectedPlan && selectedPlan.requiresPayment !== false ? { ref: selectedPlan.reference, plan: selectedPlan } : null);
564
+ const autoSkipRef = useRef(false);
565
+ useEffect(() => {
566
+ if (autoSkipRef.current) return;
567
+ const selectable = plans.filter((p) => p.requiresPayment !== false);
568
+ if (plans.length === 1 && selectable.length === 1) {
569
+ autoSkipRef.current = true;
570
+ onContinue(selectable[0].reference, selectable[0]);
571
+ }
572
+ }, [plans, onContinue]);
573
+ const handleSelect = useCallback((ref, plan) => {
574
+ setUserSelected({ ref, plan });
575
+ }, []);
576
+ const continueLabel = useMemo(() => copy.planSelector.continueButton, [copy]);
577
+ return /* @__PURE__ */ jsxs5(Fragment5, { children: [
1244
578
  /* @__PURE__ */ jsx5(
1245
- "div",
579
+ PlanSelector2,
1246
580
  {
1247
- style: {
1248
- minHeight: "40px",
1249
- display: "flex",
1250
- alignItems: "center",
1251
- justifyContent: "center"
1252
- },
1253
- children: /* @__PURE__ */ jsx5(Spinner, { size: "md" })
581
+ productRef,
582
+ initialPlanRef,
583
+ filter,
584
+ sortBy,
585
+ popularPlanRef,
586
+ autoSelectFirstPaid: true,
587
+ onSelect: handleSelect
1254
588
  }
1255
589
  ),
1256
590
  /* @__PURE__ */ jsx5(
1257
591
  "button",
1258
592
  {
1259
- type: "submit",
1260
- disabled: true,
1261
- className: buttonClassName,
1262
- "aria-busy": "false",
1263
- "aria-disabled": "true",
1264
- children: submitButtonText
593
+ type: "button",
594
+ className: "solvapay-continue",
595
+ "data-solvapay-checkout-continue": "",
596
+ "data-state": selected ? "idle" : "disabled",
597
+ disabled: !selected,
598
+ onClick: () => {
599
+ if (selected) onContinue(selected.ref, selected.plan);
600
+ },
601
+ children: continueLabel
1265
602
  }
1266
603
  )
1267
- ] }) });
604
+ ] });
1268
605
  };
1269
606
 
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",
607
+ // src/components/AmountPicker.tsx
608
+ import { Fragment as Fragment6, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
609
+ var AmountPicker2 = ({
610
+ currency,
611
+ minAmount,
612
+ maxAmount,
613
+ showCreditEstimate = true,
614
+ onChange,
1276
615
  className
1277
616
  }) => {
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;
617
+ const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
1294
618
  return /* @__PURE__ */ jsx6(
1295
- Component,
619
+ AmountPicker.Root,
1296
620
  {
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
621
+ currency,
622
+ minAmount,
623
+ maxAmount,
624
+ onChange,
625
+ className: rootClass,
626
+ children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
1306
627
  }
1307
628
  );
1308
629
  };
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
- }) });
630
+ var DefaultTree3 = ({ showCreditEstimate }) => {
631
+ const ctx = useAmountPicker();
632
+ const { selectAmountLabel, customAmountLabel, creditEstimate } = useAmountPickerCopy();
633
+ return /* @__PURE__ */ jsxs6(Fragment6, { children: [
634
+ /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-label", children: selectAmountLabel }),
635
+ /* @__PURE__ */ jsx6("div", { className: "solvapay-amount-picker-pills", children: ctx.quickAmounts.map((amount) => /* @__PURE__ */ jsx6(
636
+ AmountPicker.Option,
637
+ {
638
+ amount,
639
+ className: "solvapay-amount-picker-pill"
640
+ },
641
+ amount
642
+ )) }),
643
+ /* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-wrapper", children: [
644
+ /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-custom-label", children: customAmountLabel }),
645
+ /* @__PURE__ */ jsxs6("div", { className: "solvapay-amount-picker-custom-row", children: [
646
+ /* @__PURE__ */ jsx6("span", { className: "solvapay-amount-picker-currency-symbol", children: ctx.currencySymbol }),
647
+ /* @__PURE__ */ jsx6(AmountPicker.Custom, { className: "solvapay-amount-picker-custom-input" })
648
+ ] })
649
+ ] }),
650
+ showCreditEstimate && ctx.estimatedCredits != null && /* @__PURE__ */ jsx6("p", { className: "solvapay-amount-picker-credit-estimate", children: creditEstimate(ctx.estimatedCredits) }),
651
+ ctx.error && /* @__PURE__ */ jsx6("p", { role: "alert", className: "solvapay-amount-picker-error", children: ctx.error })
652
+ ] });
1322
653
  };
1323
654
 
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 [];
655
+ // src/components/CancelledPlanNotice.tsx
656
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
657
+ var CancelledPlanNotice2 = ({
658
+ onReactivated,
659
+ onError,
660
+ className
661
+ }) => {
662
+ const rootClass = ["solvapay-cancelled-notice", className].filter(Boolean).join(" ");
663
+ return /* @__PURE__ */ jsxs7(
664
+ CancelledPlanNotice.Root,
665
+ {
666
+ onReactivated,
667
+ onError,
668
+ className: rootClass,
669
+ children: [
670
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Heading, { className: "solvapay-cancelled-notice-heading" }),
671
+ /* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-details", children: [
672
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Expires, { className: "solvapay-cancelled-notice-expires" }),
673
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.DaysRemaining, { className: "solvapay-cancelled-notice-days-remaining" }),
674
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.AccessUntil, { className: "solvapay-cancelled-notice-access-until" })
675
+ ] }),
676
+ /* @__PURE__ */ jsxs7("div", { className: "solvapay-cancelled-notice-footer", children: [
677
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.CancelledOn, { className: "solvapay-cancelled-notice-cancelled-on" }),
678
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.Reason, { className: "solvapay-cancelled-notice-reason" })
679
+ ] }),
680
+ /* @__PURE__ */ jsx7(CancelledPlanNotice.ReactivateButton, { className: "solvapay-cancelled-notice-reactivate" })
681
+ ]
1383
682
  }
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
683
  );
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
- }
684
+ };
1514
685
 
1515
- // src/components/PricingSelector.tsx
1516
- import { Fragment as Fragment4, jsx as jsx8 } from "react/jsx-runtime";
1517
- var PricingSelector = ({
686
+ // src/components/CreditGate.tsx
687
+ import { Fragment as Fragment7, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
688
+ var AllowedContent = ({ children }) => {
689
+ const ctx = useCreditGate();
690
+ if (ctx.state !== "allowed") return null;
691
+ return /* @__PURE__ */ jsx8(Fragment7, { children });
692
+ };
693
+ var DefaultBlockedTree = ({ fallback }) => {
694
+ const ctx = useCreditGate();
695
+ if (ctx.state !== "blocked") return null;
696
+ if (fallback) return /* @__PURE__ */ jsx8(Fragment7, { children: fallback });
697
+ return /* @__PURE__ */ jsxs8(Fragment7, { children: [
698
+ /* @__PURE__ */ jsx8(CreditGate.Heading, { className: "solvapay-credit-gate-heading" }),
699
+ /* @__PURE__ */ jsx8(CreditGate.Subheading, { className: "solvapay-credit-gate-subheading" }),
700
+ /* @__PURE__ */ jsx8(CreditGate.Topup, {})
701
+ ] });
702
+ };
703
+ var CreditGate2 = ({
704
+ minCredits,
1518
705
  productRef,
1519
- fetcher,
1520
- filter,
1521
- sortBy,
1522
- autoSelectFirstPaid,
706
+ topupAmount,
707
+ topupCurrency,
708
+ fallback,
709
+ className,
1523
710
  children
1524
711
  }) => {
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]
1552
- );
1553
- return /* @__PURE__ */ jsx8(Fragment4, { children: children({
1554
- ...plansHook,
1555
- purchases,
1556
- isPaidPlan,
1557
- isCurrentPlan
1558
- }) });
1559
- };
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
-
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)})`;
1598
- }
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;
712
+ const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
713
+ return /* @__PURE__ */ jsxs8(
714
+ CreditGate.Root,
715
+ {
716
+ minCredits,
717
+ productRef,
718
+ topupAmount,
719
+ topupCurrency,
720
+ className: rootClass,
721
+ children: [
722
+ /* @__PURE__ */ jsx8(AllowedContent, { children }),
723
+ /* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
724
+ ]
1791
725
  }
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
726
  );
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
- }
727
+ };
1847
728
  export {
729
+ ActivationFlow2 as ActivationFlow,
730
+ AmountPicker2 as AmountPicker,
1848
731
  BalanceBadge,
1849
- PaymentForm,
732
+ CancelPlanButton,
733
+ CancelledPlanNotice2 as CancelledPlanNotice,
734
+ CheckoutLayout,
735
+ CheckoutSummary,
736
+ CopyContext,
737
+ CopyProvider,
738
+ CreditGate2 as CreditGate,
739
+ MandateText,
740
+ PaymentForm2 as PaymentForm,
741
+ PaymentFormContext,
742
+ PaymentFormProvider,
1850
743
  PlanBadge,
1851
- PlanSelector,
1852
- PricingSelector,
744
+ PlanSelector2 as PlanSelector,
1853
745
  ProductBadge,
1854
746
  PurchaseGate,
1855
747
  SolvaPayProvider,
1856
748
  Spinner,
1857
749
  StripePaymentFormWrapper,
1858
750
  TopupForm,
751
+ confirmPayment,
1859
752
  defaultAuthAdapter,
753
+ deriveVariant,
754
+ enCopy,
1860
755
  filterPurchases,
756
+ formatPrice,
1861
757
  getActivePurchases,
1862
758
  getCancelledPurchasesWithEndDate,
1863
759
  getMostRecentPurchase,
1864
760
  getPrimaryPurchase,
761
+ interpolate,
1865
762
  isPaidPurchase,
763
+ mergeCopy,
764
+ resolveCta,
1866
765
  useActivation,
1867
766
  useBalance,
1868
767
  useCheckout,
768
+ useCopy,
1869
769
  useCustomer,
770
+ useLocale,
771
+ useMerchant,
772
+ usePaymentForm,
773
+ usePlan,
1870
774
  usePlans,
775
+ useProduct,
1871
776
  usePurchase,
1872
777
  usePurchaseActions,
1873
778
  usePurchaseStatus,