@solvapay/react 1.0.6 → 1.0.8-preview.1

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,606 +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/SolvaPayProvider.tsx
39
- import { jsx } from "react/jsx-runtime";
40
- var SolvaPayContext = createContext(null);
41
- var CUSTOMER_REF_KEY = "solvapay_customerRef";
42
- var CUSTOMER_REF_EXPIRY = "solvapay_customerRef_expiry";
43
- var CUSTOMER_REF_USER_ID_KEY = "solvapay_customerRef_userId";
44
- var CACHE_DURATION = 24 * 60 * 60 * 1e3;
45
- function getCachedCustomerRef(userId) {
46
- if (typeof window === "undefined") return null;
47
- const cached = localStorage.getItem(CUSTOMER_REF_KEY);
48
- const expiry = localStorage.getItem(CUSTOMER_REF_EXPIRY);
49
- const cachedUserId = localStorage.getItem(CUSTOMER_REF_USER_ID_KEY);
50
- if (!cached || !expiry) return null;
51
- if (Date.now() > parseInt(expiry)) {
52
- localStorage.removeItem(CUSTOMER_REF_KEY);
53
- localStorage.removeItem(CUSTOMER_REF_EXPIRY);
54
- localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
55
- return null;
56
- }
57
- if (userId !== void 0 && userId !== null) {
58
- if (cachedUserId !== userId) {
59
- clearCachedCustomerRef();
60
- return null;
61
- }
62
- }
63
- return cached;
64
- }
65
- function setCachedCustomerRef(customerRef, userId) {
66
- if (typeof window === "undefined") return;
67
- if (userId === void 0 || userId === null) {
68
- return;
69
- }
70
- localStorage.setItem(CUSTOMER_REF_KEY, customerRef);
71
- localStorage.setItem(CUSTOMER_REF_EXPIRY, String(Date.now() + CACHE_DURATION));
72
- localStorage.setItem(CUSTOMER_REF_USER_ID_KEY, userId);
73
- }
74
- function clearCachedCustomerRef() {
75
- if (typeof window === "undefined") return;
76
- localStorage.removeItem(CUSTOMER_REF_KEY);
77
- localStorage.removeItem(CUSTOMER_REF_EXPIRY);
78
- localStorage.removeItem(CUSTOMER_REF_USER_ID_KEY);
79
- }
80
- function getAuthAdapter(config) {
81
- if (config?.auth?.adapter) {
82
- return config.auth.adapter;
83
- }
84
- if (config?.auth?.getToken || config?.auth?.getUserId) {
85
- return {
86
- async getToken() {
87
- return config?.auth?.getToken?.() || null;
88
- },
89
- async getUserId() {
90
- return config?.auth?.getUserId?.() || null;
91
- }
92
- };
93
- }
94
- return defaultAuthAdapter;
95
- }
96
- var SolvaPayProvider = ({
97
- config,
98
- createPayment: customCreatePayment,
99
- checkPurchase: customCheckPurchase,
100
- processPayment: customProcessPayment,
101
- children
102
- }) => {
103
- const [purchaseData, setPurchaseData] = useState({
104
- purchases: []
105
- });
106
- const [loading, setLoading] = useState(false);
107
- const [internalCustomerRef, setInternalCustomerRef] = useState(void 0);
108
- const [userId, setUserId] = useState(null);
109
- const [isAuthenticated, setIsAuthenticated] = useState(false);
110
- const inFlightRef = useRef(null);
111
- const lastFetchedRef = useRef(null);
112
- const checkPurchaseRef = useRef(null);
113
- const createPaymentRef = useRef(null);
114
- const processPaymentRef = useRef(null);
115
- const configRef = useRef(config);
116
- const buildDefaultCheckPurchaseRef = useRef(
117
- null
118
- );
119
- useEffect(() => {
120
- configRef.current = config;
121
- }, [config]);
122
- useEffect(() => {
123
- checkPurchaseRef.current = customCheckPurchase || null;
124
- }, [customCheckPurchase]);
125
- useEffect(() => {
126
- createPaymentRef.current = customCreatePayment || null;
127
- }, [customCreatePayment]);
128
- useEffect(() => {
129
- processPaymentRef.current = customProcessPayment || null;
130
- }, [customProcessPayment]);
131
- const buildDefaultCheckPurchase = useCallback(async () => {
132
- const currentConfig = configRef.current;
133
- const adapter = getAuthAdapter(currentConfig);
134
- const token = await adapter.getToken();
135
- const detectedUserId = await adapter.getUserId();
136
- const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
137
- const fetchFn = currentConfig?.fetch || fetch;
138
- const cachedRef = getCachedCustomerRef(detectedUserId);
139
- const headers = {
140
- "Content-Type": "application/json"
141
- };
142
- if (token) {
143
- headers["Authorization"] = `Bearer ${token}`;
144
- }
145
- if (cachedRef) {
146
- headers["x-solvapay-customer-ref"] = cachedRef;
147
- }
148
- if (currentConfig?.headers) {
149
- const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
150
- Object.assign(headers, customHeaders);
151
- }
152
- const res = await fetchFn(route, {
153
- method: "GET",
154
- headers
155
- });
156
- if (!res.ok) {
157
- const error = new Error(`Failed to check purchase: ${res.statusText}`);
158
- currentConfig?.onError?.(error, "checkPurchase");
159
- throw error;
160
- }
161
- return res.json();
162
- }, []);
163
- useEffect(() => {
164
- buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
165
- }, [buildDefaultCheckPurchase]);
166
- const buildDefaultCreatePayment = useCallback(
167
- async (params) => {
168
- const currentConfig = configRef.current;
169
- const adapter = getAuthAdapter(currentConfig);
170
- const token = await adapter.getToken();
171
- const detectedUserId = await adapter.getUserId();
172
- const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
173
- const fetchFn = currentConfig?.fetch || fetch;
174
- const cachedRef = getCachedCustomerRef(detectedUserId);
175
- const headers = {
176
- "Content-Type": "application/json"
177
- };
178
- if (token) {
179
- headers["Authorization"] = `Bearer ${token}`;
180
- }
181
- if (cachedRef) {
182
- headers["x-solvapay-customer-ref"] = cachedRef;
183
- }
184
- if (currentConfig?.headers) {
185
- const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
186
- Object.assign(headers, customHeaders);
187
- }
188
- const body = { planRef: params.planRef };
189
- if (params.productRef) {
190
- body.productRef = params.productRef;
191
- }
192
- const res = await fetchFn(route, {
193
- method: "POST",
194
- headers,
195
- body: JSON.stringify(body)
196
- });
197
- if (!res.ok) {
198
- const error = new Error(`Failed to create payment: ${res.statusText}`);
199
- currentConfig?.onError?.(error, "createPayment");
200
- throw error;
201
- }
202
- return res.json();
203
- },
204
- []
205
- );
206
- const buildDefaultProcessPayment = useCallback(
207
- async (params) => {
208
- const currentConfig = configRef.current;
209
- const adapter = getAuthAdapter(currentConfig);
210
- const token = await adapter.getToken();
211
- const detectedUserId = await adapter.getUserId();
212
- const route = currentConfig?.api?.processPayment || "/api/process-payment";
213
- const fetchFn = currentConfig?.fetch || fetch;
214
- const cachedRef = getCachedCustomerRef(detectedUserId);
215
- const headers = {
216
- "Content-Type": "application/json"
217
- };
218
- if (token) {
219
- headers["Authorization"] = `Bearer ${token}`;
220
- }
221
- if (cachedRef) {
222
- headers["x-solvapay-customer-ref"] = cachedRef;
223
- }
224
- if (currentConfig?.headers) {
225
- const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
226
- Object.assign(headers, customHeaders);
227
- }
228
- const res = await fetchFn(route, {
229
- method: "POST",
230
- headers,
231
- body: JSON.stringify(params)
232
- });
233
- if (!res.ok) {
234
- const error = new Error(`Failed to process payment: ${res.statusText}`);
235
- currentConfig?.onError?.(error, "processPayment");
236
- throw error;
237
- }
238
- return res.json();
239
- },
240
- []
241
- );
242
- const _checkPurchase = useCallback(async () => {
243
- if (checkPurchaseRef.current) {
244
- return checkPurchaseRef.current();
245
- }
246
- if (buildDefaultCheckPurchaseRef.current) {
247
- return buildDefaultCheckPurchaseRef.current();
248
- }
249
- return buildDefaultCheckPurchase();
250
- }, [buildDefaultCheckPurchase]);
251
- const createPayment = useCallback(
252
- async (params) => {
253
- if (createPaymentRef.current) {
254
- return createPaymentRef.current(params);
255
- }
256
- return buildDefaultCreatePayment(params);
257
- },
258
- [buildDefaultCreatePayment]
259
- );
260
- const processPayment = useCallback(
261
- async (params) => {
262
- if (processPaymentRef.current) {
263
- return processPaymentRef.current(params);
264
- }
265
- return buildDefaultProcessPayment(params);
266
- },
267
- [buildDefaultProcessPayment]
268
- );
269
- useEffect(() => {
270
- const detectAuth = async () => {
271
- const currentConfig = configRef.current;
272
- const adapter = getAuthAdapter(currentConfig);
273
- const token = await adapter.getToken();
274
- const detectedUserId = await adapter.getUserId();
275
- const prevUserId = userId;
276
- setIsAuthenticated(!!token);
277
- setUserId(detectedUserId);
278
- if (prevUserId !== null && detectedUserId !== prevUserId) {
279
- clearCachedCustomerRef();
280
- setInternalCustomerRef(void 0);
281
- return;
282
- }
283
- const cachedRef = getCachedCustomerRef(detectedUserId);
284
- if (cachedRef && token) {
285
- setInternalCustomerRef(cachedRef);
286
- } else if (!token) {
287
- clearCachedCustomerRef();
288
- setInternalCustomerRef(void 0);
289
- } else if (token && !cachedRef) {
290
- setInternalCustomerRef(void 0);
291
- }
292
- };
293
- detectAuth();
294
- const interval = setInterval(detectAuth, 5e3);
295
- return () => clearInterval(interval);
296
- }, [userId]);
297
- const fetchPurchase = useCallback(
298
- async (force = false) => {
299
- if (!isAuthenticated && !internalCustomerRef) {
300
- setPurchaseData({ purchases: [] });
301
- setLoading(false);
302
- inFlightRef.current = null;
303
- lastFetchedRef.current = null;
304
- return;
305
- }
306
- const cacheKey = internalCustomerRef || userId || "anonymous";
307
- if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
308
- return;
309
- }
310
- if (inFlightRef.current === cacheKey) {
311
- return;
312
- }
313
- inFlightRef.current = cacheKey;
314
- setLoading(true);
315
- try {
316
- const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
317
- if (!checkFn) {
318
- throw new Error("checkPurchase function not available");
319
- }
320
- const data = await checkFn();
321
- if (data.customerRef) {
322
- setInternalCustomerRef(data.customerRef);
323
- const currentAdapter = getAuthAdapter(configRef.current);
324
- const currentUserId = await currentAdapter.getUserId();
325
- setCachedCustomerRef(data.customerRef, currentUserId);
326
- }
327
- if (inFlightRef.current === cacheKey) {
328
- const filteredData = {
329
- ...data,
330
- purchases: filterPurchases(data.purchases || [])
331
- };
332
- setPurchaseData(filteredData);
333
- lastFetchedRef.current = cacheKey;
334
- }
335
- } catch (error) {
336
- console.error("[SolvaPayProvider] Failed to fetch purchase:", error);
337
- if (inFlightRef.current === cacheKey) {
338
- setPurchaseData({
339
- purchases: []
340
- });
341
- lastFetchedRef.current = cacheKey;
342
- }
343
- } finally {
344
- if (inFlightRef.current === cacheKey) {
345
- setLoading(false);
346
- inFlightRef.current = null;
347
- }
348
- }
349
- },
350
- [isAuthenticated, internalCustomerRef, userId]
351
- );
352
- const fetchPurchaseRef = useRef(fetchPurchase);
353
- useEffect(() => {
354
- fetchPurchaseRef.current = fetchPurchase;
355
- }, [fetchPurchase]);
356
- const refetchPurchase = useCallback(async () => {
357
- lastFetchedRef.current = null;
358
- inFlightRef.current = null;
359
- setPurchaseData({ purchases: [] });
360
- await fetchPurchaseRef.current(true);
361
- }, []);
362
- useEffect(() => {
363
- lastFetchedRef.current = null;
364
- inFlightRef.current = null;
365
- if (isAuthenticated || internalCustomerRef) {
366
- fetchPurchase();
367
- } else {
368
- setPurchaseData({ purchases: [] });
369
- setLoading(false);
370
- }
371
- }, [isAuthenticated, internalCustomerRef, userId]);
372
- const updateCustomerRef = useCallback(
373
- (newCustomerRef) => {
374
- setInternalCustomerRef(newCustomerRef);
375
- setCachedCustomerRef(newCustomerRef, userId);
376
- fetchPurchase(true);
377
- },
378
- [fetchPurchase, userId]
379
- );
380
- const purchase = useMemo(() => {
381
- const activePurchase = getPrimaryPurchase(purchaseData.purchases);
382
- const activePaidPurchases = purchaseData.purchases.filter(
383
- (p) => p.status === "active" && isPaidPurchase(p)
384
- );
385
- const activePaidPurchase = activePaidPurchases.sort(
386
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
387
- )[0] || null;
388
- return {
389
- loading,
390
- customerRef: purchaseData.customerRef || internalCustomerRef,
391
- email: purchaseData.email,
392
- name: purchaseData.name,
393
- purchases: purchaseData.purchases,
394
- hasProduct: (productName) => {
395
- return purchaseData.purchases.some(
396
- (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
397
- );
398
- },
399
- hasPlan: (productName) => {
400
- return purchaseData.purchases.some(
401
- (p) => p.productName.toLowerCase() === productName.toLowerCase() && p.status === "active"
402
- );
403
- },
404
- activePurchase,
405
- hasPaidPurchase: activePaidPurchases.length > 0,
406
- activePaidPurchase
407
- };
408
- }, [loading, purchaseData, internalCustomerRef]);
409
- const contextValue = useMemo(
410
- () => ({
411
- purchase,
412
- refetchPurchase,
413
- createPayment,
414
- processPayment,
415
- customerRef: purchaseData.customerRef || internalCustomerRef,
416
- updateCustomerRef
417
- }),
418
- [
419
- purchase,
420
- refetchPurchase,
421
- createPayment,
422
- processPayment,
423
- purchaseData.customerRef,
424
- internalCustomerRef,
425
- updateCustomerRef
426
- ]
427
- );
428
- return /* @__PURE__ */ jsx(SolvaPayContext.Provider, { value: contextValue, children });
429
- };
430
-
431
72
  // src/PaymentForm.tsx
