@solvapay/react 1.0.0-preview.5 → 1.0.0-preview.7
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.cjs +507 -173
- package/dist/index.d.cts +333 -19
- package/dist/index.d.ts +333 -19
- package/dist/index.js +497 -174
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -22,21 +22,92 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
PaymentForm: () => PaymentForm,
|
|
24
24
|
PlanBadge: () => PlanBadge,
|
|
25
|
+
PlanSelector: () => PlanSelector,
|
|
25
26
|
SolvaPayProvider: () => SolvaPayProvider,
|
|
27
|
+
Spinner: () => Spinner,
|
|
28
|
+
StripePaymentFormWrapper: () => StripePaymentFormWrapper,
|
|
26
29
|
SubscriptionGate: () => SubscriptionGate,
|
|
30
|
+
filterSubscriptions: () => filterSubscriptions,
|
|
31
|
+
getActiveSubscriptions: () => getActiveSubscriptions,
|
|
32
|
+
getCancelledSubscriptionsWithEndDate: () => getCancelledSubscriptionsWithEndDate,
|
|
33
|
+
getMostRecentSubscription: () => getMostRecentSubscription,
|
|
34
|
+
getPrimarySubscription: () => getPrimarySubscription,
|
|
35
|
+
hasActivePaidSubscription: () => hasActivePaidSubscription,
|
|
27
36
|
useCheckout: () => useCheckout,
|
|
37
|
+
usePlans: () => usePlans,
|
|
28
38
|
useSolvaPay: () => useSolvaPay,
|
|
29
|
-
useSubscription: () => useSubscription
|
|
39
|
+
useSubscription: () => useSubscription,
|
|
40
|
+
useSubscriptionHelpers: () => useSubscriptionHelpers
|
|
30
41
|
});
|
|
31
42
|
module.exports = __toCommonJS(index_exports);
|
|
32
43
|
|
|
33
44
|
// src/SolvaPayProvider.tsx
|
|
34
45
|
var import_react = require("react");
|
|
46
|
+
|
|
47
|
+
// src/utils/subscriptions.ts
|
|
48
|
+
function filterSubscriptions(subscriptions) {
|
|
49
|
+
const now = /* @__PURE__ */ new Date();
|
|
50
|
+
const filtered = subscriptions.filter((sub) => {
|
|
51
|
+
const subAny = sub;
|
|
52
|
+
const isCancelled = sub.status === "cancelled" || subAny.cancelledAt;
|
|
53
|
+
if (!isCancelled) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (isCancelled && subAny.endDate) {
|
|
57
|
+
const endDate = new Date(subAny.endDate);
|
|
58
|
+
const isFuture = endDate > now;
|
|
59
|
+
return isFuture;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
});
|
|
63
|
+
return filtered;
|
|
64
|
+
}
|
|
65
|
+
function getActiveSubscriptions(subscriptions) {
|
|
66
|
+
return subscriptions.filter((sub) => {
|
|
67
|
+
const subAny = sub;
|
|
68
|
+
return sub.status === "active" && !subAny.cancelledAt;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function getCancelledSubscriptionsWithEndDate(subscriptions) {
|
|
72
|
+
return subscriptions.filter((sub) => {
|
|
73
|
+
const subAny = sub;
|
|
74
|
+
const isCancelled = sub.status === "cancelled" || subAny.cancelledAt;
|
|
75
|
+
return isCancelled && subAny.endDate && new Date(subAny.endDate) > /* @__PURE__ */ new Date();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function getMostRecentSubscription(subscriptions) {
|
|
79
|
+
if (subscriptions.length === 0) return null;
|
|
80
|
+
return subscriptions.reduce((latest, current) => {
|
|
81
|
+
return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function getPrimarySubscription(subscriptions) {
|
|
85
|
+
const filtered = filterSubscriptions(subscriptions);
|
|
86
|
+
const activeSubs = getActiveSubscriptions(filtered);
|
|
87
|
+
if (activeSubs.length > 0) {
|
|
88
|
+
return getMostRecentSubscription(activeSubs);
|
|
89
|
+
}
|
|
90
|
+
const cancelledWithEndDate = getCancelledSubscriptionsWithEndDate(filtered);
|
|
91
|
+
if (cancelledWithEndDate.length > 0) {
|
|
92
|
+
return getMostRecentSubscription(cancelledWithEndDate);
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function hasActivePaidSubscription(subscriptions, isPaidPlan) {
|
|
97
|
+
const filtered = filterSubscriptions(subscriptions);
|
|
98
|
+
const activePaid = getActiveSubscriptions(filtered).some((sub) => isPaidPlan(sub.planName));
|
|
99
|
+
if (activePaid) return true;
|
|
100
|
+
const cancelledPaid = getCancelledSubscriptionsWithEndDate(filtered).some((sub) => isPaidPlan(sub.planName));
|
|
101
|
+
return cancelledPaid;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/SolvaPayProvider.tsx
|
|
35
105
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
36
106
|
var SolvaPayContext = (0, import_react.createContext)(null);
|
|
37
107
|
var SolvaPayProvider = ({
|
|
38
108
|
createPayment,
|
|
39
109
|
checkSubscription,
|
|
110
|
+
processPayment,
|
|
40
111
|
customerRef,
|
|
41
112
|
onCustomerRefUpdate,
|
|
42
113
|
children
|
|
@@ -52,43 +123,112 @@ var SolvaPayProvider = ({
|
|
|
52
123
|
});
|
|
53
124
|
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
54
125
|
const [internalCustomerRef, setInternalCustomerRef] = (0, import_react.useState)(customerRef);
|
|
126
|
+
const inFlightRef = (0, import_react.useRef)(null);
|
|
127
|
+
const lastFetchedRef = (0, import_react.useRef)(null);
|
|
128
|
+
const checkSubscriptionRef = (0, import_react.useRef)(checkSubscription);
|
|
55
129
|
(0, import_react.useEffect)(() => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}, [customerRef, internalCustomerRef]);
|
|
60
|
-
const fetchSubscription = (0, import_react.useCallback)(async () => {
|
|
130
|
+
checkSubscriptionRef.current = checkSubscription;
|
|
131
|
+
}, [checkSubscription]);
|
|
132
|
+
const fetchSubscription = (0, import_react.useCallback)(async (force = false) => {
|
|
61
133
|
const currentCustomerRef = internalCustomerRef;
|
|
62
134
|
if (!currentCustomerRef) {
|
|
63
135
|
setSubscriptionData({ subscriptions: [] });
|
|
64
136
|
setLoading(false);
|
|
137
|
+
inFlightRef.current = null;
|
|
138
|
+
lastFetchedRef.current = null;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!force && lastFetchedRef.current === currentCustomerRef && inFlightRef.current !== currentCustomerRef) {
|
|
65
142
|
return;
|
|
66
143
|
}
|
|
144
|
+
if (inFlightRef.current === currentCustomerRef) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
inFlightRef.current = currentCustomerRef;
|
|
67
148
|
setLoading(true);
|
|
68
149
|
try {
|
|
69
|
-
const data = await
|
|
70
|
-
|
|
150
|
+
const data = await checkSubscriptionRef.current(currentCustomerRef);
|
|
151
|
+
if (inFlightRef.current === currentCustomerRef) {
|
|
152
|
+
const filteredData = {
|
|
153
|
+
...data,
|
|
154
|
+
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
155
|
+
};
|
|
156
|
+
setSubscriptionData(filteredData);
|
|
157
|
+
lastFetchedRef.current = currentCustomerRef;
|
|
158
|
+
}
|
|
71
159
|
} catch (error) {
|
|
72
|
-
console.error("
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
160
|
+
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
161
|
+
if (inFlightRef.current === currentCustomerRef) {
|
|
162
|
+
setSubscriptionData({
|
|
163
|
+
customerRef: currentCustomerRef,
|
|
164
|
+
subscriptions: []
|
|
165
|
+
});
|
|
166
|
+
lastFetchedRef.current = currentCustomerRef;
|
|
167
|
+
}
|
|
77
168
|
} finally {
|
|
78
|
-
|
|
169
|
+
if (inFlightRef.current === currentCustomerRef) {
|
|
170
|
+
setLoading(false);
|
|
171
|
+
inFlightRef.current = null;
|
|
172
|
+
}
|
|
79
173
|
}
|
|
80
|
-
}, [internalCustomerRef
|
|
174
|
+
}, [internalCustomerRef]);
|
|
175
|
+
const refetchSubscription = (0, import_react.useCallback)(async () => {
|
|
176
|
+
const currentCustomerRef = internalCustomerRef;
|
|
177
|
+
lastFetchedRef.current = null;
|
|
178
|
+
inFlightRef.current = null;
|
|
179
|
+
await fetchSubscription(true);
|
|
180
|
+
}, [fetchSubscription, internalCustomerRef]);
|
|
181
|
+
(0, import_react.useEffect)(() => {
|
|
182
|
+
if (customerRef !== internalCustomerRef) {
|
|
183
|
+
setInternalCustomerRef(customerRef);
|
|
184
|
+
}
|
|
185
|
+
}, [customerRef, internalCustomerRef]);
|
|
81
186
|
(0, import_react.useEffect)(() => {
|
|
82
|
-
|
|
83
|
-
|
|
187
|
+
if (!customerRef) {
|
|
188
|
+
setSubscriptionData({ subscriptions: [] });
|
|
189
|
+
setLoading(false);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (customerRef === lastFetchedRef.current || customerRef === inFlightRef.current) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const doFetch = async () => {
|
|
196
|
+
inFlightRef.current = customerRef;
|
|
197
|
+
setLoading(true);
|
|
198
|
+
try {
|
|
199
|
+
const data = await checkSubscriptionRef.current(customerRef);
|
|
200
|
+
if (inFlightRef.current === customerRef) {
|
|
201
|
+
const filteredData = {
|
|
202
|
+
...data,
|
|
203
|
+
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
204
|
+
};
|
|
205
|
+
setSubscriptionData(filteredData);
|
|
206
|
+
lastFetchedRef.current = customerRef;
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
210
|
+
if (inFlightRef.current === customerRef) {
|
|
211
|
+
setSubscriptionData({
|
|
212
|
+
customerRef,
|
|
213
|
+
subscriptions: []
|
|
214
|
+
});
|
|
215
|
+
lastFetchedRef.current = customerRef;
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
if (inFlightRef.current === customerRef) {
|
|
219
|
+
setLoading(false);
|
|
220
|
+
inFlightRef.current = null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
doFetch();
|
|
225
|
+
}, [customerRef]);
|
|
84
226
|
const updateCustomerRef = (0, import_react.useCallback)((newCustomerRef) => {
|
|
85
227
|
const previousRef = internalCustomerRef;
|
|
86
228
|
setInternalCustomerRef(newCustomerRef);
|
|
87
229
|
if (onCustomerRefUpdate) {
|
|
88
230
|
onCustomerRefUpdate(newCustomerRef);
|
|
89
231
|
}
|
|
90
|
-
if (previousRef !== newCustomerRef) {
|
|
91
|
-
}
|
|
92
232
|
}, [onCustomerRefUpdate, internalCustomerRef]);
|
|
93
233
|
const subscription = (0, import_react.useMemo)(() => ({
|
|
94
234
|
loading,
|
|
@@ -107,17 +247,18 @@ var SolvaPayProvider = ({
|
|
|
107
247
|
}), [loading, subscriptionData]);
|
|
108
248
|
const contextValue = (0, import_react.useMemo)(() => ({
|
|
109
249
|
subscription,
|
|
110
|
-
refetchSubscription
|
|
250
|
+
refetchSubscription,
|
|
111
251
|
createPayment,
|
|
252
|
+
processPayment,
|
|
112
253
|
customerRef: internalCustomerRef,
|
|
113
254
|
updateCustomerRef
|
|
114
|
-
}), [subscription,
|
|
255
|
+
}), [subscription, refetchSubscription, createPayment, processPayment, internalCustomerRef, updateCustomerRef]);
|
|
115
256
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SolvaPayContext.Provider, { value: contextValue, children });
|
|
116
257
|
};
|
|
117
258
|
|
|
118
259
|
// src/PaymentForm.tsx
|
|
119
|
-
var
|
|
120
|
-
var
|
|
260
|
+
var import_react5 = require("react");
|
|
261
|
+
var import_react_stripe_js2 = require("@stripe/react-stripe-js");
|
|
121
262
|
|
|
122
263
|
// src/hooks/useCheckout.ts
|
|
123
264
|
var import_react3 = require("react");
|
|
@@ -136,6 +277,10 @@ function useSolvaPay() {
|
|
|
136
277
|
}
|
|
137
278
|
|
|
138
279
|
// src/hooks/useCheckout.ts
|
|
280
|
+
var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
281
|
+
function getStripeCacheKey(publishableKey, accountId) {
|
|
282
|
+
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
283
|
+
}
|
|
139
284
|
function useCheckout(planRef) {
|
|
140
285
|
const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
|
|
141
286
|
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
@@ -171,22 +316,16 @@ function useCheckout(planRef) {
|
|
|
171
316
|
if (!result.publishableKey || typeof result.publishableKey !== "string") {
|
|
172
317
|
throw new Error("Invalid publishable key in payment intent response");
|
|
173
318
|
}
|
|
174
|
-
if (result.customerRef && result.customerRef !== customerRef) {
|
|
175
|
-
|
|
176
|
-
sent: customerRef,
|
|
177
|
-
received: result.customerRef
|
|
178
|
-
});
|
|
179
|
-
if (updateCustomerRef) {
|
|
180
|
-
updateCustomerRef(result.customerRef);
|
|
181
|
-
console.log("\u2705 [useCheckout] Customer reference updated via callback");
|
|
182
|
-
} else {
|
|
183
|
-
console.warn("\u26A0\uFE0F [useCheckout] No updateCustomerRef callback available!");
|
|
184
|
-
}
|
|
185
|
-
} else if (result.customerRef === customerRef) {
|
|
186
|
-
console.log("\u2705 [useCheckout] Customer reference unchanged:", customerRef);
|
|
319
|
+
if (result.customerRef && result.customerRef !== customerRef && updateCustomerRef) {
|
|
320
|
+
updateCustomerRef(result.customerRef);
|
|
187
321
|
}
|
|
188
322
|
const stripeOptions = result.accountId ? { stripeAccount: result.accountId } : {};
|
|
189
|
-
const
|
|
323
|
+
const cacheKey = getStripeCacheKey(result.publishableKey, result.accountId);
|
|
324
|
+
let stripe = stripePromiseCache.get(cacheKey);
|
|
325
|
+
if (!stripe) {
|
|
326
|
+
stripe = (0, import_stripe_js.loadStripe)(result.publishableKey, stripeOptions);
|
|
327
|
+
stripePromiseCache.set(cacheKey, stripe);
|
|
328
|
+
}
|
|
190
329
|
setStripePromise(stripe);
|
|
191
330
|
setClientSecret(result.clientSecret);
|
|
192
331
|
} catch (err) {
|
|
@@ -223,7 +362,7 @@ function useSubscription() {
|
|
|
223
362
|
};
|
|
224
363
|
}
|
|
225
364
|
|
|
226
|
-
// src/
|
|
365
|
+
// src/components/Spinner.tsx
|
|
227
366
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
228
367
|
var Spinner = ({
|
|
229
368
|
className = "",
|
|
@@ -243,15 +382,12 @@ var Spinner = ({
|
|
|
243
382
|
}
|
|
244
383
|
);
|
|
245
384
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StripePaymentForm, { ...props });
|
|
253
|
-
};
|
|
254
|
-
var StripePaymentForm = ({
|
|
385
|
+
|
|
386
|
+
// src/components/StripePaymentFormWrapper.tsx
|
|
387
|
+
var import_react4 = require("react");
|
|
388
|
+
var import_react_stripe_js = require("@stripe/react-stripe-js");
|
|
389
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
390
|
+
var StripePaymentFormWrapper = ({
|
|
255
391
|
onSuccess,
|
|
256
392
|
onError,
|
|
257
393
|
returnUrl,
|
|
@@ -262,6 +398,7 @@ var StripePaymentForm = ({
|
|
|
262
398
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
263
399
|
const [isProcessing, setIsProcessing] = (0, import_react4.useState)(false);
|
|
264
400
|
const [message, setMessage] = (0, import_react4.useState)(null);
|
|
401
|
+
const [isPaymentElementReady, setIsPaymentElementReady] = (0, import_react4.useState)(false);
|
|
265
402
|
const handleSubmit = async (event) => {
|
|
266
403
|
event.preventDefault();
|
|
267
404
|
if (!stripe || !elements) {
|
|
@@ -309,15 +446,24 @@ var StripePaymentForm = ({
|
|
|
309
446
|
}
|
|
310
447
|
};
|
|
311
448
|
const isSuccess = message?.includes("successful") ?? false;
|
|
312
|
-
|
|
449
|
+
const isReady = !!(stripe && elements);
|
|
450
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
313
451
|
"form",
|
|
314
452
|
{
|
|
315
453
|
onSubmit: handleSubmit,
|
|
316
454
|
className: "space-y-6",
|
|
317
455
|
noValidate: true,
|
|
318
456
|
children: [
|
|
319
|
-
/* @__PURE__ */ (0,
|
|
320
|
-
|
|
457
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
|
|
458
|
+
(!isPaymentElementReady || !isReady) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute inset-0 z-10 p-4 bg-white border border-gray-300 rounded-lg min-h-[200px] flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "md" }) }),
|
|
459
|
+
isReady && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: isPaymentElementReady ? "block" : "opacity-0 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
460
|
+
import_react_stripe_js.PaymentElement,
|
|
461
|
+
{
|
|
462
|
+
onReady: () => setIsPaymentElementReady(true)
|
|
463
|
+
}
|
|
464
|
+
) })
|
|
465
|
+
] }),
|
|
466
|
+
message && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
321
467
|
"div",
|
|
322
468
|
{
|
|
323
469
|
className: `mt-6 p-4 rounded-lg ${isSuccess ? "bg-green-50 border border-green-400 text-green-700" : "bg-red-50 border border-red-400 text-red-700"}`,
|
|
@@ -327,207 +473,395 @@ var StripePaymentForm = ({
|
|
|
327
473
|
children: message
|
|
328
474
|
}
|
|
329
475
|
),
|
|
330
|
-
/* @__PURE__ */ (0,
|
|
476
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
331
477
|
"button",
|
|
332
478
|
{
|
|
333
479
|
type: "submit",
|
|
334
|
-
disabled: !
|
|
480
|
+
disabled: !isReady || !isPaymentElementReady || isProcessing,
|
|
335
481
|
className: buttonClassName,
|
|
336
|
-
"aria-busy": isProcessing,
|
|
337
|
-
"aria-disabled": !
|
|
338
|
-
children: isProcessing ? /* @__PURE__ */ (0,
|
|
482
|
+
"aria-busy": isProcessing || !isPaymentElementReady || !isReady,
|
|
483
|
+
"aria-disabled": !isReady || !isPaymentElementReady || isProcessing,
|
|
484
|
+
children: isProcessing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }) }) : !isReady || !isPaymentElementReady ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" }) }) : submitButtonText
|
|
339
485
|
}
|
|
340
486
|
)
|
|
341
487
|
]
|
|
342
488
|
}
|
|
343
489
|
);
|
|
344
490
|
};
|
|
491
|
+
|
|
492
|
+
// src/PaymentForm.tsx
|
|
493
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
345
494
|
var PaymentForm = ({
|
|
346
495
|
planRef,
|
|
496
|
+
agentRef,
|
|
347
497
|
onSuccess,
|
|
348
498
|
onError,
|
|
349
499
|
returnUrl,
|
|
350
500
|
submitButtonText = "Pay Now",
|
|
351
501
|
className,
|
|
352
|
-
buttonClassName
|
|
353
|
-
initialButtonText,
|
|
354
|
-
cancelButtonText = "Cancel"
|
|
502
|
+
buttonClassName
|
|
355
503
|
}) => {
|
|
356
504
|
const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
|
|
357
|
-
const
|
|
505
|
+
const checkout = useCheckout(validPlanRef);
|
|
358
506
|
const { refetch } = useSubscription();
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const key = `${planRef}-${initialButtonText}`;
|
|
368
|
-
if (initializationKey.current === key) {
|
|
369
|
-
return;
|
|
507
|
+
const { processPayment, customerRef } = useSolvaPay();
|
|
508
|
+
const hasInitializedRef = (0, import_react5.useRef)(false);
|
|
509
|
+
(0, import_react5.useEffect)(() => {
|
|
510
|
+
if (!hasInitializedRef.current && validPlanRef && !checkout.loading && !checkout.error && !checkout.clientSecret) {
|
|
511
|
+
hasInitializedRef.current = true;
|
|
512
|
+
checkout.startCheckout().catch(() => {
|
|
513
|
+
hasInitializedRef.current = false;
|
|
514
|
+
});
|
|
370
515
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
516
|
+
if (validPlanRef && checkout.clientSecret) {
|
|
517
|
+
hasInitializedRef.current = true;
|
|
518
|
+
}
|
|
519
|
+
}, [validPlanRef, checkout.loading, checkout.error, checkout.clientSecret, checkout.startCheckout]);
|
|
520
|
+
const handleSuccess = (0, import_react5.useCallback)(async (paymentIntent) => {
|
|
521
|
+
if (processPayment && customerRef && agentRef) {
|
|
522
|
+
try {
|
|
523
|
+
await processPayment({
|
|
524
|
+
paymentIntentId: paymentIntent.id,
|
|
525
|
+
agentRef,
|
|
526
|
+
customerRef,
|
|
527
|
+
planRef
|
|
381
528
|
});
|
|
382
|
-
|
|
529
|
+
await refetch();
|
|
530
|
+
} catch (error) {
|
|
531
|
+
console.error("[PaymentForm] Failed to process payment:", error);
|
|
532
|
+
await refetch();
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
await refetch();
|
|
383
536
|
}
|
|
384
|
-
}, [planRef, initialButtonText, loading, error, reset]);
|
|
385
|
-
const handleStartCheckout = async () => {
|
|
386
|
-
setHasStartedCheckout(true);
|
|
387
|
-
await startCheckout();
|
|
388
|
-
setShowPaymentForm(true);
|
|
389
|
-
};
|
|
390
|
-
const handleSuccess = async (paymentIntent) => {
|
|
391
|
-
setShowPaymentForm(false);
|
|
392
|
-
reset();
|
|
393
|
-
await refetch();
|
|
394
537
|
if (onSuccess) {
|
|
395
538
|
onSuccess(paymentIntent);
|
|
396
539
|
}
|
|
397
|
-
};
|
|
398
|
-
const
|
|
399
|
-
setShowPaymentForm(false);
|
|
400
|
-
reset();
|
|
401
|
-
};
|
|
402
|
-
const handleError = (err) => {
|
|
540
|
+
}, [processPayment, customerRef, agentRef, planRef, refetch, onSuccess]);
|
|
541
|
+
const handleError = (0, import_react5.useCallback)((err) => {
|
|
403
542
|
if (onError) {
|
|
404
543
|
onError(err);
|
|
405
544
|
}
|
|
406
|
-
};
|
|
545
|
+
}, [onError]);
|
|
407
546
|
const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
547
|
+
const isValidPlanRef = planRef && typeof planRef === "string";
|
|
548
|
+
const hasError = !!checkout.error;
|
|
549
|
+
const hasStripeData = !!(checkout.stripePromise && checkout.clientSecret);
|
|
550
|
+
const elementsOptions = (0, import_react5.useMemo)(() => {
|
|
551
|
+
if (!checkout.clientSecret) return void 0;
|
|
552
|
+
return { clientSecret: checkout.clientSecret };
|
|
553
|
+
}, [checkout.clientSecret]);
|
|
554
|
+
const [hasMountedElements, setHasMountedElements] = (0, import_react5.useState)(false);
|
|
555
|
+
(0, import_react5.useEffect)(() => {
|
|
556
|
+
if (hasStripeData) {
|
|
557
|
+
setHasMountedElements(true);
|
|
558
|
+
}
|
|
559
|
+
}, [hasStripeData]);
|
|
560
|
+
const shouldRenderElements = hasStripeData || hasMountedElements && checkout.stripePromise && checkout.clientSecret;
|
|
561
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !isValidPlanRef ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: "PaymentForm: planRef is required and must be a string" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: [
|
|
562
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "font-medium mb-1", children: "Payment initialization failed" }),
|
|
563
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-sm", children: checkout.error?.message || "Unknown error" })
|
|
564
|
+
] }) : shouldRenderElements && checkout.stripePromise && elementsOptions ? (
|
|
565
|
+
// Once we have Stripe data, always render Elements to maintain hook consistency
|
|
566
|
+
// This prevents hook count mismatches when transitioning between states
|
|
567
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
568
|
+
import_react_stripe_js2.Elements,
|
|
569
|
+
{
|
|
570
|
+
stripe: checkout.stripePromise,
|
|
571
|
+
options: elementsOptions,
|
|
572
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
573
|
+
StripePaymentFormWrapper,
|
|
574
|
+
{
|
|
575
|
+
onSuccess: handleSuccess,
|
|
576
|
+
onError: handleError,
|
|
577
|
+
returnUrl: finalReturnUrl,
|
|
578
|
+
submitButtonText,
|
|
579
|
+
buttonClassName
|
|
580
|
+
}
|
|
581
|
+
)
|
|
582
|
+
},
|
|
583
|
+
checkout.clientSecret
|
|
584
|
+
)
|
|
585
|
+
) : (
|
|
586
|
+
// Loading state before Stripe data is available
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "space-y-4", children: [
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg flex items-center justify-center min-h-[200px]", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "md" }) }),
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
444
590
|
"button",
|
|
445
591
|
{
|
|
446
592
|
disabled: true,
|
|
447
593
|
className: buttonClassName,
|
|
448
594
|
"aria-busy": "true",
|
|
449
|
-
children: /* @__PURE__ */ (0,
|
|
595
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "sm" }) })
|
|
450
596
|
}
|
|
451
597
|
)
|
|
452
|
-
] })
|
|
453
|
-
|
|
454
|
-
initialButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
455
|
-
"button",
|
|
456
|
-
{
|
|
457
|
-
onClick: () => {
|
|
458
|
-
reset();
|
|
459
|
-
handleStartCheckout();
|
|
460
|
-
},
|
|
461
|
-
className: buttonClassName,
|
|
462
|
-
children: initialButtonText
|
|
463
|
-
}
|
|
464
|
-
),
|
|
465
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-4 p-4 bg-red-50 border border-red-400 rounded-lg text-red-700", children: error.message })
|
|
466
|
-
] }),
|
|
467
|
-
!loading && !error && initialButtonText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
468
|
-
"button",
|
|
469
|
-
{
|
|
470
|
-
onClick: handleStartCheckout,
|
|
471
|
-
className: buttonClassName,
|
|
472
|
-
children: initialButtonText
|
|
473
|
-
}
|
|
474
|
-
)
|
|
475
|
-
] });
|
|
598
|
+
] })
|
|
599
|
+
) });
|
|
476
600
|
};
|
|
477
601
|
|
|
478
602
|
// src/components/PlanBadge.tsx
|
|
479
|
-
var
|
|
603
|
+
var import_react6 = require("react");
|
|
604
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
480
605
|
var PlanBadge = ({
|
|
481
606
|
children,
|
|
482
607
|
as: Component = "div",
|
|
483
608
|
className
|
|
484
609
|
}) => {
|
|
485
610
|
const { subscriptions, loading } = useSubscription();
|
|
611
|
+
const [displayPlan, setDisplayPlan] = (0, import_react6.useState)(null);
|
|
612
|
+
const [hasLoadedOnce, setHasLoadedOnce] = (0, import_react6.useState)(false);
|
|
613
|
+
const lastPlanRef = (0, import_react6.useRef)(null);
|
|
614
|
+
const lastLoadingRef = (0, import_react6.useRef)(true);
|
|
615
|
+
const previousSubscriptionsRef = (0, import_react6.useRef)(subscriptions);
|
|
616
|
+
const primarySubscription = getPrimarySubscription(subscriptions);
|
|
617
|
+
const currentPlanName = primarySubscription?.planName || null;
|
|
618
|
+
const fallbackPlanName = subscriptions.length > 0 && !currentPlanName ? subscriptions[0]?.planName || null : null;
|
|
619
|
+
const effectivePlanName = currentPlanName || fallbackPlanName;
|
|
620
|
+
(0, import_react6.useEffect)(() => {
|
|
621
|
+
if (!loading && !hasLoadedOnce) {
|
|
622
|
+
setHasLoadedOnce(true);
|
|
623
|
+
}
|
|
624
|
+
lastLoadingRef.current = loading;
|
|
625
|
+
}, [loading, hasLoadedOnce]);
|
|
626
|
+
(0, import_react6.useEffect)(() => {
|
|
627
|
+
const previousSubs = previousSubscriptionsRef.current;
|
|
628
|
+
if (previousSubs !== subscriptions) {
|
|
629
|
+
if (loading) {
|
|
630
|
+
setHasLoadedOnce(false);
|
|
631
|
+
}
|
|
632
|
+
setDisplayPlan(null);
|
|
633
|
+
lastPlanRef.current = null;
|
|
634
|
+
previousSubscriptionsRef.current = subscriptions;
|
|
635
|
+
}
|
|
636
|
+
}, [subscriptions, loading]);
|
|
637
|
+
(0, import_react6.useEffect)(() => {
|
|
638
|
+
const currentPlan = effectivePlanName;
|
|
639
|
+
const previousPlan = lastPlanRef.current;
|
|
640
|
+
if (currentPlan !== previousPlan) {
|
|
641
|
+
if (currentPlan !== null) {
|
|
642
|
+
lastPlanRef.current = currentPlan;
|
|
643
|
+
setDisplayPlan(currentPlan);
|
|
644
|
+
} else {
|
|
645
|
+
lastPlanRef.current = null;
|
|
646
|
+
setDisplayPlan(null);
|
|
647
|
+
}
|
|
648
|
+
} else if (currentPlan !== null && displayPlan === null) {
|
|
649
|
+
setDisplayPlan(currentPlan);
|
|
650
|
+
}
|
|
651
|
+
}, [effectivePlanName, displayPlan]);
|
|
652
|
+
const shouldShow = effectivePlanName !== null && hasLoadedOnce;
|
|
653
|
+
const planToDisplay = displayPlan ?? effectivePlanName;
|
|
486
654
|
if (children) {
|
|
487
|
-
return /* @__PURE__ */ (0,
|
|
655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children({ subscriptions, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
656
|
+
}
|
|
657
|
+
if (!shouldShow) {
|
|
658
|
+
return null;
|
|
488
659
|
}
|
|
489
660
|
const computedClassName = typeof className === "function" ? className({ subscriptions }) : className;
|
|
490
|
-
|
|
491
|
-
const latestSub = activeSubs.length > 0 ? activeSubs.reduce((latest, current) => {
|
|
492
|
-
return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
|
|
493
|
-
}) : null;
|
|
494
|
-
const displayText = loading ? "Loading..." : latestSub ? latestSub.planName : "Free";
|
|
495
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
496
662
|
Component,
|
|
497
663
|
{
|
|
498
664
|
className: computedClassName,
|
|
499
665
|
"data-loading": loading,
|
|
500
|
-
"data-has-subscription":
|
|
666
|
+
"data-has-subscription": !!primarySubscription,
|
|
501
667
|
role: "status",
|
|
502
668
|
"aria-live": "polite",
|
|
503
669
|
"aria-busy": loading,
|
|
504
|
-
"aria-label":
|
|
505
|
-
children:
|
|
670
|
+
"aria-label": `Current plan: ${planToDisplay}`,
|
|
671
|
+
children: planToDisplay
|
|
506
672
|
}
|
|
507
673
|
);
|
|
508
674
|
};
|
|
509
675
|
|
|
510
676
|
// src/components/SubscriptionGate.tsx
|
|
511
|
-
var
|
|
677
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
512
678
|
var SubscriptionGate = ({
|
|
513
679
|
requirePlan,
|
|
514
680
|
children
|
|
515
681
|
}) => {
|
|
516
682
|
const { subscriptions, loading, hasPlan } = useSubscription();
|
|
517
683
|
const hasAccess = requirePlan ? hasPlan(requirePlan) : subscriptions.some((sub) => sub.status === "active");
|
|
518
|
-
return /* @__PURE__ */ (0,
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children({
|
|
519
685
|
hasAccess,
|
|
520
686
|
subscriptions,
|
|
521
687
|
loading
|
|
522
688
|
}) });
|
|
523
689
|
};
|
|
690
|
+
|
|
691
|
+
// src/components/PlanSelector.tsx
|
|
692
|
+
var import_react8 = require("react");
|
|
693
|
+
|
|
694
|
+
// src/hooks/usePlans.ts
|
|
695
|
+
var import_react7 = require("react");
|
|
696
|
+
function usePlans(options) {
|
|
697
|
+
const { fetcher, agentRef, filter, sortBy, autoSelectFirstPaid = false } = options;
|
|
698
|
+
const [plans, setPlans] = (0, import_react7.useState)([]);
|
|
699
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
700
|
+
const [error, setError] = (0, import_react7.useState)(null);
|
|
701
|
+
const [selectedPlanIndex, setSelectedPlanIndex] = (0, import_react7.useState)(0);
|
|
702
|
+
const fetchPlans = (0, import_react7.useCallback)(async () => {
|
|
703
|
+
if (!agentRef) {
|
|
704
|
+
setError(new Error("Agent reference not configured"));
|
|
705
|
+
setLoading(false);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
try {
|
|
709
|
+
setLoading(true);
|
|
710
|
+
setError(null);
|
|
711
|
+
const fetchedPlans = await fetcher(agentRef);
|
|
712
|
+
let processedPlans = filter ? fetchedPlans.filter(filter) : fetchedPlans;
|
|
713
|
+
if (sortBy) {
|
|
714
|
+
processedPlans = [...processedPlans].sort(sortBy);
|
|
715
|
+
}
|
|
716
|
+
setPlans(processedPlans);
|
|
717
|
+
if (autoSelectFirstPaid && processedPlans.length > 0) {
|
|
718
|
+
const firstPaidIndex = processedPlans.findIndex(
|
|
719
|
+
(plan) => plan.price && plan.price > 0
|
|
720
|
+
);
|
|
721
|
+
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
722
|
+
}
|
|
723
|
+
} catch (err) {
|
|
724
|
+
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
725
|
+
} finally {
|
|
726
|
+
setLoading(false);
|
|
727
|
+
}
|
|
728
|
+
}, [agentRef, fetcher, filter, sortBy, autoSelectFirstPaid]);
|
|
729
|
+
(0, import_react7.useEffect)(() => {
|
|
730
|
+
fetchPlans();
|
|
731
|
+
}, [fetchPlans]);
|
|
732
|
+
const selectedPlan = (0, import_react7.useMemo)(() => {
|
|
733
|
+
return plans[selectedPlanIndex] || null;
|
|
734
|
+
}, [plans, selectedPlanIndex]);
|
|
735
|
+
const selectPlan = (0, import_react7.useCallback)((planRef) => {
|
|
736
|
+
const index = plans.findIndex((p) => p.reference === planRef);
|
|
737
|
+
if (index >= 0) {
|
|
738
|
+
setSelectedPlanIndex(index);
|
|
739
|
+
}
|
|
740
|
+
}, [plans]);
|
|
741
|
+
return {
|
|
742
|
+
plans,
|
|
743
|
+
loading,
|
|
744
|
+
error,
|
|
745
|
+
selectedPlanIndex,
|
|
746
|
+
selectedPlan,
|
|
747
|
+
setSelectedPlanIndex,
|
|
748
|
+
selectPlan,
|
|
749
|
+
refetch: fetchPlans
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/components/PlanSelector.tsx
|
|
754
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
755
|
+
var PlanSelector = ({
|
|
756
|
+
agentRef,
|
|
757
|
+
fetcher,
|
|
758
|
+
filter,
|
|
759
|
+
sortBy,
|
|
760
|
+
autoSelectFirstPaid,
|
|
761
|
+
children
|
|
762
|
+
}) => {
|
|
763
|
+
const { subscriptions } = useSubscription();
|
|
764
|
+
const plansHook = usePlans({
|
|
765
|
+
agentRef,
|
|
766
|
+
fetcher,
|
|
767
|
+
filter,
|
|
768
|
+
sortBy,
|
|
769
|
+
autoSelectFirstPaid
|
|
770
|
+
});
|
|
771
|
+
const { plans } = plansHook;
|
|
772
|
+
const isPaidPlan = (0, import_react8.useCallback)((planName) => {
|
|
773
|
+
const plan = plans.find((p) => p.name === planName);
|
|
774
|
+
return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
|
|
775
|
+
}, [plans]);
|
|
776
|
+
const activeSubscription = (0, import_react8.useMemo)(() => {
|
|
777
|
+
const activeSubs = subscriptions.filter((sub) => sub.status === "active");
|
|
778
|
+
return activeSubs.sort(
|
|
779
|
+
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
780
|
+
)[0] || null;
|
|
781
|
+
}, [subscriptions]);
|
|
782
|
+
const isCurrentPlan = (0, import_react8.useCallback)((planName) => {
|
|
783
|
+
return activeSubscription?.planName === planName;
|
|
784
|
+
}, [activeSubscription]);
|
|
785
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: children({
|
|
786
|
+
...plansHook,
|
|
787
|
+
subscriptions,
|
|
788
|
+
isPaidPlan,
|
|
789
|
+
isCurrentPlan
|
|
790
|
+
}) });
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// src/hooks/useSubscriptionHelpers.ts
|
|
794
|
+
var import_react9 = require("react");
|
|
795
|
+
function useSubscriptionHelpers(plans) {
|
|
796
|
+
const { subscriptions } = useSubscription();
|
|
797
|
+
const isPaidPlan = (0, import_react9.useCallback)((planName) => {
|
|
798
|
+
const plan = plans.find((p) => p.name === planName);
|
|
799
|
+
return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
|
|
800
|
+
}, [plans]);
|
|
801
|
+
const subscriptionData = (0, import_react9.useMemo)(() => {
|
|
802
|
+
const activePaidSubscriptions = subscriptions.filter(
|
|
803
|
+
(sub) => sub.status === "active" && isPaidPlan(sub.planName)
|
|
804
|
+
);
|
|
805
|
+
const activePaidSubscription = activePaidSubscriptions.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0] || null;
|
|
806
|
+
const cancelledPaidSubscriptions = subscriptions.filter(
|
|
807
|
+
(sub) => sub.status === "cancelled" && isPaidPlan(sub.planName)
|
|
808
|
+
);
|
|
809
|
+
const cancelledSubscription = cancelledPaidSubscriptions.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime())[0] || null;
|
|
810
|
+
const hasActiveSubscription = subscriptions.some((sub) => sub.status === "active");
|
|
811
|
+
const shouldShowCancelledNotice = !!(cancelledSubscription && (!hasActiveSubscription || cancelledSubscription.endDate));
|
|
812
|
+
return {
|
|
813
|
+
activePaidSubscription,
|
|
814
|
+
activePlanName: activePaidSubscription?.planName || null,
|
|
815
|
+
cancelledSubscription,
|
|
816
|
+
hasPaidSubscription: activePaidSubscriptions.length > 0,
|
|
817
|
+
shouldShowCancelledNotice
|
|
818
|
+
};
|
|
819
|
+
}, [subscriptions, isPaidPlan]);
|
|
820
|
+
const formatDate = (0, import_react9.useCallback)((dateString) => {
|
|
821
|
+
if (!dateString) return null;
|
|
822
|
+
return new Date(dateString).toLocaleDateString("en-US", {
|
|
823
|
+
year: "numeric",
|
|
824
|
+
month: "long",
|
|
825
|
+
day: "numeric"
|
|
826
|
+
});
|
|
827
|
+
}, []);
|
|
828
|
+
const getDaysUntilExpiration = (0, import_react9.useCallback)((endDate) => {
|
|
829
|
+
if (!endDate) return null;
|
|
830
|
+
const now = /* @__PURE__ */ new Date();
|
|
831
|
+
const expiration = new Date(endDate);
|
|
832
|
+
const diffTime = expiration.getTime() - now.getTime();
|
|
833
|
+
const diffDays = Math.ceil(diffTime / (1e3 * 60 * 60 * 24));
|
|
834
|
+
return diffDays > 0 ? diffDays : 0;
|
|
835
|
+
}, []);
|
|
836
|
+
return {
|
|
837
|
+
isPaidPlan,
|
|
838
|
+
activePaidSubscription: subscriptionData.activePaidSubscription,
|
|
839
|
+
cancelledSubscription: subscriptionData.cancelledSubscription,
|
|
840
|
+
hasPaidSubscription: subscriptionData.hasPaidSubscription,
|
|
841
|
+
shouldShowCancelledNotice: subscriptionData.shouldShowCancelledNotice,
|
|
842
|
+
activePlanName: subscriptionData.activePlanName,
|
|
843
|
+
formatDate,
|
|
844
|
+
getDaysUntilExpiration
|
|
845
|
+
};
|
|
846
|
+
}
|
|
524
847
|
// Annotate the CommonJS export names for ESM import in node:
|
|
525
848
|
0 && (module.exports = {
|
|
526
849
|
PaymentForm,
|
|
527
850
|
PlanBadge,
|
|
851
|
+
PlanSelector,
|
|
528
852
|
SolvaPayProvider,
|
|
853
|
+
Spinner,
|
|
854
|
+
StripePaymentFormWrapper,
|
|
529
855
|
SubscriptionGate,
|
|
856
|
+
filterSubscriptions,
|
|
857
|
+
getActiveSubscriptions,
|
|
858
|
+
getCancelledSubscriptionsWithEndDate,
|
|
859
|
+
getMostRecentSubscription,
|
|
860
|
+
getPrimarySubscription,
|
|
861
|
+
hasActivePaidSubscription,
|
|
530
862
|
useCheckout,
|
|
863
|
+
usePlans,
|
|
531
864
|
useSolvaPay,
|
|
532
|
-
useSubscription
|
|
865
|
+
useSubscription,
|
|
866
|
+
useSubscriptionHelpers
|
|
533
867
|
});
|