@solvapay/react 1.0.0-preview.19 → 1.0.0-preview.20
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/README.md +29 -29
- package/dist/index.cjs +170 -166
- package/dist/index.d.cts +166 -147
- package/dist/index.d.ts +166 -147
- package/dist/index.js +160 -156
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,34 +5,34 @@ import {
|
|
|
5
5
|
// src/SolvaPayProvider.tsx
|
|
6
6
|
import { createContext, useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
7
7
|
|
|
8
|
-
// src/utils/
|
|
9
|
-
function
|
|
10
|
-
return
|
|
8
|
+
// src/utils/purchases.ts
|
|
9
|
+
function filterPurchases(purchases) {
|
|
10
|
+
return purchases.filter((purchase) => purchase.status === "active");
|
|
11
11
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
12
|
+
function getActivePurchases(purchases) {
|
|
13
|
+
return purchases.filter((purchase) => purchase.status === "active");
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function getCancelledPurchasesWithEndDate(purchases) {
|
|
16
16
|
const now = /* @__PURE__ */ new Date();
|
|
17
|
-
return
|
|
18
|
-
return
|
|
17
|
+
return purchases.filter((purchase) => {
|
|
18
|
+
return purchase.status === "active" && purchase.cancelledAt && purchase.endDate && new Date(purchase.endDate) > now;
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
function
|
|
22
|
-
if (
|
|
23
|
-
return
|
|
21
|
+
function getMostRecentPurchase(purchases) {
|
|
22
|
+
if (purchases.length === 0) return null;
|
|
23
|
+
return purchases.reduce((latest, current) => {
|
|
24
24
|
return new Date(current.startDate) > new Date(latest.startDate) ? current : latest;
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
function
|
|
28
|
-
const filtered =
|
|
27
|
+
function getPrimaryPurchase(purchases) {
|
|
28
|
+
const filtered = filterPurchases(purchases);
|
|
29
29
|
if (filtered.length > 0) {
|
|
30
|
-
return
|
|
30
|
+
return getMostRecentPurchase(filtered);
|
|
31
31
|
}
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
return (
|
|
34
|
+
function isPaidPurchase(purchase) {
|
|
35
|
+
return (purchase.amount ?? 0) > 0;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// src/SolvaPayProvider.tsx
|
|
@@ -96,12 +96,12 @@ function getAuthAdapter(config) {
|
|
|
96
96
|
var SolvaPayProvider = ({
|
|
97
97
|
config,
|
|
98
98
|
createPayment: customCreatePayment,
|
|
99
|
-
|
|
99
|
+
checkPurchase: customCheckPurchase,
|
|
100
100
|
processPayment: customProcessPayment,
|
|
101
101
|
children
|
|
102
102
|
}) => {
|
|
103
|
-
const [
|
|
104
|
-
|
|
103
|
+
const [purchaseData, setPurchaseData] = useState({
|
|
104
|
+
purchases: []
|
|
105
105
|
});
|
|
106
106
|
const [loading, setLoading] = useState(false);
|
|
107
107
|
const [internalCustomerRef, setInternalCustomerRef] = useState(void 0);
|
|
@@ -109,31 +109,31 @@ var SolvaPayProvider = ({
|
|
|
109
109
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
110
110
|
const inFlightRef = useRef(null);
|
|
111
111
|
const lastFetchedRef = useRef(null);
|
|
112
|
-
const
|
|
112
|
+
const checkPurchaseRef = useRef(null);
|
|
113
113
|
const createPaymentRef = useRef(null);
|
|
114
114
|
const processPaymentRef = useRef(null);
|
|
115
115
|
const configRef = useRef(config);
|
|
116
|
-
const
|
|
116
|
+
const buildDefaultCheckPurchaseRef = useRef(
|
|
117
117
|
null
|
|
118
118
|
);
|
|
119
119
|
useEffect(() => {
|
|
120
120
|
configRef.current = config;
|
|
121
121
|
}, [config]);
|
|
122
122
|
useEffect(() => {
|
|
123
|
-
|
|
124
|
-
}, [
|
|
123
|
+
checkPurchaseRef.current = customCheckPurchase || null;
|
|
124
|
+
}, [customCheckPurchase]);
|
|
125
125
|
useEffect(() => {
|
|
126
126
|
createPaymentRef.current = customCreatePayment || null;
|
|
127
127
|
}, [customCreatePayment]);
|
|
128
128
|
useEffect(() => {
|
|
129
129
|
processPaymentRef.current = customProcessPayment || null;
|
|
130
130
|
}, [customProcessPayment]);
|
|
131
|
-
const
|
|
131
|
+
const buildDefaultCheckPurchase = useCallback(async () => {
|
|
132
132
|
const currentConfig = configRef.current;
|
|
133
133
|
const adapter = getAuthAdapter(currentConfig);
|
|
134
134
|
const token = await adapter.getToken();
|
|
135
135
|
const detectedUserId = await adapter.getUserId();
|
|
136
|
-
const route = currentConfig?.api?.
|
|
136
|
+
const route = currentConfig?.api?.checkPurchase || "/api/check-purchase";
|
|
137
137
|
const fetchFn = currentConfig?.fetch || fetch;
|
|
138
138
|
const cachedRef = getCachedCustomerRef(detectedUserId);
|
|
139
139
|
const headers = {
|
|
@@ -154,15 +154,15 @@ var SolvaPayProvider = ({
|
|
|
154
154
|
headers
|
|
155
155
|
});
|
|
156
156
|
if (!res.ok) {
|
|
157
|
-
const error = new Error(`Failed to check
|
|
158
|
-
currentConfig?.onError?.(error, "
|
|
157
|
+
const error = new Error(`Failed to check purchase: ${res.statusText}`);
|
|
158
|
+
currentConfig?.onError?.(error, "checkPurchase");
|
|
159
159
|
throw error;
|
|
160
160
|
}
|
|
161
161
|
return res.json();
|
|
162
162
|
}, []);
|
|
163
163
|
useEffect(() => {
|
|
164
|
-
|
|
165
|
-
}, [
|
|
164
|
+
buildDefaultCheckPurchaseRef.current = buildDefaultCheckPurchase;
|
|
165
|
+
}, [buildDefaultCheckPurchase]);
|
|
166
166
|
const buildDefaultCreatePayment = useCallback(
|
|
167
167
|
async (params) => {
|
|
168
168
|
const currentConfig = configRef.current;
|
|
@@ -186,8 +186,8 @@ var SolvaPayProvider = ({
|
|
|
186
186
|
Object.assign(headers, customHeaders);
|
|
187
187
|
}
|
|
188
188
|
const body = { planRef: params.planRef };
|
|
189
|
-
if (params.
|
|
190
|
-
body.
|
|
189
|
+
if (params.productRef) {
|
|
190
|
+
body.productRef = params.productRef;
|
|
191
191
|
}
|
|
192
192
|
const res = await fetchFn(route, {
|
|
193
193
|
method: "POST",
|
|
@@ -239,15 +239,15 @@ var SolvaPayProvider = ({
|
|
|
239
239
|
},
|
|
240
240
|
[]
|
|
241
241
|
);
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
return
|
|
242
|
+
const _checkPurchase = useCallback(async () => {
|
|
243
|
+
if (checkPurchaseRef.current) {
|
|
244
|
+
return checkPurchaseRef.current();
|
|
245
245
|
}
|
|
246
|
-
if (
|
|
247
|
-
return
|
|
246
|
+
if (buildDefaultCheckPurchaseRef.current) {
|
|
247
|
+
return buildDefaultCheckPurchaseRef.current();
|
|
248
248
|
}
|
|
249
|
-
return
|
|
250
|
-
}, [
|
|
249
|
+
return buildDefaultCheckPurchase();
|
|
250
|
+
}, [buildDefaultCheckPurchase]);
|
|
251
251
|
const createPayment = useCallback(
|
|
252
252
|
async (params) => {
|
|
253
253
|
if (createPaymentRef.current) {
|
|
@@ -294,10 +294,10 @@ var SolvaPayProvider = ({
|
|
|
294
294
|
const interval = setInterval(detectAuth, 5e3);
|
|
295
295
|
return () => clearInterval(interval);
|
|
296
296
|
}, [userId]);
|
|
297
|
-
const
|
|
297
|
+
const fetchPurchase = useCallback(
|
|
298
298
|
async (force = false) => {
|
|
299
299
|
if (!isAuthenticated && !internalCustomerRef) {
|
|
300
|
-
|
|
300
|
+
setPurchaseData({ purchases: [] });
|
|
301
301
|
setLoading(false);
|
|
302
302
|
inFlightRef.current = null;
|
|
303
303
|
lastFetchedRef.current = null;
|
|
@@ -313,9 +313,9 @@ var SolvaPayProvider = ({
|
|
|
313
313
|
inFlightRef.current = cacheKey;
|
|
314
314
|
setLoading(true);
|
|
315
315
|
try {
|
|
316
|
-
const checkFn =
|
|
316
|
+
const checkFn = checkPurchaseRef.current || buildDefaultCheckPurchaseRef.current;
|
|
317
317
|
if (!checkFn) {
|
|
318
|
-
throw new Error("
|
|
318
|
+
throw new Error("checkPurchase function not available");
|
|
319
319
|
}
|
|
320
320
|
const data = await checkFn();
|
|
321
321
|
if (data.customerRef) {
|
|
@@ -327,16 +327,16 @@ var SolvaPayProvider = ({
|
|
|
327
327
|
if (inFlightRef.current === cacheKey) {
|
|
328
328
|
const filteredData = {
|
|
329
329
|
...data,
|
|
330
|
-
|
|
330
|
+
purchases: filterPurchases(data.purchases || [])
|
|
331
331
|
};
|
|
332
|
-
|
|
332
|
+
setPurchaseData(filteredData);
|
|
333
333
|
lastFetchedRef.current = cacheKey;
|
|
334
334
|
}
|
|
335
335
|
} catch (error) {
|
|
336
|
-
console.error("[SolvaPayProvider] Failed to fetch
|
|
336
|
+
console.error("[SolvaPayProvider] Failed to fetch purchase:", error);
|
|
337
337
|
if (inFlightRef.current === cacheKey) {
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
setPurchaseData({
|
|
339
|
+
purchases: []
|
|
340
340
|
});
|
|
341
341
|
lastFetchedRef.current = cacheKey;
|
|
342
342
|
}
|
|
@@ -349,23 +349,23 @@ var SolvaPayProvider = ({
|
|
|
349
349
|
},
|
|
350
350
|
[isAuthenticated, internalCustomerRef, userId]
|
|
351
351
|
);
|
|
352
|
-
const
|
|
352
|
+
const fetchPurchaseRef = useRef(fetchPurchase);
|
|
353
353
|
useEffect(() => {
|
|
354
|
-
|
|
355
|
-
}, [
|
|
356
|
-
const
|
|
354
|
+
fetchPurchaseRef.current = fetchPurchase;
|
|
355
|
+
}, [fetchPurchase]);
|
|
356
|
+
const refetchPurchase = useCallback(async () => {
|
|
357
357
|
lastFetchedRef.current = null;
|
|
358
358
|
inFlightRef.current = null;
|
|
359
|
-
|
|
360
|
-
await
|
|
359
|
+
setPurchaseData({ purchases: [] });
|
|
360
|
+
await fetchPurchaseRef.current(true);
|
|
361
361
|
}, []);
|
|
362
362
|
useEffect(() => {
|
|
363
363
|
lastFetchedRef.current = null;
|
|
364
364
|
inFlightRef.current = null;
|
|
365
365
|
if (isAuthenticated || internalCustomerRef) {
|
|
366
|
-
|
|
366
|
+
fetchPurchase();
|
|
367
367
|
} else {
|
|
368
|
-
|
|
368
|
+
setPurchaseData({ purchases: [] });
|
|
369
369
|
setLoading(false);
|
|
370
370
|
}
|
|
371
371
|
}, [isAuthenticated, internalCustomerRef, userId]);
|
|
@@ -373,49 +373,49 @@ var SolvaPayProvider = ({
|
|
|
373
373
|
(newCustomerRef) => {
|
|
374
374
|
setInternalCustomerRef(newCustomerRef);
|
|
375
375
|
setCachedCustomerRef(newCustomerRef, userId);
|
|
376
|
-
|
|
376
|
+
fetchPurchase(true);
|
|
377
377
|
},
|
|
378
|
-
[
|
|
378
|
+
[fetchPurchase, userId]
|
|
379
379
|
);
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
const
|
|
383
|
-
(
|
|
380
|
+
const purchase = useMemo(() => {
|
|
381
|
+
const activePurchase = getPrimaryPurchase(purchaseData.purchases);
|
|
382
|
+
const activePaidPurchases = purchaseData.purchases.filter(
|
|
383
|
+
(p) => p.status === "active" && isPaidPurchase(p)
|
|
384
384
|
);
|
|
385
|
-
const
|
|
385
|
+
const activePaidPurchase = activePaidPurchases.sort(
|
|
386
386
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
387
387
|
)[0] || null;
|
|
388
388
|
return {
|
|
389
389
|
loading,
|
|
390
|
-
customerRef:
|
|
391
|
-
email:
|
|
392
|
-
name:
|
|
393
|
-
|
|
390
|
+
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
391
|
+
email: purchaseData.email,
|
|
392
|
+
name: purchaseData.name,
|
|
393
|
+
purchases: purchaseData.purchases,
|
|
394
394
|
hasPlan: (planName) => {
|
|
395
|
-
return
|
|
396
|
-
(
|
|
395
|
+
return purchaseData.purchases.some(
|
|
396
|
+
(p) => p.planName.toLowerCase() === planName.toLowerCase() && p.status === "active"
|
|
397
397
|
);
|
|
398
398
|
},
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
399
|
+
activePurchase,
|
|
400
|
+
hasPaidPurchase: activePaidPurchases.length > 0,
|
|
401
|
+
activePaidPurchase
|
|
402
402
|
};
|
|
403
|
-
}, [loading,
|
|
403
|
+
}, [loading, purchaseData, internalCustomerRef]);
|
|
404
404
|
const contextValue = useMemo(
|
|
405
405
|
() => ({
|
|
406
|
-
|
|
407
|
-
|
|
406
|
+
purchase,
|
|
407
|
+
refetchPurchase,
|
|
408
408
|
createPayment,
|
|
409
409
|
processPayment,
|
|
410
|
-
customerRef:
|
|
410
|
+
customerRef: purchaseData.customerRef || internalCustomerRef,
|
|
411
411
|
updateCustomerRef
|
|
412
412
|
}),
|
|
413
413
|
[
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
purchase,
|
|
415
|
+
refetchPurchase,
|
|
416
416
|
createPayment,
|
|
417
417
|
processPayment,
|
|
418
|
-
|
|
418
|
+
purchaseData.customerRef,
|
|
419
419
|
internalCustomerRef,
|
|
420
420
|
updateCustomerRef
|
|
421
421
|
]
|
|
@@ -448,7 +448,8 @@ var stripePromiseCache = /* @__PURE__ */ new Map();
|
|
|
448
448
|
function getStripeCacheKey(publishableKey, accountId) {
|
|
449
449
|
return accountId ? `${publishableKey}:${accountId}` : publishableKey;
|
|
450
450
|
}
|
|
451
|
-
function useCheckout(
|
|
451
|
+
function useCheckout(options) {
|
|
452
|
+
const { planRef, productRef } = options;
|
|
452
453
|
const { createPayment, customerRef, updateCustomerRef } = useSolvaPay();
|
|
453
454
|
const [loading, setLoading] = useState2(false);
|
|
454
455
|
const [error, setError] = useState2(
|
|
@@ -469,7 +470,7 @@ function useCheckout(planRef, agentRef) {
|
|
|
469
470
|
setLoading(true);
|
|
470
471
|
setError(null);
|
|
471
472
|
try {
|
|
472
|
-
const result = await createPayment({ planRef,
|
|
473
|
+
const result = await createPayment({ planRef, productRef });
|
|
473
474
|
if (!result || typeof result !== "object") {
|
|
474
475
|
throw new Error("Invalid payment intent response from server");
|
|
475
476
|
}
|
|
@@ -498,7 +499,7 @@ function useCheckout(planRef, agentRef) {
|
|
|
498
499
|
setLoading(false);
|
|
499
500
|
isStartingRef.current = false;
|
|
500
501
|
}
|
|
501
|
-
}, [planRef,
|
|
502
|
+
}, [planRef, productRef, createPayment, updateCustomerRef, loading]);
|
|
502
503
|
const reset = useCallback2(() => {
|
|
503
504
|
isStartingRef.current = false;
|
|
504
505
|
setLoading(false);
|
|
@@ -516,12 +517,12 @@ function useCheckout(planRef, agentRef) {
|
|
|
516
517
|
};
|
|
517
518
|
}
|
|
518
519
|
|
|
519
|
-
// src/hooks/
|
|
520
|
-
function
|
|
521
|
-
const {
|
|
520
|
+
// src/hooks/usePurchase.ts
|
|
521
|
+
function usePurchase() {
|
|
522
|
+
const { purchase, refetchPurchase } = useSolvaPay();
|
|
522
523
|
return {
|
|
523
|
-
...
|
|
524
|
-
refetch:
|
|
524
|
+
...purchase,
|
|
525
|
+
refetch: refetchPurchase
|
|
525
526
|
};
|
|
526
527
|
}
|
|
527
528
|
|
|
@@ -566,12 +567,12 @@ import { useStripe, useElements, CardElement } from "@stripe/react-stripe-js";
|
|
|
566
567
|
|
|
567
568
|
// src/hooks/useCustomer.ts
|
|
568
569
|
function useCustomer() {
|
|
569
|
-
const {
|
|
570
|
+
const { purchase, customerRef } = useSolvaPay();
|
|
570
571
|
return {
|
|
571
|
-
customerRef:
|
|
572
|
-
email:
|
|
573
|
-
name:
|
|
574
|
-
loading:
|
|
572
|
+
customerRef: purchase.customerRef || customerRef,
|
|
573
|
+
email: purchase.email,
|
|
574
|
+
name: purchase.name,
|
|
575
|
+
loading: purchase.loading
|
|
575
576
|
};
|
|
576
577
|
}
|
|
577
578
|
|
|
@@ -744,7 +745,7 @@ var StripePaymentFormWrapper = ({
|
|
|
744
745
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
745
746
|
var PaymentForm = ({
|
|
746
747
|
planRef,
|
|
747
|
-
|
|
748
|
+
productRef,
|
|
748
749
|
onSuccess,
|
|
749
750
|
onError,
|
|
750
751
|
returnUrl,
|
|
@@ -759,8 +760,8 @@ var PaymentForm = ({
|
|
|
759
760
|
clientSecret,
|
|
760
761
|
startCheckout,
|
|
761
762
|
stripePromise
|
|
762
|
-
} = useCheckout(validPlanRef,
|
|
763
|
-
const { refetch } =
|
|
763
|
+
} = useCheckout({ planRef: validPlanRef, productRef });
|
|
764
|
+
const { refetch } = usePurchase();
|
|
764
765
|
const { processPayment } = useSolvaPay();
|
|
765
766
|
const hasInitializedRef = useRef3(false);
|
|
766
767
|
useEffect2(() => {
|
|
@@ -779,11 +780,11 @@ var PaymentForm = ({
|
|
|
779
780
|
let processingTimeout = false;
|
|
780
781
|
let processingResult = null;
|
|
781
782
|
const paymentIntentAny = paymentIntent;
|
|
782
|
-
if (processPayment &&
|
|
783
|
+
if (processPayment && productRef) {
|
|
783
784
|
try {
|
|
784
785
|
const result = await processPayment({
|
|
785
786
|
paymentIntentId: paymentIntentAny.id,
|
|
786
|
-
|
|
787
|
+
productRef,
|
|
787
788
|
planRef
|
|
788
789
|
});
|
|
789
790
|
processingResult = result;
|
|
@@ -800,6 +801,7 @@ var PaymentForm = ({
|
|
|
800
801
|
...paymentIntentAny,
|
|
801
802
|
_processingTimeout: processingTimeout,
|
|
802
803
|
_processingResult: processingResult
|
|
804
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
803
805
|
});
|
|
804
806
|
}
|
|
805
807
|
throw new Error("Payment processing timed out");
|
|
@@ -813,6 +815,7 @@ var PaymentForm = ({
|
|
|
813
815
|
await onSuccess({
|
|
814
816
|
...paymentIntentAny,
|
|
815
817
|
_processingError: error
|
|
818
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
816
819
|
});
|
|
817
820
|
} catch {
|
|
818
821
|
}
|
|
@@ -827,10 +830,11 @@ var PaymentForm = ({
|
|
|
827
830
|
...paymentIntentAny,
|
|
828
831
|
_processingTimeout: processingTimeout,
|
|
829
832
|
_processingResult: processingResult
|
|
833
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
830
834
|
});
|
|
831
835
|
}
|
|
832
836
|
},
|
|
833
|
-
[processPayment,
|
|
837
|
+
[processPayment, productRef, planRef, refetch, onSuccess]
|
|
834
838
|
);
|
|
835
839
|
const handleError = useCallback3(
|
|
836
840
|
(err) => {
|
|
@@ -918,13 +922,13 @@ var PlanBadge = ({
|
|
|
918
922
|
as: Component = "div",
|
|
919
923
|
className
|
|
920
924
|
}) => {
|
|
921
|
-
const {
|
|
925
|
+
const { purchases, loading, hasPaidPurchase, activePurchase } = usePurchase();
|
|
922
926
|
const [displayPlan, setDisplayPlan] = useState5(null);
|
|
923
927
|
const [hasLoadedOnce, setHasLoadedOnce] = useState5(false);
|
|
924
928
|
const lastPlanRef = useRef4(null);
|
|
925
929
|
const lastLoadingRef = useRef4(true);
|
|
926
|
-
const
|
|
927
|
-
const currentPlanName =
|
|
930
|
+
const previousPurchasesRef = useRef4(purchases);
|
|
931
|
+
const currentPlanName = activePurchase?.planName || null;
|
|
928
932
|
const effectivePlanName = currentPlanName;
|
|
929
933
|
useEffect3(() => {
|
|
930
934
|
if (!loading && !hasLoadedOnce) {
|
|
@@ -933,16 +937,16 @@ var PlanBadge = ({
|
|
|
933
937
|
lastLoadingRef.current = loading;
|
|
934
938
|
}, [loading, hasLoadedOnce]);
|
|
935
939
|
useEffect3(() => {
|
|
936
|
-
const
|
|
937
|
-
if (
|
|
940
|
+
const previousPurchases = previousPurchasesRef.current;
|
|
941
|
+
if (previousPurchases !== purchases) {
|
|
938
942
|
if (loading) {
|
|
939
943
|
setHasLoadedOnce(false);
|
|
940
944
|
}
|
|
941
945
|
setDisplayPlan(null);
|
|
942
946
|
lastPlanRef.current = null;
|
|
943
|
-
|
|
947
|
+
previousPurchasesRef.current = purchases;
|
|
944
948
|
}
|
|
945
|
-
}, [
|
|
949
|
+
}, [purchases, loading]);
|
|
946
950
|
useEffect3(() => {
|
|
947
951
|
const currentPlan = effectivePlanName;
|
|
948
952
|
const previousPlan = lastPlanRef.current;
|
|
@@ -961,19 +965,19 @@ var PlanBadge = ({
|
|
|
961
965
|
const shouldShow = effectivePlanName !== null && hasLoadedOnce;
|
|
962
966
|
const planToDisplay = displayPlan ?? effectivePlanName;
|
|
963
967
|
if (children) {
|
|
964
|
-
return /* @__PURE__ */ jsx5(Fragment2, { children: children({
|
|
968
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children: children({ purchases, loading, displayPlan: planToDisplay, shouldShow }) });
|
|
965
969
|
}
|
|
966
970
|
if (!shouldShow) {
|
|
967
971
|
return null;
|
|
968
972
|
}
|
|
969
|
-
const computedClassName = typeof className === "function" ? className({
|
|
973
|
+
const computedClassName = typeof className === "function" ? className({ purchases }) : className;
|
|
970
974
|
return /* @__PURE__ */ jsx5(
|
|
971
975
|
Component,
|
|
972
976
|
{
|
|
973
977
|
className: computedClassName,
|
|
974
978
|
"data-loading": loading,
|
|
975
|
-
"data-has-
|
|
976
|
-
"data-has-paid-
|
|
979
|
+
"data-has-purchase": !!activePurchase,
|
|
980
|
+
"data-has-paid-purchase": hasPaidPurchase,
|
|
977
981
|
role: "status",
|
|
978
982
|
"aria-live": "polite",
|
|
979
983
|
"aria-busy": loading,
|
|
@@ -983,14 +987,14 @@ var PlanBadge = ({
|
|
|
983
987
|
);
|
|
984
988
|
};
|
|
985
989
|
|
|
986
|
-
// src/components/
|
|
990
|
+
// src/components/PurchaseGate.tsx
|
|
987
991
|
import { Fragment as Fragment3, jsx as jsx6 } from "react/jsx-runtime";
|
|
988
|
-
var
|
|
989
|
-
const {
|
|
990
|
-
const hasAccess = requirePlan ? hasPlan(requirePlan) :
|
|
992
|
+
var PurchaseGate = ({ requirePlan, requireProduct, children }) => {
|
|
993
|
+
const { purchases, loading, hasPlan } = usePurchase();
|
|
994
|
+
const hasAccess = requirePlan ? hasPlan(requirePlan) : requireProduct ? purchases.some((p) => p.status === "active" && (p.productName === requireProduct || p.productReference === requireProduct)) : purchases.some((p) => p.status === "active");
|
|
991
995
|
return /* @__PURE__ */ jsx6(Fragment3, { children: children({
|
|
992
996
|
hasAccess,
|
|
993
|
-
|
|
997
|
+
purchases,
|
|
994
998
|
loading
|
|
995
999
|
}) });
|
|
996
1000
|
};
|
|
@@ -1003,7 +1007,7 @@ import { useState as useState6, useEffect as useEffect4, useCallback as useCallb
|
|
|
1003
1007
|
var plansCache = /* @__PURE__ */ new Map();
|
|
1004
1008
|
var CACHE_DURATION2 = 5 * 60 * 1e3;
|
|
1005
1009
|
function usePlans(options) {
|
|
1006
|
-
const { fetcher,
|
|
1010
|
+
const { fetcher, productRef, filter, sortBy, autoSelectFirstPaid = false } = options;
|
|
1007
1011
|
const [plans, setPlans] = useState6([]);
|
|
1008
1012
|
const [loading, setLoading] = useState6(true);
|
|
1009
1013
|
const [error, setError] = useState6(null);
|
|
@@ -1026,12 +1030,12 @@ function usePlans(options) {
|
|
|
1026
1030
|
}, [autoSelectFirstPaid]);
|
|
1027
1031
|
const fetchPlans = useCallback4(
|
|
1028
1032
|
async (force = false) => {
|
|
1029
|
-
if (!
|
|
1030
|
-
setError(new Error("
|
|
1033
|
+
if (!productRef) {
|
|
1034
|
+
setError(new Error("Product reference not configured"));
|
|
1031
1035
|
setLoading(false);
|
|
1032
1036
|
return;
|
|
1033
1037
|
}
|
|
1034
|
-
const cached = plansCache.get(
|
|
1038
|
+
const cached = plansCache.get(productRef);
|
|
1035
1039
|
const now = Date.now();
|
|
1036
1040
|
if (!force && cached && now - cached.timestamp < CACHE_DURATION2) {
|
|
1037
1041
|
const cachedPlans = cached.plans;
|
|
@@ -1072,14 +1076,14 @@ function usePlans(options) {
|
|
|
1072
1076
|
try {
|
|
1073
1077
|
setLoading(true);
|
|
1074
1078
|
setError(null);
|
|
1075
|
-
const fetchPromise = fetcherRef.current(
|
|
1076
|
-
plansCache.set(
|
|
1079
|
+
const fetchPromise = fetcherRef.current(productRef);
|
|
1080
|
+
plansCache.set(productRef, {
|
|
1077
1081
|
plans: [],
|
|
1078
1082
|
timestamp: now,
|
|
1079
1083
|
promise: fetchPromise
|
|
1080
1084
|
});
|
|
1081
1085
|
const fetchedPlans = await fetchPromise;
|
|
1082
|
-
plansCache.set(
|
|
1086
|
+
plansCache.set(productRef, {
|
|
1083
1087
|
plans: fetchedPlans,
|
|
1084
1088
|
timestamp: now,
|
|
1085
1089
|
promise: null
|
|
@@ -1094,13 +1098,13 @@ function usePlans(options) {
|
|
|
1094
1098
|
setSelectedPlanIndex(firstPaidIndex >= 0 ? firstPaidIndex : 0);
|
|
1095
1099
|
}
|
|
1096
1100
|
} catch (err) {
|
|
1097
|
-
plansCache.delete(
|
|
1101
|
+
plansCache.delete(productRef);
|
|
1098
1102
|
setError(err instanceof Error ? err : new Error("Failed to load plans"));
|
|
1099
1103
|
} finally {
|
|
1100
1104
|
setLoading(false);
|
|
1101
1105
|
}
|
|
1102
1106
|
},
|
|
1103
|
-
[
|
|
1107
|
+
[productRef]
|
|
1104
1108
|
);
|
|
1105
1109
|
useEffect4(() => {
|
|
1106
1110
|
fetchPlans();
|
|
@@ -1133,16 +1137,16 @@ function usePlans(options) {
|
|
|
1133
1137
|
// src/components/PlanSelector.tsx
|
|
1134
1138
|
import { Fragment as Fragment4, jsx as jsx7 } from "react/jsx-runtime";
|
|
1135
1139
|
var PlanSelector = ({
|
|
1136
|
-
|
|
1140
|
+
productRef,
|
|
1137
1141
|
fetcher,
|
|
1138
1142
|
filter,
|
|
1139
1143
|
sortBy,
|
|
1140
1144
|
autoSelectFirstPaid,
|
|
1141
1145
|
children
|
|
1142
1146
|
}) => {
|
|
1143
|
-
const {
|
|
1147
|
+
const { purchases } = usePurchase();
|
|
1144
1148
|
const plansHook = usePlans({
|
|
1145
|
-
|
|
1149
|
+
productRef,
|
|
1146
1150
|
fetcher,
|
|
1147
1151
|
filter,
|
|
1148
1152
|
sortBy,
|
|
@@ -1156,46 +1160,46 @@ var PlanSelector = ({
|
|
|
1156
1160
|
},
|
|
1157
1161
|
[plans]
|
|
1158
1162
|
);
|
|
1159
|
-
const
|
|
1160
|
-
const
|
|
1161
|
-
return
|
|
1163
|
+
const activePurchase = useMemo4(() => {
|
|
1164
|
+
const activePurchases = purchases.filter((p) => p.status === "active");
|
|
1165
|
+
return activePurchases.sort(
|
|
1162
1166
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1163
1167
|
)[0] || null;
|
|
1164
|
-
}, [
|
|
1168
|
+
}, [purchases]);
|
|
1165
1169
|
const isCurrentPlan = useCallback5(
|
|
1166
1170
|
(planName) => {
|
|
1167
|
-
return
|
|
1171
|
+
return activePurchase?.planName === planName;
|
|
1168
1172
|
},
|
|
1169
|
-
[
|
|
1173
|
+
[activePurchase]
|
|
1170
1174
|
);
|
|
1171
1175
|
return /* @__PURE__ */ jsx7(Fragment4, { children: children({
|
|
1172
1176
|
...plansHook,
|
|
1173
|
-
|
|
1177
|
+
purchases,
|
|
1174
1178
|
isPaidPlan,
|
|
1175
1179
|
isCurrentPlan
|
|
1176
1180
|
}) });
|
|
1177
1181
|
};
|
|
1178
1182
|
|
|
1179
|
-
// src/hooks/
|
|
1183
|
+
// src/hooks/usePurchaseStatus.ts
|
|
1180
1184
|
import { useMemo as useMemo5, useCallback as useCallback6 } from "react";
|
|
1181
|
-
function
|
|
1182
|
-
const {
|
|
1183
|
-
const
|
|
1184
|
-
return (
|
|
1185
|
+
function usePurchaseStatus() {
|
|
1186
|
+
const { purchases } = usePurchase();
|
|
1187
|
+
const isPaidPurchase2 = useCallback6((p) => {
|
|
1188
|
+
return (p.amount ?? 0) > 0;
|
|
1185
1189
|
}, []);
|
|
1186
|
-
const
|
|
1187
|
-
const
|
|
1188
|
-
return
|
|
1190
|
+
const purchaseData = useMemo5(() => {
|
|
1191
|
+
const cancelledPaidPurchases = purchases.filter((p) => {
|
|
1192
|
+
return p.status === "active" && p.cancelledAt && isPaidPurchase2(p);
|
|
1189
1193
|
});
|
|
1190
|
-
const
|
|
1194
|
+
const cancelledPurchase = cancelledPaidPurchases.sort(
|
|
1191
1195
|
(a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()
|
|
1192
1196
|
)[0] || null;
|
|
1193
|
-
const shouldShowCancelledNotice = !!
|
|
1197
|
+
const shouldShowCancelledNotice = !!cancelledPurchase;
|
|
1194
1198
|
return {
|
|
1195
|
-
|
|
1199
|
+
cancelledPurchase,
|
|
1196
1200
|
shouldShowCancelledNotice
|
|
1197
1201
|
};
|
|
1198
|
-
}, [
|
|
1202
|
+
}, [purchases, isPaidPurchase2]);
|
|
1199
1203
|
const formatDate = useCallback6((dateString) => {
|
|
1200
1204
|
if (!dateString) return null;
|
|
1201
1205
|
return new Date(dateString).toLocaleDateString("en-US", {
|
|
@@ -1213,8 +1217,8 @@ function useSubscriptionStatus() {
|
|
|
1213
1217
|
return diffDays > 0 ? diffDays : 0;
|
|
1214
1218
|
}, []);
|
|
1215
1219
|
return {
|
|
1216
|
-
|
|
1217
|
-
shouldShowCancelledNotice:
|
|
1220
|
+
cancelledPurchase: purchaseData.cancelledPurchase,
|
|
1221
|
+
shouldShowCancelledNotice: purchaseData.shouldShowCancelledNotice,
|
|
1218
1222
|
formatDate,
|
|
1219
1223
|
getDaysUntilExpiration
|
|
1220
1224
|
};
|
|
@@ -1223,21 +1227,21 @@ export {
|
|
|
1223
1227
|
PaymentForm,
|
|
1224
1228
|
PlanBadge,
|
|
1225
1229
|
PlanSelector,
|
|
1230
|
+
PurchaseGate,
|
|
1226
1231
|
SolvaPayProvider,
|
|
1227
1232
|
Spinner,
|
|
1228
1233
|
StripePaymentFormWrapper,
|
|
1229
|
-
SubscriptionGate,
|
|
1230
1234
|
defaultAuthAdapter,
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1235
|
+
filterPurchases,
|
|
1236
|
+
getActivePurchases,
|
|
1237
|
+
getCancelledPurchasesWithEndDate,
|
|
1238
|
+
getMostRecentPurchase,
|
|
1239
|
+
getPrimaryPurchase,
|
|
1240
|
+
isPaidPurchase,
|
|
1237
1241
|
useCheckout,
|
|
1238
1242
|
useCustomer,
|
|
1239
1243
|
usePlans,
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1244
|
+
usePurchase,
|
|
1245
|
+
usePurchaseStatus,
|
|
1246
|
+
useSolvaPay
|
|
1243
1247
|
};
|