432
- import { useEffect as useEffect2, useCallback as useCallback3, useRef as useRef3, useMemo as useMemo2, useState as useState4 } from "react";
433
- import { Elements } from "@stripe/react-stripe-js";
434
-
435
- // src/hooks/useCheckout.ts
436
- import { useState as useState2, useCallback as useCallback2, useRef as useRef2 } from "react";
437
- import { loadStripe } from "@stripe/stripe-js";
438
-
439
- // src/hooks/useSolvaPay.ts
440
- import { useContext } from "react";
441
- function useSolvaPay() {
442
- const context = useContext(SolvaPayContext);
443
- if (!context) {
444
- throw new Error(
445
- "useSolvaPay must be used within a SolvaPayProvider. Wrap your component tree with <SolvaPayProvider> to use this hook."
446
- );
447
- }
448
- return context;
449
- }
450
-
451
- // src/hooks/useCheckout.ts
452
- var stripePromiseCache = /* @__PURE__ */ new Map();
453
- function getStripeCacheKey(publishableKey, accountId) {
454
- return accountId ? `${publishableKey}:${accountId}` : publishableKey;
455
- }
456
- function useCheckout(options) {
457
- const { planRef, productRef } = options;
458
- const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
459
- const [loading, setLoading] = useState2(false);
460
- const [error, setError] = useState2(
461
- !planRef || typeof planRef !== "string" ? new Error("useCheckout: planRef parameter is required and must be a string") : null
462
- );
463
- const [stripePromise, setStripePromise] = useState2(null);
464
- const [clientSecret, setClientSecret] = useState2(null);
465
- const isStartingRef = useRef2(false);
466
- const startCheckout = useCallback2(async () => {
467
- if (isStartingRef.current || loading) {
468
- return;
469
- }
470
- if (!planRef || typeof planRef !== "string") {
471
- setError(new Error("useCheckout: planRef parameter is required and must be a string"));
472
- return;
473
- }
474
- isStartingRef.current = true;
475
- setLoading(true);
476
- setError(null);
477
- try {
478
- const result = await createPayment({ planRef, productRef });
479
- if (!result || typeof result !== "object") {
480
- throw new Error("Invalid payment intent response from server");
481
- }
482
- if (!result.clientSecret || typeof result.clientSecret !== "string") {
483
- throw new Error("Invalid client secret in payment intent response");
484
- }
485
- if (!result.publishableKey || typeof result.publishableKey !== "string") {
486
- throw new Error("Invalid publishable key in payment intent response");
487
- }
488
- if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
489
- updateCustomerRef(result.customerRef);
490
- }
491
- const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
492
- const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
493
- let stripe = stripePromiseCache.get(cacheKey);
494
- if (!stripe) {
495
- stripe = loadStripe(result.publishableKey, stripeOptions);
496
- stripePromiseCache.set(cacheKey, stripe);
497
- }
498
- setStripePromise(stripe);
499
- setClientSecret(result.clientSecret);
500
- } catch (err) {
501
- const error2 = err instanceof Error ? err : new Error("Failed to start checkout");
502
- setError(error2);
503
- } finally {
504
- setLoading(false);
505
- isStartingRef.current = false;
506
- }
507
- }, [planRef, productRef, createPayment, updateCustomerRef, loading]);
508
- const reset = useCallback2(() => {
509
- isStartingRef.current = false;
510
- setLoading(false);
511
- setError(null);
512
- setStripePromise(null);
513
- setClientSecret(null);
514
- }, []);
515
- return {
516
- loading,
517
- error,
518
- stripePromise,
519
- clientSecret,
520
- startCheckout,
521
- reset
522
- };
523
- }
524
-
525
- // src/hooks/usePurchase.ts
526
- function usePurchase() {
527
- const { purchase, refetchPurchase } = useSolvaPay();
528
- return {
529
- ...purchase,
530
- refetch: refetchPurchase
531
- };
532
- }
533
-
534
- // src/components/Spinner.tsx
535
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
536
- var Spinner = ({
537
- className = "",
538
- size = "md"
539
- }) => {
540
- const sizeClasses = {
541
- sm: "h-4 w-4",
542
- md: "h-5 w-5",
543
- lg: "h-6 w-6"
544
- };
545
- return /* @__PURE__ */ jsxs(
546
- "svg",
547
- {
548
- className: `animate-spin ${sizeClasses[size]} ${className}`,
549
- xmlns: "http://www.w3.org/2000/svg",
550
- fill: "none",
551
- viewBox: "0 0 24 24",
552
- role: "status",
553
- "aria-busy": "true",
554
- children: [
555
- /* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
556
- /* @__PURE__ */ jsx2(
557
- "path",
558
- {
559
- className: "opacity-75",
560
- fill: "currentColor",
561
- 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"
562
- }
563
- )
564
- ]
565
- }
566
- );
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 }) });
567
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
+ });
568
100
 
