@solvapay/react 1.0.7 → 1.0.8-preview.2

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,534 @@ 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);
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));
1203
443
  }
1204
- },
1205
- [onSuccess]
444
+ });
445
+ ro.observe(el);
446
+ return () => ro.disconnect();
447
+ }, [size]);
448
+ const resolvedSize = size === "auto" ? autoSize : size;
449
+ const [selectedPlanRef, setSelectedPlanRef] = useState2(
450
+ planRef ?? initialPlanRef ?? null
1206
451
  );
1207
- const handleError = useCallback5(
1208
- (err) => {
1209
- if (onError) {
1210
- onError(err);
1211
- }
452
+ const [userStep, setUserStep] = useState2(
453
+ planRef ? "pay" : selectedPlanRef ? "pay" : "select"
454
+ );
455
+ const effectivePlanRef = planRef ?? selectedPlanRef ?? void 0;
456
+ const { plan: resolvedPlan } = usePlan({
457
+ planRef: effectivePlanRef,
458
+ productRef
459
+ });
460
+ const isUsageBased = resolvedPlan?.type === "usage-based";
461
+ const step = useMemo(() => {
462
+ if (userStep === "pay" && isUsageBased) return "activate";
463
+ if (userStep === "activate" && resolvedPlan && !isUsageBased) return "pay";
464
+ return userStep;
465
+ }, [userStep, isUsageBased, resolvedPlan]);
466
+ const handlePlanSelect = useCallback(
467
+ (ref, plan) => {
468
+ setSelectedPlanRef(ref);
469
+ onPlanSelect?.(ref, plan);
470
+ setUserStep(plan.type === "usage-based" ? "activate" : "pay");
1212
471
  },
1213
- [onError]
472
+ [onPlanSelect]
1214
473
  );
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,
474
+ const handleBack = useCallback(() => {
475
+ if (planRef) return;
476
+ setUserStep("select");
477
+ }, [planRef]);
478
+ return /* @__PURE__ */ jsxs5(
479
+ "div",
1227
480
  {
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: [
481
+ ref: rootRef,
482
+ "data-solvapay-checkout-layout": resolvedSize,
483
+ "data-solvapay-step": step,
484
+ className: "solvapay-checkout-layout",
485
+ children: [
486
+ !planRef && showBackButton && step !== "select" && /* @__PURE__ */ jsx5(
487
+ "button",
488
+ {
489
+ type: "button",
490
+ onClick: handleBack,
491
+ "data-solvapay-checkout-back": "",
492
+ className: "solvapay-back",
493
+ children: copy.planSelector.backButton
494
+ }
495
+ ),
496
+ step === "select" && /* @__PURE__ */ jsx5(
497
+ SelectStep,
498
+ {
499
+ productRef: productRef ?? "",
500
+ initialPlanRef: initialPlanRef ?? selectedPlanRef ?? void 0,
501
+ filter: planSelector?.filter,
502
+ sortBy: planSelector?.sortBy,
503
+ popularPlanRef: planSelector?.popularPlanRef,
504
+ onContinue: handlePlanSelect
505
+ }
506
+ ),
507
+ step === "activate" && resolvedPlan && productRef && /* @__PURE__ */ jsx5(
508
+ ActivationFlow2,
509
+ {
510
+ productRef,
511
+ planRef: effectivePlanRef,
512
+ onBack: planRef ? void 0 : handleBack,
513
+ onSuccess: (result) => onResult?.(result),
514
+ onError
515
+ }
516
+ ),
517
+ step === "pay" && /* @__PURE__ */ jsx5(
518
+ PaymentForm2,
519
+ {
520
+ planRef: effectivePlanRef,
521
+ productRef,
522
+ prefillCustomer,
523
+ requireTermsAcceptance,
524
+ onSuccess,
525
+ onResult,
526
+ onFreePlan,
527
+ onError,
528
+ submitButtonText,
529
+ returnUrl
530
+ }
531
+ )
532
+ ]
533
+ }
534
+ );
535
+ };
536
+ async function defaultListPlans(productRef, config) {
537
+ const base = config?.api?.listPlans || "/api/list-plans";
538
+ const url = `${base}?productRef=${encodeURIComponent(productRef)}`;
539
+ const fetchFn = config?.fetch || fetch;
540
+ const { headers } = await buildRequestHeaders(config);
541
+ const res = await fetchFn(url, { method: "GET", headers });
542
+ if (!res.ok) {
543
+ const error = new Error(`Failed to fetch plans: ${res.statusText || res.status}`);
544
+ config?.onError?.(error, "listPlans");
545
+ throw error;
546
+ }
547
+ const data = await res.json();
548
+ return data.plans ?? [];
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]
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;
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
+ ]
1791
729
  }
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
730
  );
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
- }
731
+ };
1847
732
  export {
733
+ ActivationFlow2 as ActivationFlow,
734
+ AmountPicker2 as AmountPicker,
1848
735
  BalanceBadge,
1849
- PaymentForm,
736
+ CancelPlanButton,
737
+ CancelledPlanNotice2 as CancelledPlanNotice,
738
+ CheckoutLayout,
739
+ CheckoutSummary,
740
+ CopyContext,
741
+ CopyProvider,
742
+ CreditGate2 as CreditGate,
743
+ MandateText,
744
+ PaymentForm2 as PaymentForm,
745
+ PaymentFormContext,
746
+ PaymentFormProvider,
1850
747
  PlanBadge,
1851
- PlanSelector,
1852
- PricingSelector,
748
+ PlanSelector2 as PlanSelector,
1853
749
  ProductBadge,
1854
750
  PurchaseGate,
1855
751
  SolvaPayProvider,
1856
752
  Spinner,
1857
753
  StripePaymentFormWrapper,
1858
754
  TopupForm,
755
+ confirmPayment,
1859
756
  defaultAuthAdapter,
757
+ deriveVariant,
758
+ enCopy,
1860
759
  filterPurchases,
760
+ formatPrice,
1861
761
  getActivePurchases,
1862
762
  getCancelledPurchasesWithEndDate,
1863
763
  getMostRecentPurchase,
1864
764
  getPrimaryPurchase,
765
+ interpolate,
1865
766
  isPaidPurchase,
767
+ mergeCopy,
768
+ resolveCta,
1866
769
  useActivation,
1867
770
  useBalance,
1868
771
  useCheckout,
772
+ useCopy,
1869
773
  useCustomer,
774
+ useLocale,
775
+ useMerchant,
776
+ usePaymentForm,
777
+ usePlan,
1870
778
  usePlans,
779
+ useProduct,
1871
780
  usePurchase,
1872
781
  usePurchaseActions,
1873
782
  usePurchaseStatus,