@sudobility/subscription_lib 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/service.d.ts +74 -0
- package/dist/core/service.d.ts.map +1 -0
- package/dist/core/service.js +251 -0
- package/dist/core/singleton.d.ts +65 -0
- package/dist/core/singleton.d.ts.map +1 -0
- package/dist/core/singleton.js +73 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +8 -0
- package/dist/hooks/useSubscribable.d.ts +50 -0
- package/dist/hooks/useSubscribable.d.ts.map +1 -0
- package/dist/hooks/useSubscribable.js +80 -0
- package/dist/hooks/useSubscriptionForPeriod.d.ts +48 -0
- package/dist/hooks/useSubscriptionForPeriod.d.ts.map +1 -0
- package/dist/hooks/useSubscriptionForPeriod.js +59 -0
- package/dist/hooks/useSubscriptionPeriods.d.ts +38 -0
- package/dist/hooks/useSubscriptionPeriods.d.ts.map +1 -0
- package/dist/hooks/useSubscriptionPeriods.js +44 -0
- package/dist/hooks/useSubscriptions.d.ts +43 -0
- package/dist/hooks/useSubscriptions.d.ts.map +1 -0
- package/dist/hooks/useSubscriptions.js +80 -0
- package/dist/hooks/useUserSubscription.d.ts +39 -0
- package/dist/hooks/useUserSubscription.d.ts.map +1 -0
- package/dist/hooks/useUserSubscription.js +76 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/types/adapter.d.ts +149 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/adapter.js +7 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/dist/types/period.d.ts +18 -0
- package/dist/types/period.d.ts.map +1 -0
- package/dist/types/period.js +25 -0
- package/dist/types/subscription.d.ts +95 -0
- package/dist/types/subscription.d.ts.map +1 -0
- package/dist/types/subscription.js +6 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/level-calculator.d.ts +68 -0
- package/dist/utils/level-calculator.d.ts.map +1 -0
- package/dist/utils/level-calculator.js +164 -0
- package/dist/utils/period-parser.d.ts +45 -0
- package/dist/utils/period-parser.d.ts.map +1 -0
- package/dist/utils/period-parser.js +113 -0
- package/package.json +55 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptionForPeriod Hook
|
|
3
|
+
*
|
|
4
|
+
* Get packages filtered by billing period, sorted by price.
|
|
5
|
+
*/
|
|
6
|
+
import type { SubscriptionPackage } from '../types/subscription';
|
|
7
|
+
import type { SubscriptionPeriod } from '../types/period';
|
|
8
|
+
/**
|
|
9
|
+
* Result of useSubscriptionForPeriod hook
|
|
10
|
+
*/
|
|
11
|
+
export interface UseSubscriptionForPeriodResult {
|
|
12
|
+
/** Packages for the period, sorted by price (ascending), includes free tier first */
|
|
13
|
+
packages: SubscriptionPackage[];
|
|
14
|
+
/** Whether data is being loaded */
|
|
15
|
+
isLoading: boolean;
|
|
16
|
+
/** Error if loading failed */
|
|
17
|
+
error: Error | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hook to get packages filtered by billing period
|
|
21
|
+
*
|
|
22
|
+
* Returns packages sorted by price (ascending):
|
|
23
|
+
* - Free tier first (if configured)
|
|
24
|
+
* - Then paid packages from lowest to highest price
|
|
25
|
+
*
|
|
26
|
+
* @param offerId Offer identifier
|
|
27
|
+
* @param period Billing period to filter by
|
|
28
|
+
* @returns Filtered and sorted packages
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const { packages, isLoading } = useSubscriptionForPeriod('default', 'monthly');
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <div className="grid">
|
|
36
|
+
* {packages.map(pkg => (
|
|
37
|
+
* <SubscriptionTile
|
|
38
|
+
* key={pkg.packageId}
|
|
39
|
+
* title={pkg.name}
|
|
40
|
+
* price={pkg.product?.priceString ?? 'Free'}
|
|
41
|
+
* />
|
|
42
|
+
* ))}
|
|
43
|
+
* </div>
|
|
44
|
+
* );
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function useSubscriptionForPeriod(offerId: string, period: SubscriptionPeriod): UseSubscriptionForPeriodResult;
|
|
48
|
+
//# sourceMappingURL=useSubscriptionForPeriod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSubscriptionForPeriod.d.ts","sourceRoot":"","sources":["../../src/hooks/useSubscriptionForPeriod.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAO1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC7C,qFAAqF;IACrF,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,GACzB,8BAA8B,CA6BhC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptionForPeriod Hook
|
|
3
|
+
*
|
|
4
|
+
* Get packages filtered by billing period, sorted by price.
|
|
5
|
+
*/
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { useSubscriptions } from './useSubscriptions';
|
|
8
|
+
import { getSubscriptionInstance, isSubscriptionInitialized, } from '../core/singleton';
|
|
9
|
+
/**
|
|
10
|
+
* Hook to get packages filtered by billing period
|
|
11
|
+
*
|
|
12
|
+
* Returns packages sorted by price (ascending):
|
|
13
|
+
* - Free tier first (if configured)
|
|
14
|
+
* - Then paid packages from lowest to highest price
|
|
15
|
+
*
|
|
16
|
+
* @param offerId Offer identifier
|
|
17
|
+
* @param period Billing period to filter by
|
|
18
|
+
* @returns Filtered and sorted packages
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const { packages, isLoading } = useSubscriptionForPeriod('default', 'monthly');
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <div className="grid">
|
|
26
|
+
* {packages.map(pkg => (
|
|
27
|
+
* <SubscriptionTile
|
|
28
|
+
* key={pkg.packageId}
|
|
29
|
+
* title={pkg.name}
|
|
30
|
+
* price={pkg.product?.priceString ?? 'Free'}
|
|
31
|
+
* />
|
|
32
|
+
* ))}
|
|
33
|
+
* </div>
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function useSubscriptionForPeriod(offerId, period) {
|
|
38
|
+
const { offer, isLoading, error } = useSubscriptions(offerId);
|
|
39
|
+
const packages = useMemo(() => {
|
|
40
|
+
if (!offer)
|
|
41
|
+
return [];
|
|
42
|
+
// Filter packages by period
|
|
43
|
+
const periodPackages = offer.packages.filter(pkg => pkg.product && pkg.product.period === period);
|
|
44
|
+
// Sort by price ascending
|
|
45
|
+
periodPackages.sort((a, b) => {
|
|
46
|
+
const priceA = a.product?.price ?? 0;
|
|
47
|
+
const priceB = b.product?.price ?? 0;
|
|
48
|
+
return priceA - priceB;
|
|
49
|
+
});
|
|
50
|
+
// Add free tier at the beginning if subscription is initialized
|
|
51
|
+
if (isSubscriptionInitialized()) {
|
|
52
|
+
const service = getSubscriptionInstance();
|
|
53
|
+
const freeTier = service.getFreeTierPackage();
|
|
54
|
+
return [freeTier, ...periodPackages];
|
|
55
|
+
}
|
|
56
|
+
return periodPackages;
|
|
57
|
+
}, [offer, period]);
|
|
58
|
+
return { packages, isLoading, error };
|
|
59
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptionPeriods Hook
|
|
3
|
+
*
|
|
4
|
+
* Extract unique billing periods from an offer's packages.
|
|
5
|
+
*/
|
|
6
|
+
import type { SubscriptionPeriod } from '../types/period';
|
|
7
|
+
/**
|
|
8
|
+
* Result of useSubscriptionPeriods hook
|
|
9
|
+
*/
|
|
10
|
+
export interface UseSubscriptionPeriodsResult {
|
|
11
|
+
/** Available periods sorted (weekly → monthly → quarterly → yearly → lifetime) */
|
|
12
|
+
periods: SubscriptionPeriod[];
|
|
13
|
+
/** Whether data is being loaded */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Error if loading failed */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Hook to get available billing periods from an offer
|
|
20
|
+
*
|
|
21
|
+
* @param offerId Offer identifier
|
|
22
|
+
* @returns Available periods, sorted from shortest to longest
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const { periods, isLoading } = useSubscriptionPeriods('default');
|
|
27
|
+
*
|
|
28
|
+
* return (
|
|
29
|
+
* <SegmentedControl
|
|
30
|
+
* options={periods.map(p => ({ value: p, label: capitalize(p) }))}
|
|
31
|
+
* value={selectedPeriod}
|
|
32
|
+
* onChange={setSelectedPeriod}
|
|
33
|
+
* />
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function useSubscriptionPeriods(offerId: string): UseSubscriptionPeriodsResult;
|
|
38
|
+
//# sourceMappingURL=useSubscriptionPeriods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSubscriptionPeriods.d.ts","sourceRoot":"","sources":["../../src/hooks/useSubscriptionPeriods.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAI1D;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC3C,kFAAkF;IAClF,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GACd,4BAA4B,CAoB9B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptionPeriods Hook
|
|
3
|
+
*
|
|
4
|
+
* Extract unique billing periods from an offer's packages.
|
|
5
|
+
*/
|
|
6
|
+
import { useMemo } from 'react';
|
|
7
|
+
import { ALL_PERIODS } from '../types/period';
|
|
8
|
+
import { useSubscriptions } from './useSubscriptions';
|
|
9
|
+
/**
|
|
10
|
+
* Hook to get available billing periods from an offer
|
|
11
|
+
*
|
|
12
|
+
* @param offerId Offer identifier
|
|
13
|
+
* @returns Available periods, sorted from shortest to longest
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const { periods, isLoading } = useSubscriptionPeriods('default');
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <SegmentedControl
|
|
21
|
+
* options={periods.map(p => ({ value: p, label: capitalize(p) }))}
|
|
22
|
+
* value={selectedPeriod}
|
|
23
|
+
* onChange={setSelectedPeriod}
|
|
24
|
+
* />
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export function useSubscriptionPeriods(offerId) {
|
|
29
|
+
const { offer, isLoading, error } = useSubscriptions(offerId);
|
|
30
|
+
const periods = useMemo(() => {
|
|
31
|
+
if (!offer)
|
|
32
|
+
return [];
|
|
33
|
+
// Collect unique periods from packages that have products
|
|
34
|
+
const periodSet = new Set();
|
|
35
|
+
for (const pkg of offer.packages) {
|
|
36
|
+
if (pkg.product) {
|
|
37
|
+
periodSet.add(pkg.product.period);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Sort by the standard order
|
|
41
|
+
return ALL_PERIODS.filter(p => periodSet.has(p));
|
|
42
|
+
}, [offer]);
|
|
43
|
+
return { periods, isLoading, error };
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptions Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetch and manage subscription offer data.
|
|
5
|
+
*/
|
|
6
|
+
import type { SubscriptionOffer } from '../types/subscription';
|
|
7
|
+
/**
|
|
8
|
+
* Result of useSubscriptions hook
|
|
9
|
+
*/
|
|
10
|
+
export interface UseSubscriptionsResult {
|
|
11
|
+
/** The requested offer, null if not found or loading */
|
|
12
|
+
offer: SubscriptionOffer | null;
|
|
13
|
+
/** Whether data is being loaded */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Error if loading failed */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
/** Manually refetch the data */
|
|
18
|
+
refetch: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook to get subscription offer data
|
|
22
|
+
*
|
|
23
|
+
* @param offerId Offer identifier to fetch
|
|
24
|
+
* @returns Offer data, loading state, and error
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const { offer, isLoading, error } = useSubscriptions('default');
|
|
29
|
+
*
|
|
30
|
+
* if (isLoading) return <Spinner />;
|
|
31
|
+
* if (error) return <Error message={error.message} />;
|
|
32
|
+
*
|
|
33
|
+
* return (
|
|
34
|
+
* <div>
|
|
35
|
+
* {offer?.packages.map(pkg => (
|
|
36
|
+
* <SubscriptionTile key={pkg.packageId} package={pkg} />
|
|
37
|
+
* ))}
|
|
38
|
+
* </div>
|
|
39
|
+
* );
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function useSubscriptions(offerId: string): UseSubscriptionsResult;
|
|
43
|
+
//# sourceMappingURL=useSubscriptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSubscriptions.d.ts","sourceRoot":"","sources":["../../src/hooks/useSubscriptions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAM/D;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gCAAgC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAsDxE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useSubscriptions Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetch and manage subscription offer data.
|
|
5
|
+
*/
|
|
6
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
+
import { getSubscriptionInstance, isSubscriptionInitialized, } from '../core/singleton';
|
|
8
|
+
/**
|
|
9
|
+
* Hook to get subscription offer data
|
|
10
|
+
*
|
|
11
|
+
* @param offerId Offer identifier to fetch
|
|
12
|
+
* @returns Offer data, loading state, and error
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const { offer, isLoading, error } = useSubscriptions('default');
|
|
17
|
+
*
|
|
18
|
+
* if (isLoading) return <Spinner />;
|
|
19
|
+
* if (error) return <Error message={error.message} />;
|
|
20
|
+
*
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* {offer?.packages.map(pkg => (
|
|
24
|
+
* <SubscriptionTile key={pkg.packageId} package={pkg} />
|
|
25
|
+
* ))}
|
|
26
|
+
* </div>
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function useSubscriptions(offerId) {
|
|
31
|
+
const [offer, setOffer] = useState(null);
|
|
32
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
33
|
+
const [error, setError] = useState(null);
|
|
34
|
+
const loadData = useCallback(async () => {
|
|
35
|
+
if (!isSubscriptionInitialized()) {
|
|
36
|
+
setError(new Error('Subscription not initialized'));
|
|
37
|
+
setIsLoading(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const service = getSubscriptionInstance();
|
|
41
|
+
try {
|
|
42
|
+
setIsLoading(true);
|
|
43
|
+
setError(null);
|
|
44
|
+
// Load offerings if not already loaded
|
|
45
|
+
if (!service.hasLoadedOfferings()) {
|
|
46
|
+
await service.loadOfferings();
|
|
47
|
+
}
|
|
48
|
+
const loadedOffer = service.getOffer(offerId);
|
|
49
|
+
setOffer(loadedOffer);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
setError(err instanceof Error ? err : new Error('Failed to load offer'));
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setIsLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}, [offerId]);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
loadData();
|
|
60
|
+
}, [loadData]);
|
|
61
|
+
const refetch = useCallback(async () => {
|
|
62
|
+
if (!isSubscriptionInitialized())
|
|
63
|
+
return;
|
|
64
|
+
const service = getSubscriptionInstance();
|
|
65
|
+
try {
|
|
66
|
+
setIsLoading(true);
|
|
67
|
+
setError(null);
|
|
68
|
+
await service.loadOfferings();
|
|
69
|
+
const loadedOffer = service.getOffer(offerId);
|
|
70
|
+
setOffer(loadedOffer);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
setError(err instanceof Error ? err : new Error('Failed to load offer'));
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
}
|
|
78
|
+
}, [offerId]);
|
|
79
|
+
return { offer, isLoading, error, refetch };
|
|
80
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useUserSubscription Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetch and manage current user's subscription status.
|
|
5
|
+
*/
|
|
6
|
+
import type { CurrentSubscription } from '../types/subscription';
|
|
7
|
+
/**
|
|
8
|
+
* Result of useUserSubscription hook
|
|
9
|
+
*/
|
|
10
|
+
export interface UseUserSubscriptionResult {
|
|
11
|
+
/** Current subscription info, null if loading */
|
|
12
|
+
subscription: CurrentSubscription | null;
|
|
13
|
+
/** Whether data is being loaded */
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
/** Error if loading failed */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
/** Manually refetch the data */
|
|
18
|
+
refetch: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Hook to get current user's subscription status
|
|
22
|
+
*
|
|
23
|
+
* @returns Current subscription data, loading state, and error
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const { subscription, isLoading } = useUserSubscription();
|
|
28
|
+
*
|
|
29
|
+
* if (isLoading) return <Spinner />;
|
|
30
|
+
*
|
|
31
|
+
* if (subscription?.isActive) {
|
|
32
|
+
* return <div>Your plan: {subscription.entitlements.join(', ')}</div>;
|
|
33
|
+
* } else {
|
|
34
|
+
* return <div>No active subscription</div>;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function useUserSubscription(): UseUserSubscriptionResult;
|
|
39
|
+
//# sourceMappingURL=useUserSubscription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useUserSubscription.d.ts","sourceRoot":"","sources":["../../src/hooks/useUserSubscription.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAMjE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,iDAAiD;IACjD,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACzC,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gCAAgC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,IAAI,yBAAyB,CA4D/D"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useUserSubscription Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetch and manage current user's subscription status.
|
|
5
|
+
*/
|
|
6
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
7
|
+
import { getSubscriptionInstance, isSubscriptionInitialized, } from '../core/singleton';
|
|
8
|
+
/**
|
|
9
|
+
* Hook to get current user's subscription status
|
|
10
|
+
*
|
|
11
|
+
* @returns Current subscription data, loading state, and error
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const { subscription, isLoading } = useUserSubscription();
|
|
16
|
+
*
|
|
17
|
+
* if (isLoading) return <Spinner />;
|
|
18
|
+
*
|
|
19
|
+
* if (subscription?.isActive) {
|
|
20
|
+
* return <div>Your plan: {subscription.entitlements.join(', ')}</div>;
|
|
21
|
+
* } else {
|
|
22
|
+
* return <div>No active subscription</div>;
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function useUserSubscription() {
|
|
27
|
+
const [subscription, setSubscription] = useState(null);
|
|
28
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
29
|
+
const [error, setError] = useState(null);
|
|
30
|
+
const loadData = useCallback(async () => {
|
|
31
|
+
if (!isSubscriptionInitialized()) {
|
|
32
|
+
setError(new Error('Subscription not initialized'));
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const service = getSubscriptionInstance();
|
|
37
|
+
try {
|
|
38
|
+
setIsLoading(true);
|
|
39
|
+
setError(null);
|
|
40
|
+
// Load customer info if not already loaded
|
|
41
|
+
if (!service.hasLoadedCustomerInfo()) {
|
|
42
|
+
await service.loadCustomerInfo();
|
|
43
|
+
}
|
|
44
|
+
const currentSub = service.getCurrentSubscription();
|
|
45
|
+
setSubscription(currentSub);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
setError(err instanceof Error ? err : new Error('Failed to load subscription'));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
setIsLoading(false);
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
loadData();
|
|
56
|
+
}, [loadData]);
|
|
57
|
+
const refetch = useCallback(async () => {
|
|
58
|
+
if (!isSubscriptionInitialized())
|
|
59
|
+
return;
|
|
60
|
+
const service = getSubscriptionInstance();
|
|
61
|
+
try {
|
|
62
|
+
setIsLoading(true);
|
|
63
|
+
setError(null);
|
|
64
|
+
await service.loadCustomerInfo();
|
|
65
|
+
const currentSub = service.getCurrentSubscription();
|
|
66
|
+
setSubscription(currentSub);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
setError(err instanceof Error ? err : new Error('Failed to load subscription'));
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setIsLoading(false);
|
|
73
|
+
}
|
|
74
|
+
}, []);
|
|
75
|
+
return { subscription, isLoading, error, refetch };
|
|
76
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sudobility/subscription_lib
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform subscription management library with RevenueCat adapter pattern.
|
|
5
|
+
* Works with both React (web) and React Native.
|
|
6
|
+
*/
|
|
7
|
+
export { initializeSubscription, getSubscriptionInstance, isSubscriptionInitialized, resetSubscription, refreshSubscription, SubscriptionService, type SubscriptionConfig, type SubscriptionServiceConfig, } from './core';
|
|
8
|
+
export { useSubscriptions, useUserSubscription, useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, type UseSubscriptionsResult, type UseUserSubscriptionResult, type UseSubscriptionPeriodsResult, type UseSubscriptionForPeriodResult, type UseSubscribableResult, } from './hooks';
|
|
9
|
+
export type { SubscriptionAdapter, AdapterOfferings, AdapterOffering, AdapterPackage, AdapterProduct, AdapterSubscriptionOption, AdapterPricingPhase, AdapterCustomerInfo, AdapterEntitlementInfo, AdapterPurchaseParams, AdapterPurchaseResult, SubscriptionProduct, SubscriptionPackage, SubscriptionOffer, CurrentSubscription, FreeTierConfig, PackageWithLevel, SubscriptionPeriod, } from './types';
|
|
10
|
+
export { PERIOD_RANKS, ALL_PERIODS } from './types';
|
|
11
|
+
export { parseISO8601Period, getPeriodRank, comparePeriods, isPeriodGreaterOrEqual, calculatePackageLevels, addLevelsToPackages, getPackageLevel, findUpgradeablePackages, } from './utils';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,GAC/B,MAAM,QAAQ,CAAC;AAGhB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,wBAAwB,EACxB,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACnC,KAAK,qBAAqB,GAC3B,MAAM,SAAS,CAAC;AAGjB,YAAY,EAEV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EAErB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAEhB,kBAAkB,GACnB,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGpD,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,GACxB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sudobility/subscription_lib
|
|
3
|
+
*
|
|
4
|
+
* Cross-platform subscription management library with RevenueCat adapter pattern.
|
|
5
|
+
* Works with both React (web) and React Native.
|
|
6
|
+
*/
|
|
7
|
+
// Core
|
|
8
|
+
export { initializeSubscription, getSubscriptionInstance, isSubscriptionInitialized, resetSubscription, refreshSubscription, SubscriptionService, } from './core';
|
|
9
|
+
// Hooks
|
|
10
|
+
export { useSubscriptions, useUserSubscription, useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, } from './hooks';
|
|
11
|
+
export { PERIOD_RANKS, ALL_PERIODS } from './types';
|
|
12
|
+
// Utils
|
|
13
|
+
export { parseISO8601Period, getPeriodRank, comparePeriods, isPeriodGreaterOrEqual, calculatePackageLevels, addLevelsToPackages, getPackageLevel, findUpgradeablePackages, } from './utils';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RevenueCat Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* Platform-agnostic interface that abstracts RevenueCat SDK differences
|
|
5
|
+
* between web (@revenuecat/purchases-js) and React Native (react-native-purchases).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Pricing phase information for trials and intro prices
|
|
9
|
+
*/
|
|
10
|
+
export interface AdapterPricingPhase {
|
|
11
|
+
/** Duration in ISO 8601 format (e.g., "P7D", "P1M") */
|
|
12
|
+
periodDuration: string | null;
|
|
13
|
+
/** Price in standard units (not micros), null for free trials */
|
|
14
|
+
price: number | null;
|
|
15
|
+
/** Formatted price string, null for free trials */
|
|
16
|
+
priceString: string | null;
|
|
17
|
+
/** Number of billing cycles for this phase */
|
|
18
|
+
cycleCount: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Subscription option with trial and intro price information
|
|
22
|
+
*/
|
|
23
|
+
export interface AdapterSubscriptionOption {
|
|
24
|
+
/** Unique identifier for this option */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Trial phase information, null if no trial */
|
|
27
|
+
trial: AdapterPricingPhase | null;
|
|
28
|
+
/** Introductory price phase, null if no intro price */
|
|
29
|
+
introPrice: AdapterPricingPhase | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Product information from RevenueCat
|
|
33
|
+
*/
|
|
34
|
+
export interface AdapterProduct {
|
|
35
|
+
/** Product ID from the store */
|
|
36
|
+
identifier: string;
|
|
37
|
+
/** Display title */
|
|
38
|
+
title: string;
|
|
39
|
+
/** Product description */
|
|
40
|
+
description: string | null;
|
|
41
|
+
/** Price in standard units (e.g., 9.99 not 9990000) */
|
|
42
|
+
price: number;
|
|
43
|
+
/** Formatted price string (e.g., "$9.99") */
|
|
44
|
+
priceString: string;
|
|
45
|
+
/** Currency code (e.g., "USD") */
|
|
46
|
+
currencyCode: string;
|
|
47
|
+
/** Billing period in ISO 8601 format (e.g., "P1M", "P1Y"), null for non-subscriptions */
|
|
48
|
+
normalPeriodDuration: string | null;
|
|
49
|
+
/** Available subscription options with offers */
|
|
50
|
+
subscriptionOptions?: Record<string, AdapterSubscriptionOption>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Package containing a product
|
|
54
|
+
*/
|
|
55
|
+
export interface AdapterPackage {
|
|
56
|
+
/** Package identifier (e.g., "pro_monthly") */
|
|
57
|
+
identifier: string;
|
|
58
|
+
/** Package type (e.g., "$rc_monthly", "custom") */
|
|
59
|
+
packageType: string;
|
|
60
|
+
/** The product in this package */
|
|
61
|
+
product: AdapterProduct;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Offering containing packages
|
|
65
|
+
*/
|
|
66
|
+
export interface AdapterOffering {
|
|
67
|
+
/** Offering identifier */
|
|
68
|
+
identifier: string;
|
|
69
|
+
/** Offering metadata from RevenueCat dashboard */
|
|
70
|
+
metadata: Record<string, unknown> | null;
|
|
71
|
+
/** Available packages in this offering */
|
|
72
|
+
availablePackages: AdapterPackage[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* All offerings response
|
|
76
|
+
*/
|
|
77
|
+
export interface AdapterOfferings {
|
|
78
|
+
/** Map of all offerings by identifier */
|
|
79
|
+
all: Record<string, AdapterOffering>;
|
|
80
|
+
/** Current/default offering */
|
|
81
|
+
current: AdapterOffering | null;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Entitlement info for a customer
|
|
85
|
+
*/
|
|
86
|
+
export interface AdapterEntitlementInfo {
|
|
87
|
+
/** Entitlement identifier */
|
|
88
|
+
identifier: string;
|
|
89
|
+
/** Product that granted this entitlement */
|
|
90
|
+
productIdentifier: string;
|
|
91
|
+
/** Expiration date in ISO format, null for lifetime */
|
|
92
|
+
expirationDate: string | null;
|
|
93
|
+
/** Whether subscription will auto-renew */
|
|
94
|
+
willRenew: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Customer info from RevenueCat
|
|
98
|
+
*/
|
|
99
|
+
export interface AdapterCustomerInfo {
|
|
100
|
+
/** Set of active subscription product identifiers */
|
|
101
|
+
activeSubscriptions: string[];
|
|
102
|
+
/** Entitlements information */
|
|
103
|
+
entitlements: {
|
|
104
|
+
/** Active entitlements map */
|
|
105
|
+
active: Record<string, AdapterEntitlementInfo>;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Parameters for making a purchase
|
|
110
|
+
*/
|
|
111
|
+
export interface AdapterPurchaseParams {
|
|
112
|
+
/** Package identifier to purchase */
|
|
113
|
+
packageId: string;
|
|
114
|
+
/** Offering identifier the package belongs to */
|
|
115
|
+
offeringId: string;
|
|
116
|
+
/** Customer email for web billing */
|
|
117
|
+
customerEmail?: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Result of a purchase operation
|
|
121
|
+
*/
|
|
122
|
+
export interface AdapterPurchaseResult {
|
|
123
|
+
/** Updated customer info after purchase */
|
|
124
|
+
customerInfo: AdapterCustomerInfo;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* RevenueCat Adapter Interface
|
|
128
|
+
*
|
|
129
|
+
* Implement this interface to provide platform-specific RevenueCat SDK integration.
|
|
130
|
+
*/
|
|
131
|
+
export interface SubscriptionAdapter {
|
|
132
|
+
/**
|
|
133
|
+
* Get all configured offerings
|
|
134
|
+
* @param params Optional parameters like currency
|
|
135
|
+
*/
|
|
136
|
+
getOfferings(params?: {
|
|
137
|
+
currency?: string;
|
|
138
|
+
}): Promise<AdapterOfferings>;
|
|
139
|
+
/**
|
|
140
|
+
* Get current customer info including entitlements
|
|
141
|
+
*/
|
|
142
|
+
getCustomerInfo(): Promise<AdapterCustomerInfo>;
|
|
143
|
+
/**
|
|
144
|
+
* Make a purchase
|
|
145
|
+
* @param params Purchase parameters
|
|
146
|
+
*/
|
|
147
|
+
purchase(params: AdapterPurchaseParams): Promise<AdapterPurchaseResult>;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/types/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type Exports
|
|
3
|
+
*/
|
|
4
|
+
export type { SubscriptionAdapter, AdapterOfferings, AdapterOffering, AdapterPackage, AdapterProduct, AdapterSubscriptionOption, AdapterPricingPhase, AdapterCustomerInfo, AdapterEntitlementInfo, AdapterPurchaseParams, AdapterPurchaseResult, } from './adapter';
|
|
5
|
+
export type { SubscriptionProduct, SubscriptionPackage, SubscriptionOffer, CurrentSubscription, FreeTierConfig, PackageWithLevel, } from './subscription';
|
|
6
|
+
export type { SubscriptionPeriod } from './period';
|
|
7
|
+
export { PERIOD_RANKS, ALL_PERIODS } from './period';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,cAAc,EACd,yBAAyB,EACzB,mBAAmB,EACnB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC"}
|