@solvapay/react 1.0.0-preview.17 → 1.0.0-preview.19
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/LICENSE.md +21 -0
- package/README.md +79 -69
- package/dist/index.cjs +391 -333
- package/dist/index.d.cts +236 -50
- package/dist/index.d.ts +236 -50
- package/dist/index.js +391 -333
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -15,8 +15,7 @@ function getActiveSubscriptions(subscriptions) {
|
|
|
15
15
|
function getCancelledSubscriptionsWithEndDate(subscriptions) {
|
|
16
16
|
const now = /* @__PURE__ */ new Date();
|
|
17
17
|
return subscriptions.filter((sub) => {
|
|
18
|
-
|
|
19
|
-
return sub.status === "active" && subAny.cancelledAt && subAny.endDate && new Date(subAny.endDate) > now;
|
|
18
|
+
return sub.status === "active" && sub.cancelledAt && sub.endDate && new Date(sub.endDate) > now;
|
|
20
19
|
});
|
|
21
20
|
}
|
|
22
21
|
function getMostRecentSubscription(subscriptions) {
|
|
@@ -114,7 +113,9 @@ var SolvaPayProvider = ({
|
|
|
114
113
|
const createPaymentRef = useRef(null);
|
|
115
114
|
const processPaymentRef = useRef(null);
|
|
116
115
|
const configRef = useRef(config);
|
|
117
|
-
const buildDefaultCheckSubscriptionRef = useRef(
|
|
116
|
+
const buildDefaultCheckSubscriptionRef = useRef(
|
|
117
|
+
null
|
|
118
|
+
);
|
|
118
119
|
useEffect(() => {
|
|
119
120
|
configRef.current = config;
|
|
120
121
|
}, [config]);
|
|
@@ -162,77 +163,83 @@ var SolvaPayProvider = ({
|
|
|
162
163
|
useEffect(() => {
|
|
163
164
|
buildDefaultCheckSubscriptionRef.current = buildDefaultCheckSubscription;
|
|
164
165
|
}, [buildDefaultCheckSubscription]);
|
|
165
|
-
const buildDefaultCreatePayment = useCallback(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
headers
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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.agentRef) {
|
|
190
|
+
body.agentRef = params.agentRef;
|
|
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 _checkSubscription = useCallback(async () => {
|
|
236
243
|
if (checkSubscriptionRef.current) {
|
|
237
244
|
return checkSubscriptionRef.current();
|
|
238
245
|
}
|
|
@@ -240,19 +247,25 @@ var SolvaPayProvider = ({
|
|
|
240
247
|
return buildDefaultCheckSubscriptionRef.current();
|
|
241
248
|
}
|
|
242
249
|
return buildDefaultCheckSubscription();
|
|
243
|
-
}, []);
|
|
244
|
-
const createPayment = useCallback(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
250
|
+
}, [buildDefaultCheckSubscription]);
|
|
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
|
+
);
|
|
256
269
|
useEffect(() => {
|
|
257
270
|
const detectAuth = async () => {
|
|
258
271
|
const currentConfig = configRef.current;
|
|
@@ -281,58 +294,61 @@ var SolvaPayProvider = ({
|
|
|
281
294
|
const interval = setInterval(detectAuth, 5e3);
|
|
282
295
|
return () => clearInterval(interval);
|
|
283
296
|
}, [userId]);
|
|
284
|
-
const fetchSubscription = useCallback(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
293
|
-
if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
if (inFlightRef.current === cacheKey) {
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
inFlightRef.current = cacheKey;
|
|
300
|
-
setLoading(true);
|
|
301
|
-
try {
|
|
302
|
-
const checkFn = checkSubscriptionRef.current || buildDefaultCheckSubscriptionRef.current;
|
|
303
|
-
if (!checkFn) {
|
|
304
|
-
throw new Error("checkSubscription function not available");
|
|
305
|
-
}
|
|
306
|
-
const data = await checkFn();
|
|
307
|
-
if (data.customerRef) {
|
|
308
|
-
setInternalCustomerRef(data.customerRef);
|
|
309
|
-
const currentAdapter = getAuthAdapter(configRef.current);
|
|
310
|
-
const currentUserId = await currentAdapter.getUserId();
|
|
311
|
-
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
297
|
+
const fetchSubscription = useCallback(
|
|
298
|
+
async (force = false) => {
|
|
299
|
+
if (!isAuthenticated && !internalCustomerRef) {
|
|
300
|
+
setSubscriptionData({ subscriptions: [] });
|
|
301
|
+
setLoading(false);
|
|
302
|
+
inFlightRef.current = null;
|
|
303
|
+
lastFetchedRef.current = null;
|
|
304
|
+
return;
|
|
312
305
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
317
|
-
};
|
|
318
|
-
setSubscriptionData(filteredData);
|
|
319
|
-
lastFetchedRef.current = cacheKey;
|
|
306
|
+
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
307
|
+
if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
|
|
308
|
+
return;
|
|
320
309
|
}
|
|
321
|
-
} catch (error) {
|
|
322
|
-
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
323
310
|
if (inFlightRef.current === cacheKey) {
|
|
324
|
-
|
|
325
|
-
subscriptions: []
|
|
326
|
-
});
|
|
327
|
-
lastFetchedRef.current = cacheKey;
|
|
311
|
+
return;
|
|
328
312
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
313
|
+
inFlightRef.current = cacheKey;
|
|
314
|
+
setLoading(true);
|
|
315
|
+
try {
|
|
316
|
+
const checkFn = checkSubscriptionRef.current || buildDefaultCheckSubscriptionRef.current;
|
|
317
|
+
if (!checkFn) {
|
|
318
|
+
throw new Error("checkSubscription 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
|
+
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
331
|
+
};
|
|
332
|
+
setSubscriptionData(filteredData);
|
|
333
|
+
lastFetchedRef.current = cacheKey;
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
337
|
+
if (inFlightRef.current === cacheKey) {
|
|
338
|
+
setSubscriptionData({
|
|
339
|
+
subscriptions: []
|
|
340
|
+
});
|
|
341
|
+
lastFetchedRef.current = cacheKey;
|
|
342
|
+
}
|
|
343
|
+
} finally {
|
|
344
|
+
if (inFlightRef.current === cacheKey) {
|
|
345
|
+
setLoading(false);
|
|
346
|
+
inFlightRef.current = null;
|
|
347
|
+
}
|
|
333
348
|
}
|
|
334
|
-
}
|
|
335
|
-
|
|
349
|
+
},
|
|
350
|
+
[isAuthenticated, internalCustomerRef, userId]
|
|
351
|
+
);
|
|
336
352
|
const fetchSubscriptionRef = useRef(fetchSubscription);
|
|
337
353
|
useEffect(() => {
|
|
338
354
|
fetchSubscriptionRef.current = fetchSubscription;
|
|
@@ -353,17 +369,22 @@ var SolvaPayProvider = ({
|
|
|
353
369
|
setLoading(false);
|
|
354
370
|
}
|
|
355
371
|
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
356
|
-
const updateCustomerRef = useCallback(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
372
|
+
const updateCustomerRef = useCallback(
|
|
373
|
+
(newCustomerRef) => {
|
|
374
|
+
setInternalCustomerRef(newCustomerRef);
|
|
375
|
+
setCachedCustomerRef(newCustomerRef, userId);
|
|
376
|
+
fetchSubscription(true);
|
|
377
|
+
},
|
|
378
|
+
[fetchSubscription, userId]
|
|
379
|
+
);
|
|
361
380
|
const subscription = useMemo(() => {
|
|
362
381
|
const activeSubscription = getPrimarySubscription(subscriptionData.subscriptions);
|
|
363
382
|
const activePaidSubscriptions = subscriptionData.subscriptions.filter(
|
|
364
383
|
(sub) => sub.status === "active" && isPaidSubscription(sub)
|
|
365
384
|
);
|
|
366
|
-
const activePaidSubscription = activePaidSubscriptions.sort(
|
|
385
|
+
const activePaidSubscription = activePaidSubscriptions.sort(
|
|
386
|
+
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
387
|
+
)[0] || null;
|
|
367
388
|
return {
|
|
368
389
|
loading,
|
|
369
390
|
customerRef: subscriptionData.customerRef || internalCustomerRef,
|
|
@@ -380,14 +401,25 @@ var SolvaPayProvider = ({
|
|
|
380
401
|
activePaidSubscription
|
|
381
402
|
};
|
|
382
403
|
}, [loading, subscriptionData, internalCustomerRef]);
|
|
383
|
-
const contextValue = useMemo(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
404
|
+
const contextValue = useMemo(
|
|
405
|
+
() => ({
|
|
406
|
+
subscription,
|
|
407
|
+
refetchSubscription,
|
|
408
|
+
createPayment,
|
|
409
|
+
processPayment,
|
|
410
|
+
customerRef: subscriptionData.customerRef || internalCustomerRef,
|
|
411
|
+
updateCustomerRef
|
|
412
|
+
}),
|
|
413
|
+
[
|
|
414
|
+
subscription,
|
|
415
|
+
refetchSubscription,
|
|
416
|
+
createPayment,
|
|
417
|
+
processPayment,
|
|
418
|
+
subscriptionData.customerRef,
|
|
419
|
+
internalCustomerRef,
|
|
420
|
+
updateCustomerRef
|
|
421
|
+
]
|
|
422
|
+
);
|
|
391
423
|
return /* @__PURE__ */ jsx(SolvaPayContext.Provider, { value: contextValue, children });
|
|
392
424
|
};
|
|
393
425
|
|
|
@@ -493,17 +525,6 @@ function useSubscription() {
|
|
|
493
525
|
};
|
|
494
526
|
}
|
|
495
527
|
|
|
496
|
-
// src/hooks/useCustomer.ts
|
|
497
|
-
function useCustomer() {
|
|
498
|
-
const { subscription, customerRef } = useSolvaPay();
|
|
499
|
-
return {
|
|
500
|
-
customerRef: subscription.customerRef || customerRef,
|
|
501
|
-
email: subscription.email,
|
|
502
|
-
name: subscription.name,
|
|
503
|
-
loading: subscription.loading
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
|
|
507
528
|
// src/components/Spinner.tsx
|
|
508
529
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
509
530
|
var Spinner = ({
|
|
@@ -525,17 +546,7 @@ var Spinner = ({
|
|
|
525
546
|
role: "status",
|
|
526
547
|
"aria-busy": "true",
|
|
527
548
|
children: [
|
|
528
|
-
/* @__PURE__ */ jsx2(
|
|
529
|
-
"circle",
|
|
530
|
-
{
|
|
531
|
-
className: "opacity-25",
|
|
532
|
-
cx: "12",
|
|
533
|
-
cy: "12",
|
|
534
|
-
r: "10",
|
|
535
|
-
stroke: "currentColor",
|
|
536
|
-
strokeWidth: "4"
|
|
537
|
-
}
|
|
538
|
-
),
|
|
549
|
+
/* @__PURE__ */ jsx2("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
539
550
|
/* @__PURE__ */ jsx2(
|
|
540
551
|
"path",
|
|
541
552
|
{
|
|
@@ -552,11 +563,24 @@ var Spinner = ({
|
|
|
552
563
|
// src/components/StripePaymentFormWrapper.tsx
|
|
553
564
|
import { useState as useState3 } from "react";
|
|
554
565
|
import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
|
|
566
|
+
|
|
567
|
+
// src/hooks/useCustomer.ts
|
|
568
|
+
function useCustomer() {
|
|
569
|
+
const { subscription, customerRef } = useSolvaPay();
|
|
570
|
+
return {
|
|
571
|
+
customerRef: subscription.customerRef || customerRef,
|
|
572
|
+
email: subscription.email,
|
|
573
|
+
name: subscription.name,
|
|
574
|
+
loading: subscription.loading
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/components/StripePaymentFormWrapper.tsx
|
|
555
579
|
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
556
580
|
var StripePaymentFormWrapper = ({
|
|
557
581
|
onSuccess,
|
|
558
582
|
onError,
|
|
559
|
-
returnUrl,
|
|
583
|
+
returnUrl: _returnUrl,
|
|
560
584
|
submitButtonText = "Pay Now",
|
|
561
585
|
buttonClassName,
|
|
562
586
|
clientSecret
|
|
@@ -681,16 +705,23 @@ var StripePaymentFormWrapper = ({
|
|
|
681
705
|
}
|
|
682
706
|
}
|
|
683
707
|
}
|
|
684
|
-
) : /* @__PURE__ */ jsx3(
|
|
685
|
-
message && !isSuccess && /* @__PURE__ */ jsx3(
|
|
708
|
+
) : /* @__PURE__ */ jsx3(
|
|
686
709
|
"div",
|
|
687
710
|
{
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
711
|
+
style: {
|
|
712
|
+
padding: "12px",
|
|
713
|
+
border: "1px solid #cbd5e1",
|
|
714
|
+
borderRadius: "0.375rem",
|
|
715
|
+
backgroundColor: "white",
|
|
716
|
+
display: "flex",
|
|
717
|
+
alignItems: "center",
|
|
718
|
+
justifyContent: "center",
|
|
719
|
+
minHeight: "40px"
|
|
720
|
+
},
|
|
721
|
+
children: /* @__PURE__ */ jsx3(Spinner, { size: "sm" })
|
|
692
722
|
}
|
|
693
723
|
),
|
|
724
|
+
message && !isSuccess && /* @__PURE__ */ jsx3("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
694
725
|
/* @__PURE__ */ jsx3(
|
|
695
726
|
"button",
|
|
696
727
|
{
|
|
@@ -722,106 +753,118 @@ var PaymentForm = ({
|
|
|
722
753
|
buttonClassName
|
|
723
754
|
}) => {
|
|
724
755
|
const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
|
|
725
|
-
const
|
|
756
|
+
const {
|
|
757
|
+
loading: checkoutLoading,
|
|
758
|
+
error: checkoutError,
|
|
759
|
+
clientSecret,
|
|
760
|
+
startCheckout,
|
|
761
|
+
stripePromise
|
|
762
|
+
} = useCheckout(validPlanRef, agentRef);
|
|
726
763
|
const { refetch } = useSubscription();
|
|
727
|
-
const { processPayment
|
|
728
|
-
const customer = useCustomer();
|
|
764
|
+
const { processPayment } = useSolvaPay();
|
|
729
765
|
const hasInitializedRef = useRef3(false);
|
|
730
766
|
useEffect2(() => {
|
|
731
|
-
if (!hasInitializedRef.current && validPlanRef && !
|
|
767
|
+
if (!hasInitializedRef.current && validPlanRef && !checkoutLoading && !checkoutError && !clientSecret) {
|
|
732
768
|
hasInitializedRef.current = true;
|
|
733
|
-
|
|
769
|
+
startCheckout().catch(() => {
|
|
734
770
|
hasInitializedRef.current = false;
|
|
735
771
|
});
|
|
736
772
|
}
|
|
737
|
-
if (validPlanRef &&
|
|
773
|
+
if (validPlanRef && clientSecret) {
|
|
738
774
|
hasInitializedRef.current = true;
|
|
739
775
|
}
|
|
740
|
-
}, [validPlanRef,
|
|
741
|
-
const handleSuccess = useCallback3(
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
776
|
+
}, [validPlanRef, checkoutLoading, checkoutError, clientSecret, startCheckout]);
|
|
777
|
+
const handleSuccess = useCallback3(
|
|
778
|
+
async (paymentIntent) => {
|
|
779
|
+
let processingTimeout = false;
|
|
780
|
+
let processingResult = null;
|
|
781
|
+
const paymentIntentAny = paymentIntent;
|
|
782
|
+
if (processPayment && agentRef) {
|
|
783
|
+
try {
|
|
784
|
+
const result = await processPayment({
|
|
785
|
+
paymentIntentId: paymentIntentAny.id,
|
|
786
|
+
agentRef,
|
|
787
|
+
planRef
|
|
788
|
+
});
|
|
789
|
+
processingResult = result;
|
|
790
|
+
const isTimeout = result?.status === "timeout";
|
|
791
|
+
processingTimeout = isTimeout;
|
|
792
|
+
if (isTimeout) {
|
|
793
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
794
|
+
const delay = attempt * 1e3;
|
|
795
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
796
|
+
await refetch();
|
|
797
|
+
}
|
|
798
|
+
if (onSuccess) {
|
|
799
|
+
await onSuccess({
|
|
800
|
+
...paymentIntentAny,
|
|
801
|
+
_processingTimeout: processingTimeout,
|
|
802
|
+
_processingResult: processingResult
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
throw new Error("Payment processing timed out");
|
|
806
|
+
} else {
|
|
758
807
|
await refetch();
|
|
759
808
|
}
|
|
809
|
+
} catch (error) {
|
|
810
|
+
console.error("[PaymentForm] Failed to process payment:", error);
|
|
760
811
|
if (onSuccess) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
} else {
|
|
769
|
-
await refetch();
|
|
770
|
-
}
|
|
771
|
-
} catch (error) {
|
|
772
|
-
console.error("[PaymentForm] Failed to process payment:", error);
|
|
773
|
-
if (onSuccess) {
|
|
774
|
-
try {
|
|
775
|
-
await onSuccess({
|
|
776
|
-
...paymentIntent,
|
|
777
|
-
_processingError: error
|
|
778
|
-
});
|
|
779
|
-
} catch (callbackError) {
|
|
812
|
+
try {
|
|
813
|
+
await onSuccess({
|
|
814
|
+
...paymentIntentAny,
|
|
815
|
+
_processingError: error
|
|
816
|
+
});
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
780
819
|
}
|
|
820
|
+
throw error;
|
|
781
821
|
}
|
|
782
|
-
|
|
822
|
+
} else {
|
|
823
|
+
await refetch();
|
|
783
824
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
825
|
+
if (onSuccess && !processingTimeout) {
|
|
826
|
+
await onSuccess({
|
|
827
|
+
...paymentIntentAny,
|
|
828
|
+
_processingTimeout: processingTimeout,
|
|
829
|
+
_processingResult: processingResult
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
[processPayment, agentRef, planRef, refetch, onSuccess]
|
|
834
|
+
);
|
|
835
|
+
const handleError = useCallback3(
|
|
836
|
+
(err) => {
|
|
837
|
+
if (onError) {
|
|
838
|
+
onError(err);
|
|
839
|
+
}
|
|
840
|
+
},
|
|
841
|
+
[onError]
|
|
842
|
+
);
|
|
800
843
|
const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
|
|
801
844
|
const isValidPlanRef = planRef && typeof planRef === "string";
|
|
802
|
-
const hasError = !!
|
|
803
|
-
const hasStripeData = !!(
|
|
845
|
+
const hasError = !!checkoutError;
|
|
846
|
+
const hasStripeData = !!(stripePromise && clientSecret);
|
|
804
847
|
const elementsOptions = useMemo2(() => {
|
|
805
|
-
if (!
|
|
806
|
-
return { clientSecret
|
|
807
|
-
}, [
|
|
848
|
+
if (!clientSecret) return void 0;
|
|
849
|
+
return { clientSecret };
|
|
850
|
+
}, [clientSecret]);
|
|
808
851
|
const [hasMountedElements, setHasMountedElements] = useState4(false);
|
|
809
852
|
useEffect2(() => {
|
|
810
853
|
if (hasStripeData) {
|
|
811
854
|
setHasMountedElements(true);
|
|
812
855
|
}
|
|
813
856
|
}, [hasStripeData]);
|
|
814
|
-
const shouldRenderElements = hasStripeData || hasMountedElements &&
|
|
857
|
+
const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
|
|
815
858
|
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: [
|
|
816
859
|
/* @__PURE__ */ jsx4("div", { children: "Payment initialization failed" }),
|
|
817
|
-
/* @__PURE__ */ jsx4("div", { children:
|
|
818
|
-
] }) : shouldRenderElements &&
|
|
860
|
+
/* @__PURE__ */ jsx4("div", { children: checkoutError?.message || "Unknown error" })
|
|
861
|
+
] }) : shouldRenderElements && elementsOptions ? (
|
|
819
862
|
// Once we have Stripe data, always render Elements to maintain hook consistency
|
|
820
863
|
// This prevents hook count mismatches when transitioning between states
|
|
821
864
|
/* @__PURE__ */ jsx4(
|
|
822
865
|
Elements,
|
|
823
866
|
{
|
|
824
|
-
stripe:
|
|
867
|
+
stripe: stripePromise,
|
|
825
868
|
options: elementsOptions,
|
|
826
869
|
children: /* @__PURE__ */ jsx4(
|
|
827
870
|
StripePaymentFormWrapper,
|
|
@@ -831,16 +874,27 @@ var PaymentForm = ({
|
|
|
831
874
|
returnUrl: finalReturnUrl,
|
|
832
875
|
submitButtonText,
|
|
833
876
|
buttonClassName,
|
|
834
|
-
clientSecret
|
|
877
|
+
clientSecret
|
|
835
878
|
}
|
|
836
879
|
)
|
|
837
880
|
},
|
|
838
|
-
|
|
881
|
+
clientSecret
|
|
839
882
|
)
|
|
840
883
|
) : (
|
|
841
884
|
// Loading state before Stripe data is available
|
|
842
885
|
/* @__PURE__ */ jsxs3("div", { children: [
|
|
843
|
-
/* @__PURE__ */ jsx4(
|
|
886
|
+
/* @__PURE__ */ jsx4(
|
|
887
|
+
"div",
|
|
888
|
+
{
|
|
889
|
+
style: {
|
|
890
|
+
minHeight: "40px",
|
|
891
|
+
display: "flex",
|
|
892
|
+
alignItems: "center",
|
|
893
|
+
justifyContent: "center"
|
|
894
|
+
},
|
|
895
|
+
children: /* @__PURE__ */ jsx4(Spinner, { size: "md" })
|
|
896
|
+
}
|
|
897
|
+
),
|
|
844
898
|
/* @__PURE__ */ jsx4(
|
|
845
899
|
"button",
|
|
846
900
|
{
|
|
@@ -931,10 +985,7 @@ var PlanBadge = ({
|
|
|
931
985
|
|
|
932
986
|
// src/components/SubscriptionGate.tsx
|
|
933
987
|
import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
|
|
934
|
-
var SubscriptionGate = ({
|
|
935
|
-
requirePlan,
|
|
936
|
-
children
|
|
937
|
-
}) => {
|
|
988
|
+
var SubscriptionGate = ({ requirePlan, children }) => {
|
|
938
989
|
const { subscriptions, loading, hasPlan } = useSubscription();
|
|
939
990
|
const hasAccess = requirePlan ? hasPlan(requirePlan) : subscriptions.some((sub) => sub.status === "active");
|
|
940
991
|
return /* @__PURE__ */ jsx6(Fragment3, { children: children({
|
|
@@ -973,99 +1024,99 @@ function usePlans(options) {
|
|
|
973
1024
|
useEffect4(() => {
|
|
974
1025
|
autoSelectFirstPaidRef.current = autoSelectFirstPaid;
|
|
975
1026
|
}, [autoSelectFirstPaid]);
|
|
976
|
-
const fetchPlans = useCallback4(
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
const cached = plansCache.get(agentRef);
|
|
983
|
-
const now = Date.now();
|
|
984
|
-
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
985
|
-
const cachedPlans = cached.plans;
|
|
986
|
-
let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
|
|
987
|
-
if (sortByRef.current) {
|
|
988
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1027
|
+
const fetchPlans = useCallback4(
|
|
1028
|
+
async (force = false) => {
|
|
1029
|
+
if (!agentRef) {
|
|
1030
|
+
setError(new Error("Agent reference not configured"));
|
|
1031
|
+
setLoading(false);
|
|
1032
|
+
return;
|
|
989
1033
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1034
|
+
const cached = plansCache.get(agentRef);
|
|
1035
|
+
const now = Date.now();
|
|
1036
|
+
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1037
|
+
const cachedPlans = cached.plans;
|
|
1038
|
+
let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
|
|
1039
|
+
if (sortByRef.current) {
|
|
1040
|
+
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1041
|
+
}
|
|
1042
|
+
setPlans(processedPlans);
|
|
1043
|
+
setLoading(false);
|
|
1044
|
+
setError(null);
|
|
1045
|
+
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1046
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1047
|
+
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1048
|
+
}
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (cached?.promise) {
|
|
1052
|
+
try {
|
|
1053
|
+
setLoading(true);
|
|
1054
|
+
const fetchedPlans = await cached.promise;
|
|
1055
|
+
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1056
|
+
if (sortByRef.current) {
|
|
1057
|
+
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1058
|
+
}
|
|
1059
|
+
setPlans(processedPlans);
|
|
1060
|
+
setError(null);
|
|
1061
|
+
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1062
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1063
|
+
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1064
|
+
}
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1067
|
+
} finally {
|
|
1068
|
+
setLoading(false);
|
|
1069
|
+
}
|
|
1070
|
+
return;
|
|
998
1071
|
}
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
if (cached?.promise) {
|
|
1002
1072
|
try {
|
|
1003
1073
|
setLoading(true);
|
|
1004
|
-
|
|
1074
|
+
setError(null);
|
|
1075
|
+
const fetchPromise = fetcherRef.current(agentRef);
|
|
1076
|
+
plansCache.set(agentRef, {
|
|
1077
|
+
plans: [],
|
|
1078
|
+
timestamp: now,
|
|
1079
|
+
promise: fetchPromise
|
|
1080
|
+
});
|
|
1081
|
+
const fetchedPlans = await fetchPromise;
|
|
1082
|
+
plansCache.set(agentRef, {
|
|
1083
|
+
plans: fetchedPlans,
|
|
1084
|
+
timestamp: now,
|
|
1085
|
+
promise: null
|
|
1086
|
+
});
|
|
1005
1087
|
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1006
1088
|
if (sortByRef.current) {
|
|
1007
1089
|
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1008
1090
|
}
|
|
1009
1091
|
setPlans(processedPlans);
|
|
1010
|
-
setError(null);
|
|
1011
1092
|
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1012
|
-
const firstPaidIndex = processedPlans.findIndex(
|
|
1013
|
-
(plan) => plan.price && plan.price > 0
|
|
1014
|
-
);
|
|
1093
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1015
1094
|
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1016
1095
|
}
|
|
1017
1096
|
} catch (err) {
|
|
1097
|
+
plansCache.delete(agentRef);
|
|
1018
1098
|
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1019
1099
|
} finally {
|
|
1020
1100
|
setLoading(false);
|
|
1021
1101
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
setLoading(true);
|
|
1026
|
-
setError(null);
|
|
1027
|
-
const fetchPromise = fetcherRef.current(agentRef);
|
|
1028
|
-
plansCache.set(agentRef, {
|
|
1029
|
-
plans: [],
|
|
1030
|
-
timestamp: now,
|
|
1031
|
-
promise: fetchPromise
|
|
1032
|
-
});
|
|
1033
|
-
const fetchedPlans = await fetchPromise;
|
|
1034
|
-
plansCache.set(agentRef, {
|
|
1035
|
-
plans: fetchedPlans,
|
|
1036
|
-
timestamp: now,
|
|
1037
|
-
promise: null
|
|
1038
|
-
});
|
|
1039
|
-
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1040
|
-
if (sortByRef.current) {
|
|
1041
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1042
|
-
}
|
|
1043
|
-
setPlans(processedPlans);
|
|
1044
|
-
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1045
|
-
const firstPaidIndex = processedPlans.findIndex(
|
|
1046
|
-
(plan) => plan.price && plan.price > 0
|
|
1047
|
-
);
|
|
1048
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1049
|
-
}
|
|
1050
|
-
} catch (err) {
|
|
1051
|
-
plansCache.delete(agentRef);
|
|
1052
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1053
|
-
} finally {
|
|
1054
|
-
setLoading(false);
|
|
1055
|
-
}
|
|
1056
|
-
}, [agentRef]);
|
|
1102
|
+
},
|
|
1103
|
+
[agentRef]
|
|
1104
|
+
);
|
|
1057
1105
|
useEffect4(() => {
|
|
1058
1106
|
fetchPlans();
|
|
1059
1107
|
}, [fetchPlans]);
|
|
1060
1108
|
const selectedPlan = useMemo3(() => {
|
|
1061
1109
|
return plans[selectedPlanIndex] || null;
|
|
1062
1110
|
}, [plans, selectedPlanIndex]);
|
|
1063
|
-
const selectPlan = useCallback4(
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1111
|
+
const selectPlan = useCallback4(
|
|
1112
|
+
(planRef) => {
|
|
1113
|
+
const index = plans.findIndex((p) => p.reference === planRef);
|
|
1114
|
+
if (index >= 0) {
|
|
1115
|
+
setSelectedPlanIndex(index);
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
[plans]
|
|
1119
|
+
);
|
|
1069
1120
|
return {
|
|
1070
1121
|
plans,
|
|
1071
1122
|
loading,
|
|
@@ -1098,19 +1149,25 @@ var PlanSelector = ({
|
|
|
1098
1149
|
autoSelectFirstPaid
|
|
1099
1150
|
});
|
|
1100
1151
|
const { plans } = plansHook;
|
|
1101
|
-
const isPaidPlan = useCallback5(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1152
|
+
const isPaidPlan = useCallback5(
|
|
1153
|
+
(planName) => {
|
|
1154
|
+
const plan = plans.find((p) => p.name === planName);
|
|
1155
|
+
return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
|
|
1156
|
+
},
|
|
1157
|
+
[plans]
|
|
1158
|
+
);
|
|
1105
1159
|
const activeSubscription = useMemo4(() => {
|
|
1106
1160
|
const activeSubs = subscriptions.filter((sub) => sub.status === "active");
|
|
1107
1161
|
return activeSubs.sort(
|
|
1108
1162
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1109
1163
|
)[0] || null;
|
|
1110
1164
|
}, [subscriptions]);
|
|
1111
|
-
const isCurrentPlan = useCallback5(
|
|
1112
|
-
|
|
1113
|
-
|
|
1165
|
+
const isCurrentPlan = useCallback5(
|
|
1166
|
+
(planName) => {
|
|
1167
|
+
return activeSubscription?.planName === planName;
|
|
1168
|
+
},
|
|
1169
|
+
[activeSubscription]
|
|
1170
|
+
);
|
|
1114
1171
|
return /* @__PURE__ */ jsx7(Fragment4, { children: children({
|
|
1115
1172
|
...plansHook,
|
|
1116
1173
|
subscriptions,
|
|
@@ -1128,10 +1185,11 @@ function useSubscriptionStatus() {
|
|
|
1128
1185
|
}, []);
|
|
1129
1186
|
const subscriptionData = useMemo5(() => {
|
|
1130
1187
|
const cancelledPaidSubscriptions = subscriptions.filter((sub) => {
|
|
1131
|
-
|
|
1132
|
-
return sub.status === "active" && subAny.cancelledAt && isPaidSubscription2(sub);
|
|
1188
|
+
return sub.status === "active" && sub.cancelledAt && isPaidSubscription2(sub);
|
|
1133
1189
|
});
|
|
1134
|
-
const cancelledSubscription = cancelledPaidSubscriptions.sort(
|
|
1190
|
+
const cancelledSubscription = cancelledPaidSubscriptions.sort(
|
|
1191
|
+
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1192
|
+
)[0] || null;
|
|
1135
1193
|
const shouldShowCancelledNotice = !!cancelledSubscription;
|
|
1136
1194
|
return {
|
|
1137
1195
|
cancelledSubscription,
|