569
101
  // src/components/StripePaymentFormWrapper.tsx
570
- import { useState as useState3 } from "react";
102
+ import { useState } from "react";
571
103
  import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
572
-
573
- // src/hooks/useCustomer.ts
574
- function useCustomer() {
575
- const { purchase, customerRef } = useSolvaPay();
576
- return {
577
- customerRef: purchase.customerRef || customerRef,
578
- email: purchase.email,
579
- name: purchase.name,
580
- loading: purchase.loading
581
- };
582
- }
583
-
584
- // src/components/StripePaymentFormWrapper.tsx
585
- 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";
586
105
  var StripePaymentFormWrapper = ({
587
106
  onSuccess,
588
107
  onError,
589
108
  returnUrl: _returnUrl,
590
- submitButtonText = "Pay Now",
109
+ submitButtonText,
591
110
  buttonClassName,
592
111
  clientSecret
593
112
  }) => {
594
113
  const stripe = useStripe();
595
114
  const elements = useElements();
596
115
  const customer = useCustomer();
597
- const [isProcessing, setIsProcessing] = useState3(false);
598
- const [message, setMessage] = useState3(null);
599
- 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);
600
121
  const handleSubmit = async (event) => {
601
122
  event.preventDefault();
602
123
  if (!stripe || !elements) {
603
- const errorMessage = "Stripe is not available. Please refresh the page.";
124
+ const errorMessage = copy.errors.stripeUnavailable;
604
125
  setMessage(errorMessage);
605
126
  if (onError) {
606
127
  onError(new Error(errorMessage));
@@ -608,7 +129,7 @@ var StripePaymentFormWrapper = ({
608
129
  return;
609
130
  }
610
131
  if (!clientSecret) {
611
- const errorMessage = "Payment intent not available. Please refresh the page.";
132
+ const errorMessage = copy.errors.paymentIntentUnavailable;
612
133
  setMessage(errorMessage);
613
134
  if (onError) {
614
135
  onError(new Error(errorMessage));
@@ -620,7 +141,7 @@ var StripePaymentFormWrapper = ({
620
141
  try {
621
142
  const cardElement = elements.getElement(CardElement);
622
143
  if (!cardElement) {
623
- const errorMessage = "Card element not found";
144
+ const errorMessage = copy.errors.cardElementMissing;
624
145
  setMessage(errorMessage);
625
146
  setIsProcessing(false);
626
147
  if (onError) {
@@ -638,7 +159,7 @@ var StripePaymentFormWrapper = ({
638
159
  }
639
160
  });
640
161
  if (error) {
641
- const errorMessage = error.message || "An unexpected error occurred.";
162
+ const errorMessage = error.message || copy.errors.paymentUnexpected;
642
163
  setMessage(errorMessage);
643
164
  setIsProcessing(false);
644
165
  if (onError) {
@@ -651,8 +172,8 @@ var StripePaymentFormWrapper = ({
651
172
  await onSuccess(paymentIntent);
652
173
  setMessage("Payment successful!");
653
174
  } catch (err) {
654
- const error2 = err instanceof Error ? err : new Error("Payment processing failed");
655
- 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);
656
177
  setIsProcessing(false);
657
178
  if (onError) {
658
179
  onError(error2);
@@ -663,14 +184,18 @@ var StripePaymentFormWrapper = ({
663
184
  setMessage("Payment successful!");
664
185
  }
665
186
  } else if (paymentIntent && paymentIntent.status === "requires_action") {
666
- setMessage("Payment requires additional authentication. Please complete the verification.");
187
+ setMessage(copy.errors.paymentRequires3ds);
667
188
  setIsProcessing(false);
668
189
  } else if (paymentIntent) {
669
- setMessage(`Payment status: ${paymentIntent.status || "processing"}`);
190
+ setMessage(
191
+ interpolate(copy.errors.paymentStatusPrefix, {
192
+ status: paymentIntent.status || "processing"
193
+ })
194
+ );
670
195
  setIsProcessing(false);
671
196
  }
672
197
  } catch (err) {
673
- const error = err instanceof Error ? err : new Error("Unknown error occurred");
198
+ const error = err instanceof Error ? err : new Error(copy.errors.unknownError);
674
199
  setMessage(error.message);
675
200
  if (onError) {
676
201
  onError(error);
@@ -684,20 +209,22 @@ var StripePaymentFormWrapper = ({
684
209
  const cardElementOptions = {
685
210
  style: {
686
211
  base: {
687
- fontSize: "16px",
688
- color: "#424770",
212
+ fontSize: "17px",
213
+ lineHeight: "24px",
214
+ color: "#1e293b",
215
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
689
216
  "::placeholder": {
690
- color: "#aab7c4"
217
+ color: "#94a3b8"
691
218
  }
692
219
  },
693
220
  invalid: {
694
- color: "#9e2146"
221
+ color: "#dc2626"
695
222
  }
696
223
  },
697
224
  hidePostalCode: true
698
225
  };
699
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
700
- isReady ? /* @__PURE__ */ jsx3(
226
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
227
+ isReady ? /* @__PURE__ */ jsx2(
701
228
  CardElement,
702
229
  {
703
230
  options: cardElementOptions,
@@ -711,24 +238,9 @@ var StripePaymentFormWrapper = ({
711
238
  }
712
239
  }
713
240
  }
714
- ) : /* @__PURE__ */ jsx3(
715
- "div",
716
- {
717
- style: {
718
- padding: "12px",
719
- border: "1px solid #cbd5e1",
720
- borderRadius: "0.375rem",
721
- backgroundColor: "white",
722
- display: "flex",
723
- alignItems: "center",
724
- justifyContent: "center",
725
- minHeight: "40px"
726
- },
727
- children: /* @__PURE__ */ jsx3(Spinner, { size: "sm" })
728
- }
729
- ),
730
- message && !isSuccess && /* @__PURE__ */ jsx3("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
731
- /* @__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(
732
244
  "button",
733
245
  {
734
246
  type: "submit",
@@ -737,488 +249,538 @@ var StripePaymentFormWrapper = ({
737
249
  "aria-busy": isProcessing,
738
250
  "aria-disabled": !isReady || !cardComplete || isProcessing || !clientSecret,
739
251
  onClick: handleSubmit,
740
- children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
741
- /* @__PURE__ */ jsx3(Spinner, { size: "sm" }),
742
- /* @__PURE__ */ jsx3("span", { children: "Processing..." })
743
- ] }) : submitButtonText
252
+ children: isProcessing ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
253
+ /* @__PURE__ */ jsx2(Spinner, { size: "sm" }),
254
+ /* @__PURE__ */ jsx2("span", { children: copy.cta.processing })
255
+ ] }) : effectiveSubmitText
744
256
  }
745
257
  )
746
258
  ] });
747
259
  };
748
260
 
749
- // src/PaymentForm.tsx
750
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
751
- var PaymentForm = ({
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: [
339
+ /* @__PURE__ */ jsx4(
340
+ "button",
341
+ {
342
+ type: "button",
343
+ onClick: ctx.backToSelectAmount,
344
+ className: "solvapay-activation-flow-change-amount",
345
+ children: copy.activationFlow.changeAmountButton
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsx4(
349
+ TopupForm,
350
+ {
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)))
355
+ }
356
+ )
357
+ ] });
358
+ };
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
386
+ }
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
401
+ }
402
+ );
403
+ };
404
+
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 = ({
752
417
  planRef,
753
418
  productRef,
419
+ prefillCustomer,
420
+ size = "auto",
421
+ requireTermsAcceptance,
754
422
  onSuccess,
423
+ onResult,
424
+ onFreePlan,
755
425
  onError,
756
- returnUrl,
757
- submitButtonText = "Pay Now",
758
- className,
759
- buttonClassName
426
+ initialPlanRef,
427
+ onPlanSelect,
428
+ planSelector,
429
+ showBackButton = true,
430
+ submitButtonText,
431
+ returnUrl
760
432
  }) => {
761
- const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
762
- const {
763
- loading: checkoutLoading,
764
- error: checkoutError,
765
- clientSecret,
766
- startCheckout,
767
- stripePromise
768
- } = useCheckout({ planRef: validPlanRef, productRef });
769
- const { refetch } = usePurchase();
770
- const { processPayment } = useSolvaPay();
771
- const hasInitializedRef = useRef3(false);
772
- useEffect2(() => {
773
- if (!hasInitializedRef.current && validPlanRef && !checkoutLoading && !checkoutError && !clientSecret) {
774
- hasInitializedRef.current = true;
775
- startCheckout().catch(() => {
776
- hasInitializedRef.current = false;
777
- });
778
- }
779
- if (validPlanRef && clientSecret) {
780
- hasInitializedRef.current = true;
781
- }
782
- }, [validPlanRef, checkoutLoading, checkoutError, clientSecret, startCheckout]);
783
- const handleSuccess = useCallback3(
784
- async (paymentIntent) => {
785
- let processingTimeout = false;
786
- let processingResult = null;
787
- const paymentIntentAny = paymentIntent;
788
- if (processPayment && productRef) {
789
- try {
790
- const result = await processPayment({
791
- paymentIntentId: paymentIntentAny.id,
792
- productRef,
793
- planRef
794
- });
795
- processingResult = result;
796
- const isTimeout = result?.status === "timeout";
797
- processingTimeout = isTimeout;
798
- if (isTimeout) {
799
- for (let attempt = 1; attempt <= 5; attempt++) {
800
- const delay = attempt * 1e3;
801
- await new Promise((resolve) => setTimeout(resolve, delay));
802
- await refetch();
803
- }
804
- if (onSuccess) {
805
- await onSuccess({
806
- ...paymentIntentAny,
807
- _processingTimeout: processingTimeout,
808
- _processingResult: processingResult
809
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
810
- });
811
- }
812
- throw new Error("Payment processing timed out");
813
- } else {
814
- await refetch();
815
- }
816
- } catch (error) {
817
- console.error("[PaymentForm] Failed to process payment:", error);
818
- if (onSuccess) {
819
- try {
820
- await onSuccess({
821
- ...paymentIntentAny,
822
- _processingError: error
823
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
824
- });
825
- } catch {
826
- }
827
- }
828
- throw error;
829
- }
830
- } else {
831
- await refetch();
832
- }
833
- if (onSuccess && !processingTimeout) {
834
- await onSuccess({
835
- ...paymentIntentAny,
836
- _processingTimeout: processingTimeout,
837
- _processingResult: processingResult
838
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
839
- });
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));
840
443
  }
841
- },
842
- [processPayment, productRef, planRef, refetch, 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
843
451
  );
844
- const handleError = useCallback3(
845
- (err) => {
846
- if (onError) {
847
- onError(err);
848
- }
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");
849
471
  },
850
- [onError]
472
+ [onPlanSelect]
851
473
  );
852
- const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
853
- const isValidPlanRef = planRef && typeof planRef === "string";
854
- const hasError = !!checkoutError;
855
- const hasStripeData = !!(stripePromise && clientSecret);
856
- const elementsOptions = useMemo2(() => {
857
- if (!clientSecret) return void 0;
858
- return { clientSecret };
859
- }, [clientSecret]);
860
- const [hasMountedElements, setHasMountedElements] = useState4(false);
861
- useEffect2(() => {
862
- if (hasStripeData) {
863
- setHasMountedElements(true);
864
- }
865
- }, [hasStripeData]);
866
- const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
867
- return /* @__PURE__ */ jsx4("div", { className, children: !isValidPlanRef ? /* @__PURE__ */ jsx4("div", { children: "PaymentForm: planRef is required and must be a string" }) : hasError ? /* @__PURE__ */ jsxs3("div", { children: [
868
- /* @__PURE__ */ jsx4("div", { children: "Payment initialization failed" }),
869
- /* @__PURE__ */ jsx4("div", { children: checkoutError?.message || "Unknown error" })
870
- ] }) : shouldRenderElements && elementsOptions ? (
871
- // Once we have Stripe data, always render Elements to maintain hook consistency
872
- // This prevents hook count mismatches when transitioning between states
873
- /* @__PURE__ */ jsx4(
874
- Elements,
875
- {
876
- stripe: stripePromise,
877
- options: elementsOptions,
878
- children: /* @__PURE__ */ jsx4(
879
- StripePaymentFormWrapper,
474
+ const handleBack = useCallback(() => {
475
+ if (planRef) return;
476
+ setUserStep("select");
477
+ }, [planRef]);
478
+ return /* @__PURE__ */ jsxs5(
479
+ "div",
480
+ {
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,
880
519
  {
881
- onSuccess: handleSuccess,
882
- onError: handleError,
883
- returnUrl: finalReturnUrl,
520
+ planRef: effectivePlanRef,
521
+ productRef,
522
+ prefillCustomer,
523
+ requireTermsAcceptance,
524
+ onSuccess,
525
+ onResult,
526
+ onFreePlan,
527
+ onError,
884
528
  submitButtonText,
885
- buttonClassName,
886
- clientSecret
529
+ returnUrl
887
530
  }
888
531
  )
889
- },
890
- clientSecret
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: [
582
+ /* @__PURE__ */ jsx5(
583
+ PlanSelector2,
584
+ {
585
+ productRef,
586
+ initialPlanRef,
587
+ filter,
588
+ sortBy,
589
+ popularPlanRef,
590
+ autoSelectFirstPaid: true,
591
+ onSelect: handleSelect
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx5(
595
+ "button",
596
+ {
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
606
+ }
891
607
  )
892
- ) : (
893
- // Loading state before Stripe data is available
894
- /* @__PURE__ */ jsxs3("div", { children: [
895
- /* @__PURE__ */ jsx4(
896
- "div",
897
- {
898
- style: {
899
- minHeight: "40px",
900
- display: "flex",
901
- alignItems: "center",
902
- justifyContent: "center"
903
- },
904
- children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
905
- }
906
- ),
907
- /* @__PURE__ */ jsx4(
908
- "button",
909
- {
910
- type: "submit",
911
- disabled: true,
912
- className: buttonClassName,
913
- "aria-busy": "false",
914
- "aria-disabled": "true",
915
- children: submitButtonText
916
- }
917
- )
918
- ] })
919
- ) });
608
+ ] });
920
609
  };
921
610
 
922
- // src/components/ProductBadge.tsx
923
- import React4 from "react";
924
- import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
925
- var ProductBadge = ({
926
- children,
927
- 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,
928
619
  className
929
620
  }) => {
930
- const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
931
- const [hasLoadedOnce, setHasLoadedOnce] = React4.useState(false);
932
- React4.useEffect(() => {
933
- if (!loading) {
934
- setHasLoadedOnce(true);
935
- }
936
- }, [loading]);
937
- const planToDisplay = activePurchase?.productName || null;
938
- const shouldShow = planToDisplay !== null && (!loading || hasLoadedOnce);
939
- if (children) {
940
- return /* @__PURE__ */ jsx5(Fragment2, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
941
- }
942
- if (!shouldShow) {
943
- return null;
944
- }
945
- const computedClassName = typeof className === "function" ? className({ purchases }) : className;
946
- return /* @__PURE__ */ jsx5(
947
- Component,
621
+ const rootClass = ["solvapay-amount-picker", className].filter(Boolean).join(" ");
622
+ return /* @__PURE__ */ jsx6(
623
+ AmountPicker.Root,
948
624
  {
949
- className: computedClassName,
950
- "data-loading": loading,
951
- "data-has-purchase": !!activePurchase,
952
- "data-has-paid-purchase": hasPaidPurchase,
953
- role: "status",
954
- "aria-live": "polite",
955
- "aria-busy": loading,
956
- "aria-label": `Current product: ${planToDisplay}`,
957
- children: planToDisplay
625
+ currency,
626
+ minAmount,
627
+ maxAmount,
628
+ onChange,
629
+ className: rootClass,
630
+ children: /* @__PURE__ */ jsx6(DefaultTree3, { showCreditEstimate })
958
631
  }
959
632
  );
960
633
  };
961
- var PlanBadge = ProductBadge;
962
-
963
- // src/components/PurchaseGate.tsx
964
- import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
965
- var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
966
- const { purchases, loading, hasProduct } = usePurchase();
967
- const productToCheck = requireProduct || requirePlan;
968
- const hasAccess = productToCheck ? hasProduct(productToCheck) : purchases.some((p) => p.status === "active");
969
- return /* @__PURE__ */ jsx6(Fragment3, { children: children({
970
- hasAccess,
971
- purchases,
972
- loading
973
- }) });
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
+ ] });
974
657
  };
975
658
 
976
- // src/components/PricingSelector.tsx
977
- import { useCallback as useCallback5, useMemo as useMemo4 } from "react";
978
-
979
- // src/hooks/usePlans.ts
980
- import { useState as useState5, useEffect as useEffect3, useCallback as useCallback4, useMemo as useMemo3, useRef as useRef4 } from "react";
981
- var plansCache = /* @__PURE__ */ new Map();
982
- var CACHE_DURATION2 = 5 * 60 * 1e3;
983
- function usePlans(options) {
984
- const { fetcher, productRef, filter, sortBy, autoSelectFirstPaid = false } = options;
985
- const [plans, setPlans] = useState5([]);
986
- const [loading, setLoading] = useState5(true);
987
- const [error, setError] = useState5(null);
988
- const [selectedPlanIndex, setSelectedPlanIndex] = useState5(0);
989
- const fetcherRef = useRef4(fetcher);
990
- const filterRef = useRef4(filter);
991
- const sortByRef = useRef4(sortBy);
992
- const autoSelectFirstPaidRef = useRef4(autoSelectFirstPaid);
993
- useEffect3(() => {
994
- fetcherRef.current = fetcher;
995
- }, [fetcher]);
996
- useEffect3(() => {
997
- filterRef.current = filter;
998
- }, [filter]);
999
- useEffect3(() => {
1000
- sortByRef.current = sortBy;
1001
- }, [sortBy]);
1002
- useEffect3(() => {
1003
- autoSelectFirstPaidRef.current = autoSelectFirstPaid;
1004
- }, [autoSelectFirstPaid]);
1005
- const fetchPlans = useCallback4(
1006
- async (force = false) => {
1007
- if (!productRef) {
1008
- setError(new Error("Product reference not configured"));
1009
- setLoading(false);
1010
- return;
1011
- }
1012
- const cached = plansCache.get(productRef);
1013
- const now = Date.now();
1014
- if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
1015
- const cachedPlans = cached.plans;
1016
- let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
1017
- if (sortByRef.current) {
1018
- processedPlans = [...processedPlans].sort(sortByRef.current);
1019
- }
1020
- setPlans(processedPlans);
1021
- setLoading(false);
1022
- setError(null);
1023
- if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1024
- const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1025
- setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1026
- }
1027
- return;
1028
- }
1029
- if (cached?.promise) {
1030
- try {
1031
- setLoading(true);
1032
- const fetchedPlans = await cached.promise;
1033
- let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
1034
- if (sortByRef.current) {
1035
- processedPlans = [...processedPlans].sort(sortByRef.current);
1036
- }
1037
- setPlans(processedPlans);
1038
- setError(null);
1039
- if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1040
- const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1041
- setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1042
- }
1043
- } catch (err) {
1044
- setError(err instanceof Error ? err : new Error("Failed to load plans"));
1045
- } finally {
1046
- setLoading(false);
1047
- }
1048
- return;
1049
- }
1050
- try {
1051
- setLoading(true);
1052
- setError(null);
1053
- const fetchPromise = fetcherRef.current(productRef);
1054
- plansCache.set(productRef, {
1055
- plans: [],
1056
- timestamp: now,
1057
- promise: fetchPromise
1058
- });
1059
- const fetchedPlans = await fetchPromise;
1060
- plansCache.set(productRef, {
1061
- plans: fetchedPlans,
1062
- timestamp: now,
1063
- promise: null
1064
- });
1065
- let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
1066
- if (sortByRef.current) {
1067
- processedPlans = [...processedPlans].sort(sortByRef.current);
1068
- }
1069
- setPlans(processedPlans);
1070
- if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
1071
- const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
1072
- setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
1073
- }
1074
- } catch (err) {
1075
- plansCache.delete(productRef);
1076
- setError(err instanceof Error ? err : new Error("Failed to load plans"));
1077
- } finally {
1078
- setLoading(false);
1079
- }
1080
- },
1081
- [productRef]
1082
- );
1083
- useEffect3(() => {
1084
- fetchPlans();
1085
- }, [fetchPlans]);
1086
- const selectedPlan = useMemo3(() => {
1087
- return plans[selectedPlanIndex] || null;
1088
- }, [plans, selectedPlanIndex]);
1089
- const selectPlan = useCallback4(
1090
- (planRef) => {
1091
- const index = plans.findIndex((p) => p.reference === planRef);
1092
- if (index >= 0) {
1093
- setSelectedPlanIndex(index);
1094
- }
1095
- },
1096
- [plans]
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
+ ]
686
+ }
1097
687
  );
