@solvapay/react 1.0.0-preview.18 → 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.cjs
CHANGED
|
@@ -56,8 +56,7 @@ function getActiveSubscriptions(subscriptions) {
|
|
|
56
56
|
function getCancelledSubscriptionsWithEndDate(subscriptions) {
|
|
57
57
|
const now = /* @__PURE__ */ new Date();
|
|
58
58
|
return subscriptions.filter((sub) => {
|
|
59
|
-
|
|
60
|
-
return sub.status === "active" && subAny.cancelledAt && subAny.endDate && new Date(subAny.endDate) > now;
|
|
59
|
+
return sub.status === "active" && sub.cancelledAt && sub.endDate && new Date(sub.endDate) > now;
|
|
61
60
|
});
|
|
62
61
|
}
|
|
63
62
|
function getMostRecentSubscription(subscriptions) {
|
|
@@ -177,7 +176,9 @@ var SolvaPayProvider = ({
|
|
|
177
176
|
const createPaymentRef = (0, import_react.useRef)(null);
|
|
178
177
|
const processPaymentRef = (0, import_react.useRef)(null);
|
|
179
178
|
const configRef = (0, import_react.useRef)(config);
|
|
180
|
-
const buildDefaultCheckSubscriptionRef = (0, import_react.useRef)(
|
|
179
|
+
const buildDefaultCheckSubscriptionRef = (0, import_react.useRef)(
|
|
180
|
+
null
|
|
181
|
+
);
|
|
181
182
|
(0, import_react.useEffect)(() => {
|
|
182
183
|
configRef.current = config;
|
|
183
184
|
}, [config]);
|
|
@@ -225,77 +226,83 @@ var SolvaPayProvider = ({
|
|
|
225
226
|
(0, import_react.useEffect)(() => {
|
|
226
227
|
buildDefaultCheckSubscriptionRef.current = buildDefaultCheckSubscription;
|
|
227
228
|
}, [buildDefaultCheckSubscription]);
|
|
228
|
-
const buildDefaultCreatePayment = (0, import_react.useCallback)(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
headers
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
229
|
+
const buildDefaultCreatePayment = (0, import_react.useCallback)(
|
|
230
|
+
async (params) => {
|
|
231
|
+
const currentConfig = configRef.current;
|
|
232
|
+
const adapter = getAuthAdapter(currentConfig);
|
|
233
|
+
const token = await adapter.getToken();
|
|
234
|
+
const detectedUserId = await adapter.getUserId();
|
|
235
|
+
const route = currentConfig?.api?.createPayment || "/api/create-payment-intent";
|
|
236
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
237
|
+
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
238
|
+
const headers = {
|
|
239
|
+
"Content-Type": "application/json"
|
|
240
|
+
};
|
|
241
|
+
if (token) {
|
|
242
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
243
|
+
}
|
|
244
|
+
if (cachedRef) {
|
|
245
|
+
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
246
|
+
}
|
|
247
|
+
if (currentConfig?.headers) {
|
|
248
|
+
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
249
|
+
Object.assign(headers, customHeaders);
|
|
250
|
+
}
|
|
251
|
+
const body = { planRef: params.planRef };
|
|
252
|
+
if (params.agentRef) {
|
|
253
|
+
body.agentRef = params.agentRef;
|
|
254
|
+
}
|
|
255
|
+
const res = await fetchFn(route, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers,
|
|
258
|
+
body: JSON.stringify(body)
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
const error = new Error(`Failed to create payment: ${res.statusText}`);
|
|
262
|
+
currentConfig?.onError?.(error, "createPayment");
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
return res.json();
|
|
266
|
+
},
|
|
267
|
+
[]
|
|
268
|
+
);
|
|
269
|
+
const buildDefaultProcessPayment = (0, import_react.useCallback)(
|
|
270
|
+
async (params) => {
|
|
271
|
+
const currentConfig = configRef.current;
|
|
272
|
+
const adapter = getAuthAdapter(currentConfig);
|
|
273
|
+
const token = await adapter.getToken();
|
|
274
|
+
const detectedUserId = await adapter.getUserId();
|
|
275
|
+
const route = currentConfig?.api?.processPayment || "/api/process-payment";
|
|
276
|
+
const fetchFn = currentConfig?.fetch || fetch;
|
|
277
|
+
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
278
|
+
const headers = {
|
|
279
|
+
"Content-Type": "application/json"
|
|
280
|
+
};
|
|
281
|
+
if (token) {
|
|
282
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
283
|
+
}
|
|
284
|
+
if (cachedRef) {
|
|
285
|
+
headers["x-solvapay-customer-ref"] = cachedRef;
|
|
286
|
+
}
|
|
287
|
+
if (currentConfig?.headers) {
|
|
288
|
+
const customHeaders = typeof currentConfig.headers === "function" ? await currentConfig.headers() : currentConfig.headers;
|
|
289
|
+
Object.assign(headers, customHeaders);
|
|
290
|
+
}
|
|
291
|
+
const res = await fetchFn(route, {
|
|
292
|
+
method: "POST",
|
|
293
|
+
headers,
|
|
294
|
+
body: JSON.stringify(params)
|
|
295
|
+
});
|
|
296
|
+
if (!res.ok) {
|
|
297
|
+
const error = new Error(`Failed to process payment: ${res.statusText}`);
|
|
298
|
+
currentConfig?.onError?.(error, "processPayment");
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
return res.json();
|
|
302
|
+
},
|
|
303
|
+
[]
|
|
304
|
+
);
|
|
305
|
+
const _checkSubscription = (0, import_react.useCallback)(async () => {
|
|
299
306
|
if (checkSubscriptionRef.current) {
|
|
300
307
|
return checkSubscriptionRef.current();
|
|
301
308
|
}
|
|
@@ -303,19 +310,25 @@ var SolvaPayProvider = ({
|
|
|
303
310
|
return buildDefaultCheckSubscriptionRef.current();
|
|
304
311
|
}
|
|
305
312
|
return buildDefaultCheckSubscription();
|
|
306
|
-
}, []);
|
|
307
|
-
const createPayment = (0, import_react.useCallback)(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
313
|
+
}, [buildDefaultCheckSubscription]);
|
|
314
|
+
const createPayment = (0, import_react.useCallback)(
|
|
315
|
+
async (params) => {
|
|
316
|
+
if (createPaymentRef.current) {
|
|
317
|
+
return createPaymentRef.current(params);
|
|
318
|
+
}
|
|
319
|
+
return buildDefaultCreatePayment(params);
|
|
320
|
+
},
|
|
321
|
+
[buildDefaultCreatePayment]
|
|
322
|
+
);
|
|
323
|
+
const processPayment = (0, import_react.useCallback)(
|
|
324
|
+
async (params) => {
|
|
325
|
+
if (processPaymentRef.current) {
|
|
326
|
+
return processPaymentRef.current(params);
|
|
327
|
+
}
|
|
328
|
+
return buildDefaultProcessPayment(params);
|
|
329
|
+
},
|
|
330
|
+
[buildDefaultProcessPayment]
|
|
331
|
+
);
|
|
319
332
|
(0, import_react.useEffect)(() => {
|
|
320
333
|
const detectAuth = async () => {
|
|
321
334
|
const currentConfig = configRef.current;
|
|
@@ -344,58 +357,61 @@ var SolvaPayProvider = ({
|
|
|
344
357
|
const interval = setInterval(detectAuth, 5e3);
|
|
345
358
|
return () => clearInterval(interval);
|
|
346
359
|
}, [userId]);
|
|
347
|
-
const fetchSubscription = (0, import_react.useCallback)(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
356
|
-
if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
if (inFlightRef.current === cacheKey) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
inFlightRef.current = cacheKey;
|
|
363
|
-
setLoading(true);
|
|
364
|
-
try {
|
|
365
|
-
const checkFn = checkSubscriptionRef.current || buildDefaultCheckSubscriptionRef.current;
|
|
366
|
-
if (!checkFn) {
|
|
367
|
-
throw new Error("checkSubscription function not available");
|
|
368
|
-
}
|
|
369
|
-
const data = await checkFn();
|
|
370
|
-
if (data.customerRef) {
|
|
371
|
-
setInternalCustomerRef(data.customerRef);
|
|
372
|
-
const currentAdapter = getAuthAdapter(configRef.current);
|
|
373
|
-
const currentUserId = await currentAdapter.getUserId();
|
|
374
|
-
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
360
|
+
const fetchSubscription = (0, import_react.useCallback)(
|
|
361
|
+
async (force = false) => {
|
|
362
|
+
if (!isAuthenticated && !internalCustomerRef) {
|
|
363
|
+
setSubscriptionData({ subscriptions: [] });
|
|
364
|
+
setLoading(false);
|
|
365
|
+
inFlightRef.current = null;
|
|
366
|
+
lastFetchedRef.current = null;
|
|
367
|
+
return;
|
|
375
368
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
380
|
-
};
|
|
381
|
-
setSubscriptionData(filteredData);
|
|
382
|
-
lastFetchedRef.current = cacheKey;
|
|
369
|
+
const cacheKey = internalCustomerRef || userId || "anonymous";
|
|
370
|
+
if (!force && lastFetchedRef.current === cacheKey && inFlightRef.current !== cacheKey) {
|
|
371
|
+
return;
|
|
383
372
|
}
|
|
384
|
-
} catch (error) {
|
|
385
|
-
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
386
373
|
if (inFlightRef.current === cacheKey) {
|
|
387
|
-
|
|
388
|
-
subscriptions: []
|
|
389
|
-
});
|
|
390
|
-
lastFetchedRef.current = cacheKey;
|
|
374
|
+
return;
|
|
391
375
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
376
|
+
inFlightRef.current = cacheKey;
|
|
377
|
+
setLoading(true);
|
|
378
|
+
try {
|
|
379
|
+
const checkFn = checkSubscriptionRef.current || buildDefaultCheckSubscriptionRef.current;
|
|
380
|
+
if (!checkFn) {
|
|
381
|
+
throw new Error("checkSubscription function not available");
|
|
382
|
+
}
|
|
383
|
+
const data = await checkFn();
|
|
384
|
+
if (data.customerRef) {
|
|
385
|
+
setInternalCustomerRef(data.customerRef);
|
|
386
|
+
const currentAdapter = getAuthAdapter(configRef.current);
|
|
387
|
+
const currentUserId = await currentAdapter.getUserId();
|
|
388
|
+
setCachedCustomerRef(data.customerRef, currentUserId);
|
|
389
|
+
}
|
|
390
|
+
if (inFlightRef.current === cacheKey) {
|
|
391
|
+
const filteredData = {
|
|
392
|
+
...data,
|
|
393
|
+
subscriptions: filterSubscriptions(data.subscriptions || [])
|
|
394
|
+
};
|
|
395
|
+
setSubscriptionData(filteredData);
|
|
396
|
+
lastFetchedRef.current = cacheKey;
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error("[SolvaPayProvider] Failed to fetch subscription:", error);
|
|
400
|
+
if (inFlightRef.current === cacheKey) {
|
|
401
|
+
setSubscriptionData({
|
|
402
|
+
subscriptions: []
|
|
403
|
+
});
|
|
404
|
+
lastFetchedRef.current = cacheKey;
|
|
405
|
+
}
|
|
406
|
+
} finally {
|
|
407
|
+
if (inFlightRef.current === cacheKey) {
|
|
408
|
+
setLoading(false);
|
|
409
|
+
inFlightRef.current = null;
|
|
410
|
+
}
|
|
396
411
|
}
|
|
397
|
-
}
|
|
398
|
-
|
|
412
|
+
},
|
|
413
|
+
[isAuthenticated, internalCustomerRef, userId]
|
|
414
|
+
);
|
|
399
415
|
const fetchSubscriptionRef = (0, import_react.useRef)(fetchSubscription);
|
|
400
416
|
(0, import_react.useEffect)(() => {
|
|
401
417
|
fetchSubscriptionRef.current = fetchSubscription;
|
|
@@ -416,17 +432,22 @@ var SolvaPayProvider = ({
|
|
|
416
432
|
setLoading(false);
|
|
417
433
|
}
|
|
418
434
|
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
419
|
-
const updateCustomerRef = (0, import_react.useCallback)(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
435
|
+
const updateCustomerRef = (0, import_react.useCallback)(
|
|
436
|
+
(newCustomerRef) => {
|
|
437
|
+
setInternalCustomerRef(newCustomerRef);
|
|
438
|
+
setCachedCustomerRef(newCustomerRef, userId);
|
|
439
|
+
fetchSubscription(true);
|
|
440
|
+
},
|
|
441
|
+
[fetchSubscription, userId]
|
|
442
|
+
);
|
|
424
443
|
const subscription = (0, import_react.useMemo)(() => {
|
|
425
444
|
const activeSubscription = getPrimarySubscription(subscriptionData.subscriptions);
|
|
426
445
|
const activePaidSubscriptions = subscriptionData.subscriptions.filter(
|
|
427
446
|
(sub) => sub.status === "active" && isPaidSubscription(sub)
|
|
428
447
|
);
|
|
429
|
-
const activePaidSubscription = activePaidSubscriptions.sort(
|
|
448
|
+
const activePaidSubscription = activePaidSubscriptions.sort(
|
|
449
|
+
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
450
|
+
)[0] || null;
|
|
430
451
|
return {
|
|
431
452
|
loading,
|
|
432
453
|
customerRef: subscriptionData.customerRef || internalCustomerRef,
|
|
@@ -443,14 +464,25 @@ var SolvaPayProvider = ({
|
|
|
443
464
|
activePaidSubscription
|
|
444
465
|
};
|
|
445
466
|
}, [loading, subscriptionData, internalCustomerRef]);
|
|
446
|
-
const contextValue = (0, import_react.useMemo)(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
467
|
+
const contextValue = (0, import_react.useMemo)(
|
|
468
|
+
() => ({
|
|
469
|
+
subscription,
|
|
470
|
+
refetchSubscription,
|
|
471
|
+
createPayment,
|
|
472
|
+
processPayment,
|
|
473
|
+
customerRef: subscriptionData.customerRef || internalCustomerRef,
|
|
474
|
+
updateCustomerRef
|
|
475
|
+
}),
|
|
476
|
+
[
|
|
477
|
+
subscription,
|
|
478
|
+
refetchSubscription,
|
|
479
|
+
createPayment,
|
|
480
|
+
processPayment,
|
|
481
|
+
subscriptionData.customerRef,
|
|
482
|
+
internalCustomerRef,
|
|
483
|
+
updateCustomerRef
|
|
484
|
+
]
|
|
485
|
+
);
|
|
454
486
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SolvaPayContext.Provider, { value: contextValue, children });
|
|
455
487
|
};
|
|
456
488
|
|
|
@@ -556,17 +588,6 @@ function useSubscription() {
|
|
|
556
588
|
};
|
|
557
589
|
}
|
|
558
590
|
|
|
559
|
-
// src/hooks/useCustomer.ts
|
|
560
|
-
function useCustomer() {
|
|
561
|
-
const { subscription, customerRef } = useSolvaPay();
|
|
562
|
-
return {
|
|
563
|
-
customerRef: subscription.customerRef || customerRef,
|
|
564
|
-
email: subscription.email,
|
|
565
|
-
name: subscription.name,
|
|
566
|
-
loading: subscription.loading
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
|
|
570
591
|
// src/components/Spinner.tsx
|
|
571
592
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
572
593
|
var Spinner = ({
|
|
@@ -588,17 +609,7 @@ var Spinner = ({
|
|
|
588
609
|
role: "status",
|
|
589
610
|
"aria-busy": "true",
|
|
590
611
|
children: [
|
|
591
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
592
|
-
"circle",
|
|
593
|
-
{
|
|
594
|
-
className: "opacity-25",
|
|
595
|
-
cx: "12",
|
|
596
|
-
cy: "12",
|
|
597
|
-
r: "10",
|
|
598
|
-
stroke: "currentColor",
|
|
599
|
-
strokeWidth: "4"
|
|
600
|
-
}
|
|
601
|
-
),
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
|
|
602
613
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
603
614
|
"path",
|
|
604
615
|
{
|
|
@@ -615,11 +626,24 @@ var Spinner = ({
|
|
|
615
626
|
// src/components/StripePaymentFormWrapper.tsx
|
|
616
627
|
var import_react4 = require("react");
|
|
617
628
|
var import_react_stripe_js = require("@stripe/react-stripe-js");
|
|
629
|
+
|
|
630
|
+
// src/hooks/useCustomer.ts
|
|
631
|
+
function useCustomer() {
|
|
632
|
+
const { subscription, customerRef } = useSolvaPay();
|
|
633
|
+
return {
|
|
634
|
+
customerRef: subscription.customerRef || customerRef,
|
|
635
|
+
email: subscription.email,
|
|
636
|
+
name: subscription.name,
|
|
637
|
+
loading: subscription.loading
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/components/StripePaymentFormWrapper.tsx
|
|
618
642
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
619
643
|
var StripePaymentFormWrapper = ({
|
|
620
644
|
onSuccess,
|
|
621
645
|
onError,
|
|
622
|
-
returnUrl,
|
|
646
|
+
returnUrl: _returnUrl,
|
|
623
647
|
submitButtonText = "Pay Now",
|
|
624
648
|
buttonClassName,
|
|
625
649
|
clientSecret
|
|
@@ -744,16 +768,23 @@ var StripePaymentFormWrapper = ({
|
|
|
744
768
|
}
|
|
745
769
|
}
|
|
746
770
|
}
|
|
747
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
748
|
-
message && !isSuccess && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
771
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
749
772
|
"div",
|
|
750
773
|
{
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
774
|
+
style: {
|
|
775
|
+
padding: "12px",
|
|
776
|
+
border: "1px solid #cbd5e1",
|
|
777
|
+
borderRadius: "0.375rem",
|
|
778
|
+
backgroundColor: "white",
|
|
779
|
+
display: "flex",
|
|
780
|
+
alignItems: "center",
|
|
781
|
+
justifyContent: "center",
|
|
782
|
+
minHeight: "40px"
|
|
783
|
+
},
|
|
784
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Spinner, { size: "sm" })
|
|
755
785
|
}
|
|
756
786
|
),
|
|
787
|
+
message && !isSuccess && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "true", children: message }),
|
|
757
788
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
758
789
|
"button",
|
|
759
790
|
{
|
|
@@ -785,106 +816,118 @@ var PaymentForm = ({
|
|
|
785
816
|
buttonClassName
|
|
786
817
|
}) => {
|
|
787
818
|
const validPlanRef = planRef && typeof planRef === "string" ? planRef : "";
|
|
788
|
-
const
|
|
819
|
+
const {
|
|
820
|
+
loading: checkoutLoading,
|
|
821
|
+
error: checkoutError,
|
|
822
|
+
clientSecret,
|
|
823
|
+
startCheckout,
|
|
824
|
+
stripePromise
|
|
825
|
+
} = useCheckout(validPlanRef, agentRef);
|
|
789
826
|
const { refetch } = useSubscription();
|
|
790
|
-
const { processPayment
|
|
791
|
-
const customer = useCustomer();
|
|
827
|
+
const { processPayment } = useSolvaPay();
|
|
792
828
|
const hasInitializedRef = (0, import_react5.useRef)(false);
|
|
793
829
|
(0, import_react5.useEffect)(() => {
|
|
794
|
-
if (!hasInitializedRef.current && validPlanRef && !
|
|
830
|
+
if (!hasInitializedRef.current && validPlanRef && !checkoutLoading && !checkoutError && !clientSecret) {
|
|
795
831
|
hasInitializedRef.current = true;
|
|
796
|
-
|
|
832
|
+
startCheckout().catch(() => {
|
|
797
833
|
hasInitializedRef.current = false;
|
|
798
834
|
});
|
|
799
835
|
}
|
|
800
|
-
if (validPlanRef &&
|
|
836
|
+
if (validPlanRef && clientSecret) {
|
|
801
837
|
hasInitializedRef.current = true;
|
|
802
838
|
}
|
|
803
|
-
}, [validPlanRef,
|
|
804
|
-
const handleSuccess = (0, import_react5.useCallback)(
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
839
|
+
}, [validPlanRef, checkoutLoading, checkoutError, clientSecret, startCheckout]);
|
|
840
|
+
const handleSuccess = (0, import_react5.useCallback)(
|
|
841
|
+
async (paymentIntent) => {
|
|
842
|
+
let processingTimeout = false;
|
|
843
|
+
let processingResult = null;
|
|
844
|
+
const paymentIntentAny = paymentIntent;
|
|
845
|
+
if (processPayment && agentRef) {
|
|
846
|
+
try {
|
|
847
|
+
const result = await processPayment({
|
|
848
|
+
paymentIntentId: paymentIntentAny.id,
|
|
849
|
+
agentRef,
|
|
850
|
+
planRef
|
|
851
|
+
});
|
|
852
|
+
processingResult = result;
|
|
853
|
+
const isTimeout = result?.status === "timeout";
|
|
854
|
+
processingTimeout = isTimeout;
|
|
855
|
+
if (isTimeout) {
|
|
856
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
857
|
+
const delay = attempt * 1e3;
|
|
858
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
859
|
+
await refetch();
|
|
860
|
+
}
|
|
861
|
+
if (onSuccess) {
|
|
862
|
+
await onSuccess({
|
|
863
|
+
...paymentIntentAny,
|
|
864
|
+
_processingTimeout: processingTimeout,
|
|
865
|
+
_processingResult: processingResult
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
throw new Error("Payment processing timed out");
|
|
869
|
+
} else {
|
|
821
870
|
await refetch();
|
|
822
871
|
}
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.error("[PaymentForm] Failed to process payment:", error);
|
|
823
874
|
if (onSuccess) {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
} else {
|
|
832
|
-
await refetch();
|
|
833
|
-
}
|
|
834
|
-
} catch (error) {
|
|
835
|
-
console.error("[PaymentForm] Failed to process payment:", error);
|
|
836
|
-
if (onSuccess) {
|
|
837
|
-
try {
|
|
838
|
-
await onSuccess({
|
|
839
|
-
...paymentIntent,
|
|
840
|
-
_processingError: error
|
|
841
|
-
});
|
|
842
|
-
} catch (callbackError) {
|
|
875
|
+
try {
|
|
876
|
+
await onSuccess({
|
|
877
|
+
...paymentIntentAny,
|
|
878
|
+
_processingError: error
|
|
879
|
+
});
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
843
882
|
}
|
|
883
|
+
throw error;
|
|
844
884
|
}
|
|
845
|
-
|
|
885
|
+
} else {
|
|
886
|
+
await refetch();
|
|
846
887
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
888
|
+
if (onSuccess && !processingTimeout) {
|
|
889
|
+
await onSuccess({
|
|
890
|
+
...paymentIntentAny,
|
|
891
|
+
_processingTimeout: processingTimeout,
|
|
892
|
+
_processingResult: processingResult
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
},
|
|
896
|
+
[processPayment, agentRef, planRef, refetch, onSuccess]
|
|
897
|
+
);
|
|
898
|
+
const handleError = (0, import_react5.useCallback)(
|
|
899
|
+
(err) => {
|
|
900
|
+
if (onError) {
|
|
901
|
+
onError(err);
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
[onError]
|
|
905
|
+
);
|
|
863
906
|
const finalReturnUrl = returnUrl || (typeof window !== "undefined" ? window.location.href : "/");
|
|
864
907
|
const isValidPlanRef = planRef && typeof planRef === "string";
|
|
865
|
-
const hasError = !!
|
|
866
|
-
const hasStripeData = !!(
|
|
908
|
+
const hasError = !!checkoutError;
|
|
909
|
+
const hasStripeData = !!(stripePromise && clientSecret);
|
|
867
910
|
const elementsOptions = (0, import_react5.useMemo)(() => {
|
|
868
|
-
if (!
|
|
869
|
-
return { clientSecret
|
|
870
|
-
}, [
|
|
911
|
+
if (!clientSecret) return void 0;
|
|
912
|
+
return { clientSecret };
|
|
913
|
+
}, [clientSecret]);
|
|
871
914
|
const [hasMountedElements, setHasMountedElements] = (0, import_react5.useState)(false);
|
|
872
915
|
(0, import_react5.useEffect)(() => {
|
|
873
916
|
if (hasStripeData) {
|
|
874
917
|
setHasMountedElements(true);
|
|
875
918
|
}
|
|
876
919
|
}, [hasStripeData]);
|
|
877
|
-
const shouldRenderElements = hasStripeData || hasMountedElements &&
|
|
920
|
+
const shouldRenderElements = hasStripeData || hasMountedElements && stripePromise && clientSecret;
|
|
878
921
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, children: !isValidPlanRef ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "PaymentForm: planRef is required and must be a string" }) : hasError ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
879
922
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: "Payment initialization failed" }),
|
|
880
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children:
|
|
881
|
-
] }) : shouldRenderElements &&
|
|
923
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: checkoutError?.message || "Unknown error" })
|
|
924
|
+
] }) : shouldRenderElements && elementsOptions ? (
|
|
882
925
|
// Once we have Stripe data, always render Elements to maintain hook consistency
|
|
883
926
|
// This prevents hook count mismatches when transitioning between states
|
|
884
927
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
885
928
|
import_react_stripe_js2.Elements,
|
|
886
929
|
{
|
|
887
|
-
stripe:
|
|
930
|
+
stripe: stripePromise,
|
|
888
931
|
options: elementsOptions,
|
|
889
932
|
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
890
933
|
StripePaymentFormWrapper,
|
|
@@ -894,16 +937,27 @@ var PaymentForm = ({
|
|
|
894
937
|
returnUrl: finalReturnUrl,
|
|
895
938
|
submitButtonText,
|
|
896
939
|
buttonClassName,
|
|
897
|
-
clientSecret
|
|
940
|
+
clientSecret
|
|
898
941
|
}
|
|
899
942
|
)
|
|
900
943
|
},
|
|
901
|
-
|
|
944
|
+
clientSecret
|
|
902
945
|
)
|
|
903
946
|
) : (
|
|
904
947
|
// Loading state before Stripe data is available
|
|
905
948
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
906
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
950
|
+
"div",
|
|
951
|
+
{
|
|
952
|
+
style: {
|
|
953
|
+
minHeight: "40px",
|
|
954
|
+
display: "flex",
|
|
955
|
+
alignItems: "center",
|
|
956
|
+
justifyContent: "center"
|
|
957
|
+
},
|
|
958
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Spinner, { size: "md" })
|
|
959
|
+
}
|
|
960
|
+
),
|
|
907
961
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
908
962
|
"button",
|
|
909
963
|
{
|
|
@@ -994,10 +1048,7 @@ var PlanBadge = ({
|
|
|
994
1048
|
|
|
995
1049
|
// src/components/SubscriptionGate.tsx
|
|
996
1050
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
997
|
-
var SubscriptionGate = ({
|
|
998
|
-
requirePlan,
|
|
999
|
-
children
|
|
1000
|
-
}) => {
|
|
1051
|
+
var SubscriptionGate = ({ requirePlan, children }) => {
|
|
1001
1052
|
const { subscriptions, loading, hasPlan } = useSubscription();
|
|
1002
1053
|
const hasAccess = requirePlan ? hasPlan(requirePlan) : subscriptions.some((sub) => sub.status === "active");
|
|
1003
1054
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: children({
|
|
@@ -1036,99 +1087,99 @@ function usePlans(options) {
|
|
|
1036
1087
|
(0, import_react7.useEffect)(() => {
|
|
1037
1088
|
autoSelectFirstPaidRef.current = autoSelectFirstPaid;
|
|
1038
1089
|
}, [autoSelectFirstPaid]);
|
|
1039
|
-
const fetchPlans = (0, import_react7.useCallback)(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
const cached = plansCache.get(agentRef);
|
|
1046
|
-
const now = Date.now();
|
|
1047
|
-
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1048
|
-
const cachedPlans = cached.plans;
|
|
1049
|
-
let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
|
|
1050
|
-
if (sortByRef.current) {
|
|
1051
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1090
|
+
const fetchPlans = (0, import_react7.useCallback)(
|
|
1091
|
+
async (force = false) => {
|
|
1092
|
+
if (!agentRef) {
|
|
1093
|
+
setError(new Error("Agent reference not configured"));
|
|
1094
|
+
setLoading(false);
|
|
1095
|
+
return;
|
|
1052
1096
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1097
|
+
const cached = plansCache.get(agentRef);
|
|
1098
|
+
const now = Date.now();
|
|
1099
|
+
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1100
|
+
const cachedPlans = cached.plans;
|
|
1101
|
+
let processedPlans = filterRef.current ? cachedPlans.filter(filterRef.current) : cachedPlans;
|
|
1102
|
+
if (sortByRef.current) {
|
|
1103
|
+
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1104
|
+
}
|
|
1105
|
+
setPlans(processedPlans);
|
|
1106
|
+
setLoading(false);
|
|
1107
|
+
setError(null);
|
|
1108
|
+
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1109
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1110
|
+
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1111
|
+
}
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (cached?.promise) {
|
|
1115
|
+
try {
|
|
1116
|
+
setLoading(true);
|
|
1117
|
+
const fetchedPlans = await cached.promise;
|
|
1118
|
+
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1119
|
+
if (sortByRef.current) {
|
|
1120
|
+
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1121
|
+
}
|
|
1122
|
+
setPlans(processedPlans);
|
|
1123
|
+
setError(null);
|
|
1124
|
+
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1125
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1126
|
+
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1127
|
+
}
|
|
1128
|
+
} catch (err) {
|
|
1129
|
+
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1130
|
+
} finally {
|
|
1131
|
+
setLoading(false);
|
|
1132
|
+
}
|
|
1133
|
+
return;
|
|
1061
1134
|
}
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
if (cached?.promise) {
|
|
1065
1135
|
try {
|
|
1066
1136
|
setLoading(true);
|
|
1067
|
-
|
|
1137
|
+
setError(null);
|
|
1138
|
+
const fetchPromise = fetcherRef.current(agentRef);
|
|
1139
|
+
plansCache.set(agentRef, {
|
|
1140
|
+
plans: [],
|
|
1141
|
+
timestamp: now,
|
|
1142
|
+
promise: fetchPromise
|
|
1143
|
+
});
|
|
1144
|
+
const fetchedPlans = await fetchPromise;
|
|
1145
|
+
plansCache.set(agentRef, {
|
|
1146
|
+
plans: fetchedPlans,
|
|
1147
|
+
timestamp: now,
|
|
1148
|
+
promise: null
|
|
1149
|
+
});
|
|
1068
1150
|
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1069
1151
|
if (sortByRef.current) {
|
|
1070
1152
|
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1071
1153
|
}
|
|
1072
1154
|
setPlans(processedPlans);
|
|
1073
|
-
setError(null);
|
|
1074
1155
|
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1075
|
-
const firstPaidIndex = processedPlans.findIndex(
|
|
1076
|
-
(plan) => plan.price && plan.price > 0
|
|
1077
|
-
);
|
|
1156
|
+
const firstPaidIndex = processedPlans.findIndex((plan) => plan.price && plan.price > 0);
|
|
1078
1157
|
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1079
1158
|
}
|
|
1080
1159
|
} catch (err) {
|
|
1160
|
+
plansCache.delete(agentRef);
|
|
1081
1161
|
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1082
1162
|
} finally {
|
|
1083
1163
|
setLoading(false);
|
|
1084
1164
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
setLoading(true);
|
|
1089
|
-
setError(null);
|
|
1090
|
-
const fetchPromise = fetcherRef.current(agentRef);
|
|
1091
|
-
plansCache.set(agentRef, {
|
|
1092
|
-
plans: [],
|
|
1093
|
-
timestamp: now,
|
|
1094
|
-
promise: fetchPromise
|
|
1095
|
-
});
|
|
1096
|
-
const fetchedPlans = await fetchPromise;
|
|
1097
|
-
plansCache.set(agentRef, {
|
|
1098
|
-
plans: fetchedPlans,
|
|
1099
|
-
timestamp: now,
|
|
1100
|
-
promise: null
|
|
1101
|
-
});
|
|
1102
|
-
let processedPlans = filterRef.current ? fetchedPlans.filter(filterRef.current) : fetchedPlans;
|
|
1103
|
-
if (sortByRef.current) {
|
|
1104
|
-
processedPlans = [...processedPlans].sort(sortByRef.current);
|
|
1105
|
-
}
|
|
1106
|
-
setPlans(processedPlans);
|
|
1107
|
-
if (autoSelectFirstPaidRef.current && processedPlans.length > 0) {
|
|
1108
|
-
const firstPaidIndex = processedPlans.findIndex(
|
|
1109
|
-
(plan) => plan.price && plan.price > 0
|
|
1110
|
-
);
|
|
1111
|
-
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1112
|
-
}
|
|
1113
|
-
} catch (err) {
|
|
1114
|
-
plansCache.delete(agentRef);
|
|
1115
|
-
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1116
|
-
} finally {
|
|
1117
|
-
setLoading(false);
|
|
1118
|
-
}
|
|
1119
|
-
}, [agentRef]);
|
|
1165
|
+
},
|
|
1166
|
+
[agentRef]
|
|
1167
|
+
);
|
|
1120
1168
|
(0, import_react7.useEffect)(() => {
|
|
1121
1169
|
fetchPlans();
|
|
1122
1170
|
}, [fetchPlans]);
|
|
1123
1171
|
const selectedPlan = (0, import_react7.useMemo)(() => {
|
|
1124
1172
|
return plans[selectedPlanIndex] || null;
|
|
1125
1173
|
}, [plans, selectedPlanIndex]);
|
|
1126
|
-
const selectPlan = (0, import_react7.useCallback)(
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1174
|
+
const selectPlan = (0, import_react7.useCallback)(
|
|
1175
|
+
(planRef) => {
|
|
1176
|
+
const index = plans.findIndex((p) => p.reference === planRef);
|
|
1177
|
+
if (index >= 0) {
|
|
1178
|
+
setSelectedPlanIndex(index);
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
[plans]
|
|
1182
|
+
);
|
|
1132
1183
|
return {
|
|
1133
1184
|
plans,
|
|
1134
1185
|
loading,
|
|
@@ -1161,19 +1212,25 @@ var PlanSelector = ({
|
|
|
1161
1212
|
autoSelectFirstPaid
|
|
1162
1213
|
});
|
|
1163
1214
|
const { plans } = plansHook;
|
|
1164
|
-
const isPaidPlan = (0, import_react8.useCallback)(
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1215
|
+
const isPaidPlan = (0, import_react8.useCallback)(
|
|
1216
|
+
(planName) => {
|
|
1217
|
+
const plan = plans.find((p) => p.name === planName);
|
|
1218
|
+
return plan ? (plan.price ?? 0) > 0 && !plan.isFreeTier : true;
|
|
1219
|
+
},
|
|
1220
|
+
[plans]
|
|
1221
|
+
);
|
|
1168
1222
|
const activeSubscription = (0, import_react8.useMemo)(() => {
|
|
1169
1223
|
const activeSubs = subscriptions.filter((sub) => sub.status === "active");
|
|
1170
1224
|
return activeSubs.sort(
|
|
1171
1225
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1172
1226
|
)[0] || null;
|
|
1173
1227
|
}, [subscriptions]);
|
|
1174
|
-
const isCurrentPlan = (0, import_react8.useCallback)(
|
|
1175
|
-
|
|
1176
|
-
|
|
1228
|
+
const isCurrentPlan = (0, import_react8.useCallback)(
|
|
1229
|
+
(planName) => {
|
|
1230
|
+
return activeSubscription?.planName === planName;
|
|
1231
|
+
},
|
|
1232
|
+
[activeSubscription]
|
|
1233
|
+
);
|
|
1177
1234
|
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: children({
|
|
1178
1235
|
...plansHook,
|
|
1179
1236
|
subscriptions,
|
|
@@ -1191,10 +1248,11 @@ function useSubscriptionStatus() {
|
|
|
1191
1248
|
}, []);
|
|
1192
1249
|
const subscriptionData = (0, import_react9.useMemo)(() => {
|
|
1193
1250
|
const cancelledPaidSubscriptions = subscriptions.filter((sub) => {
|
|
1194
|
-
|
|
1195
|
-
return sub.status === "active" && subAny.cancelledAt && isPaidSubscription2(sub);
|
|
1251
|
+
return sub.status === "active" && sub.cancelledAt && isPaidSubscription2(sub);
|
|
1196
1252
|
});
|
|
1197
|
-
const cancelledSubscription = cancelledPaidSubscriptions.sort(
|
|
1253
|
+
const cancelledSubscription = cancelledPaidSubscriptions.sort(
|
|
1254
|
+
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1255
|
+
)[0] || null;
|
|
1198
1256
|
const shouldShowCancelledNotice = !!cancelledSubscription;
|
|
1199
1257
|
return {
|
|
1200
1258
|
cancelledSubscription,
|