1098
- return {
1099
- plans,
1100
- loading,
1101
- error,
1102
- selectedPlanIndex,
1103
- selectedPlan,
1104
- setSelectedPlanIndex,
1105
- selectPlan,
1106
- refetch: () => fetchPlans(true)
1107
- // Force refetch
1108
- };
1109
- }
688
+ };
1110
689
 
1111
- // src/components/PricingSelector.tsx
1112
- import { Fragment as Fragment4, jsx as jsx7 } from "react/jsx-runtime";
1113
- 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,
1114
709
  productRef,
1115
- fetcher,
1116
- filter,
1117
- sortBy,
1118
- autoSelectFirstPaid,
710
+ topupAmount,
711
+ topupCurrency,
712
+ fallback,
713
+ className,
1119
714
  children
1120
715
  }) => {
1121
- const { purchases } = usePurchase();
1122
- const plansHook = usePlans({
1123
- productRef,
1124
- fetcher,
1125
- filter,
1126
- sortBy,
1127
- autoSelectFirstPaid
1128
- });
1129
- const { plans } = plansHook;
1130
- const isPaidPlan = useCallback5(
1131
- (planRef) => {
1132
- const plan = plans.find((p) => p.reference === planRef);
1133
- return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
1134
- },
1135
- [plans]
1136
- );
1137
- const activePurchase = useMemo4(() => {
1138
- const activePurchases = purchases.filter((p) => p.status === "active");
1139
- return activePurchases.sort(
1140
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
1141
- )[0] || null;
1142
- }, [purchases]);
1143
- const isCurrentPlan = useCallback5(
1144
- (planRef) => {
1145
- return activePurchase?.planSnapshot?.reference === planRef;
1146
- },
1147
- [activePurchase]
716
+ const rootClass = ["solvapay-credit-gate", className].filter(Boolean).join(" ");
717
+ return /* @__PURE__ */ jsxs8(
718
+ CreditGate.Root,
719
+ {
720
+ minCredits,
721
+ productRef,
722
+ topupAmount,
723
+ topupCurrency,
724
+ className: rootClass,
725
+ children: [
726
+ /* @__PURE__ */ jsx8(AllowedContent, { children }),
727
+ /* @__PURE__ */ jsx8(DefaultBlockedTree, { fallback })
728
+ ]
729
+ }
1148
730
  );
1149
- return /* @__PURE__ */ jsx7(Fragment4, { children: children({
1150
- ...plansHook,
1151
- purchases,
1152
- isPaidPlan,
1153
- isCurrentPlan
1154
- }) });
1155
731
  };
1156
- var PlanSelector = PricingSelector;
1157
-
1158
- // src/hooks/usePurchaseStatus.ts
1159
- import { useMemo as useMemo5, useCallback as useCallback6 } from "react";
1160
- function usePurchaseStatus() {
1161
- const { purchases } = usePurchase();
1162
- const isPaidPurchase2 = useCallback6((p) => {
1163
- return (p.amount ?? 0) > 0;
1164
- }, []);
1165
- const purchaseData = useMemo5(() => {
1166
- const cancelledPaidPurchases = purchases.filter((p) => {
1167
- return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
1168
- });
1169
- const cancelledPurchase = cancelledPaidPurchases.sort(
1170
- (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
1171
- )[0] || null;
1172
- const shouldShowCancelledNotice = !!cancelledPurchase;
1173
- return {
1174
- cancelledPurchase,
1175
- shouldShowCancelledNotice
1176
- };
1177
- }, [purchases, isPaidPurchase2]);
1178
- const formatDate = useCallback6((dateString) => {
1179
- if (!dateString) return null;
1180
- return new Date(dateString).toLocaleDateString("en-US", {
1181
- year: "numeric",
1182
- month: "long",
1183
- day: "numeric"
1184
- });
1185
- }, []);
1186
- const getDaysUntilExpiration = useCallback6((endDate) => {
1187
- if (!endDate) return null;
1188
- const now = /* @__PURE__ */ new Date();
1189
- const expiration = new Date(endDate);
1190
- const diffTime = expiration.getTime() - now.getTime();
1191
- const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
1192
- return diffDays > 0 ? diffDays : 0;
1193
- }, []);
1194
- return {
1195
- cancelledPurchase: purchaseData.cancelledPurchase,
1196
- shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
1197
- formatDate,
1198
- getDaysUntilExpiration
1199
- };
1200
- }
1201
732
  export {
1202
- PaymentForm,
733
+ ActivationFlow2 as ActivationFlow,
734
+ AmountPicker2 as AmountPicker,
735
+ BalanceBadge,
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,
1203
747
  PlanBadge,
1204
- PlanSelector,
1205
- PricingSelector,
748
+ PlanSelector2 as PlanSelector,
1206
749
  ProductBadge,
1207
750
  PurchaseGate,
1208
751
  SolvaPayProvider,
1209
752
  Spinner,
1210
753
  StripePaymentFormWrapper,
754
+ TopupForm,
755
+ confirmPayment,
1211
756
  defaultAuthAdapter,
757
+ deriveVariant,
758
+ enCopy,
1212
759
  filterPurchases,
760
+ formatPrice,
1213
761
  getActivePurchases,
1214
762
  getCancelledPurchasesWithEndDate,
1215
763
  getMostRecentPurchase,
1216
764
  getPrimaryPurchase,
765
+ interpolate,
1217
766
  isPaidPurchase,
767
+ mergeCopy,
768
+ resolveCta,
769
+ useActivation,
770
+ useBalance,
1218
771
  useCheckout,
772
+ useCopy,
1219
773
  useCustomer,
774
+ useLocale,
775
+ useMerchant,
776
+ usePaymentForm,
777
+ usePlan,
1220
778
  usePlans,
779
+ useProduct,
1221
780
  usePurchase,
781
+ usePurchaseActions,
1222
782
  usePurchaseStatus,
1223
- useSolvaPay
783
+ useSolvaPay,
784
+ useTopup,
785
+ useTopupAmountSelector
1224
786
  };