@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.
Files changed (52) hide show
  1. package/dist/core/index.d.ts +6 -0
  2. package/dist/core/index.d.ts.map +1 -0
  3. package/dist/core/index.js +5 -0
  4. package/dist/core/service.d.ts +74 -0
  5. package/dist/core/service.d.ts.map +1 -0
  6. package/dist/core/service.js +251 -0
  7. package/dist/core/singleton.d.ts +65 -0
  8. package/dist/core/singleton.d.ts.map +1 -0
  9. package/dist/core/singleton.js +73 -0
  10. package/dist/hooks/index.d.ts +9 -0
  11. package/dist/hooks/index.d.ts.map +1 -0
  12. package/dist/hooks/index.js +8 -0
  13. package/dist/hooks/useSubscribable.d.ts +50 -0
  14. package/dist/hooks/useSubscribable.d.ts.map +1 -0
  15. package/dist/hooks/useSubscribable.js +80 -0
  16. package/dist/hooks/useSubscriptionForPeriod.d.ts +48 -0
  17. package/dist/hooks/useSubscriptionForPeriod.d.ts.map +1 -0
  18. package/dist/hooks/useSubscriptionForPeriod.js +59 -0
  19. package/dist/hooks/useSubscriptionPeriods.d.ts +38 -0
  20. package/dist/hooks/useSubscriptionPeriods.d.ts.map +1 -0
  21. package/dist/hooks/useSubscriptionPeriods.js +44 -0
  22. package/dist/hooks/useSubscriptions.d.ts +43 -0
  23. package/dist/hooks/useSubscriptions.d.ts.map +1 -0
  24. package/dist/hooks/useSubscriptions.js +80 -0
  25. package/dist/hooks/useUserSubscription.d.ts +39 -0
  26. package/dist/hooks/useUserSubscription.d.ts.map +1 -0
  27. package/dist/hooks/useUserSubscription.js +76 -0
  28. package/dist/index.d.ts +12 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +13 -0
  31. package/dist/types/adapter.d.ts +149 -0
  32. package/dist/types/adapter.d.ts.map +1 -0
  33. package/dist/types/adapter.js +7 -0
  34. package/dist/types/index.d.ts +8 -0
  35. package/dist/types/index.d.ts.map +1 -0
  36. package/dist/types/index.js +4 -0
  37. package/dist/types/period.d.ts +18 -0
  38. package/dist/types/period.d.ts.map +1 -0
  39. package/dist/types/period.js +25 -0
  40. package/dist/types/subscription.d.ts +95 -0
  41. package/dist/types/subscription.d.ts.map +1 -0
  42. package/dist/types/subscription.js +6 -0
  43. package/dist/utils/index.d.ts +6 -0
  44. package/dist/utils/index.d.ts.map +1 -0
  45. package/dist/utils/index.js +5 -0
  46. package/dist/utils/level-calculator.d.ts +68 -0
  47. package/dist/utils/level-calculator.d.ts.map +1 -0
  48. package/dist/utils/level-calculator.js +164 -0
  49. package/dist/utils/period-parser.d.ts +45 -0
  50. package/dist/utils/period-parser.d.ts.map +1 -0
  51. package/dist/utils/period-parser.js +113 -0
  52. package/package.json +55 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Core Exports
3
+ */
4
+ export { SubscriptionService, type SubscriptionServiceConfig } from './service';
5
+ export { initializeSubscription, getSubscriptionInstance, isSubscriptionInitialized, resetSubscription, refreshSubscription, type SubscriptionConfig, } from './singleton';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEhF,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,yBAAyB,EACzB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Core Exports
3
+ */
4
+ export { SubscriptionService } from './service';
5
+ export { initializeSubscription, getSubscriptionInstance, isSubscriptionInitialized, resetSubscription, refreshSubscription, } from './singleton';
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Subscription Service
3
+ *
4
+ * Core service for managing subscription data with clean abstractions.
5
+ */
6
+ import type { AdapterPurchaseParams, AdapterPurchaseResult, SubscriptionAdapter } from '../types/adapter';
7
+ import type { CurrentSubscription, FreeTierConfig, SubscriptionOffer, SubscriptionPackage } from '../types/subscription';
8
+ /**
9
+ * Configuration for SubscriptionService
10
+ */
11
+ export interface SubscriptionServiceConfig {
12
+ /** RevenueCat adapter implementation */
13
+ adapter: SubscriptionAdapter;
14
+ /** Free tier configuration */
15
+ freeTier: FreeTierConfig;
16
+ }
17
+ /**
18
+ * Service for managing subscription data with clean abstractions.
19
+ */
20
+ export declare class SubscriptionService {
21
+ private adapter;
22
+ private freeTier;
23
+ private offersCache;
24
+ private currentSubscription;
25
+ private isLoadingOfferings;
26
+ private isLoadingCustomerInfo;
27
+ constructor(config: SubscriptionServiceConfig);
28
+ /**
29
+ * Load offerings from RevenueCat
30
+ */
31
+ loadOfferings(params?: {
32
+ currency?: string;
33
+ }): Promise<void>;
34
+ /**
35
+ * Load customer info from RevenueCat
36
+ */
37
+ loadCustomerInfo(): Promise<void>;
38
+ /**
39
+ * Get offer by ID with complete hierarchy
40
+ */
41
+ getOffer(offerId: string): SubscriptionOffer | null;
42
+ /**
43
+ * Get all offer IDs
44
+ */
45
+ getOfferIds(): string[];
46
+ /**
47
+ * Get current subscription info
48
+ */
49
+ getCurrentSubscription(): CurrentSubscription | null;
50
+ /**
51
+ * Get free tier as a SubscriptionPackage
52
+ */
53
+ getFreeTierPackage(): SubscriptionPackage;
54
+ /**
55
+ * Get free tier config
56
+ */
57
+ getFreeTierConfig(): FreeTierConfig;
58
+ /**
59
+ * Make a purchase
60
+ */
61
+ purchase(params: AdapterPurchaseParams): Promise<AdapterPurchaseResult>;
62
+ /**
63
+ * Check if offerings are loaded
64
+ */
65
+ hasLoadedOfferings(): boolean;
66
+ /**
67
+ * Check if customer info is loaded
68
+ */
69
+ hasLoadedCustomerInfo(): boolean;
70
+ private parseOffering;
71
+ private parsePackage;
72
+ private extractEntitlementsFromMetadata;
73
+ }
74
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/core/service.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAGV,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EAEpB,MAAM,uBAAuB,CAAC;AAG/B;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,wCAAwC;IACxC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,mBAAmB,CAAoC;IAC/D,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,MAAM,EAAE,yBAAyB;IAS7C;;OAEG;IACG,aAAa,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBlE;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6DvC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAInD;;OAEG;IACH,WAAW,IAAI,MAAM,EAAE;IAIvB;;OAEG;IACH,sBAAsB,IAAI,mBAAmB,GAAG,IAAI;IAIpD;;OAEG;IACH,kBAAkB,IAAI,mBAAmB;IASzC;;OAEG;IACH,iBAAiB,IAAI,cAAc;IAInC;;OAEG;IACG,QAAQ,CACZ,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IASjC;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;OAEG;IACH,qBAAqB,IAAI,OAAO;IAQhC,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,YAAY;IAgCpB,OAAO,CAAC,+BAA+B;CA6BxC"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Subscription Service
3
+ *
4
+ * Core service for managing subscription data with clean abstractions.
5
+ */
6
+ import { parseISO8601Period } from '../utils/period-parser';
7
+ /**
8
+ * Service for managing subscription data with clean abstractions.
9
+ */
10
+ export class SubscriptionService {
11
+ constructor(config) {
12
+ Object.defineProperty(this, "adapter", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: void 0
17
+ });
18
+ Object.defineProperty(this, "freeTier", {
19
+ enumerable: true,
20
+ configurable: true,
21
+ writable: true,
22
+ value: void 0
23
+ });
24
+ Object.defineProperty(this, "offersCache", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: new Map()
29
+ });
30
+ Object.defineProperty(this, "currentSubscription", {
31
+ enumerable: true,
32
+ configurable: true,
33
+ writable: true,
34
+ value: null
35
+ });
36
+ Object.defineProperty(this, "isLoadingOfferings", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: false
41
+ });
42
+ Object.defineProperty(this, "isLoadingCustomerInfo", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: false
47
+ });
48
+ this.adapter = config.adapter;
49
+ this.freeTier = config.freeTier;
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Data Loading
53
+ // ---------------------------------------------------------------------------
54
+ /**
55
+ * Load offerings from RevenueCat
56
+ */
57
+ async loadOfferings(params) {
58
+ if (this.isLoadingOfferings)
59
+ return;
60
+ this.isLoadingOfferings = true;
61
+ try {
62
+ const offerings = await this.adapter.getOfferings(params);
63
+ this.offersCache.clear();
64
+ for (const [offerId, offering] of Object.entries(offerings.all)) {
65
+ const parsedOffer = this.parseOffering(offerId, offering);
66
+ this.offersCache.set(offerId, parsedOffer);
67
+ }
68
+ // Also add current offering as 'default' if available
69
+ if (offerings.current && !this.offersCache.has('default')) {
70
+ const parsedOffer = this.parseOffering('default', offerings.current);
71
+ this.offersCache.set('default', parsedOffer);
72
+ }
73
+ }
74
+ finally {
75
+ this.isLoadingOfferings = false;
76
+ }
77
+ }
78
+ /**
79
+ * Load customer info from RevenueCat
80
+ */
81
+ async loadCustomerInfo() {
82
+ if (this.isLoadingCustomerInfo)
83
+ return;
84
+ this.isLoadingCustomerInfo = true;
85
+ try {
86
+ // Ensure offerings are loaded first so we can match packageId
87
+ if (!this.hasLoadedOfferings()) {
88
+ await this.loadOfferings();
89
+ }
90
+ const customerInfo = await this.adapter.getCustomerInfo();
91
+ const activeEntitlementIds = Object.keys(customerInfo.entitlements.active);
92
+ if (activeEntitlementIds.length === 0) {
93
+ this.currentSubscription = {
94
+ isActive: false,
95
+ entitlements: [],
96
+ };
97
+ return;
98
+ }
99
+ const firstEntitlement = customerInfo.entitlements.active[activeEntitlementIds[0]];
100
+ // Find the package for this product
101
+ let packageId;
102
+ let period;
103
+ for (const offer of this.offersCache.values()) {
104
+ const pkg = offer.packages.find(p => p.product?.productId === firstEntitlement.productIdentifier);
105
+ if (pkg) {
106
+ packageId = pkg.packageId;
107
+ period = pkg.product?.period;
108
+ break;
109
+ }
110
+ }
111
+ this.currentSubscription = {
112
+ isActive: true,
113
+ productId: firstEntitlement.productIdentifier,
114
+ packageId,
115
+ entitlements: activeEntitlementIds,
116
+ period,
117
+ expirationDate: firstEntitlement.expirationDate
118
+ ? new Date(firstEntitlement.expirationDate)
119
+ : undefined,
120
+ willRenew: firstEntitlement.willRenew,
121
+ };
122
+ }
123
+ finally {
124
+ this.isLoadingCustomerInfo = false;
125
+ }
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Public API
129
+ // ---------------------------------------------------------------------------
130
+ /**
131
+ * Get offer by ID with complete hierarchy
132
+ */
133
+ getOffer(offerId) {
134
+ return this.offersCache.get(offerId) ?? null;
135
+ }
136
+ /**
137
+ * Get all offer IDs
138
+ */
139
+ getOfferIds() {
140
+ return Array.from(this.offersCache.keys());
141
+ }
142
+ /**
143
+ * Get current subscription info
144
+ */
145
+ getCurrentSubscription() {
146
+ return this.currentSubscription;
147
+ }
148
+ /**
149
+ * Get free tier as a SubscriptionPackage
150
+ */
151
+ getFreeTierPackage() {
152
+ return {
153
+ packageId: this.freeTier.packageId,
154
+ name: this.freeTier.name,
155
+ product: undefined,
156
+ entitlements: [],
157
+ };
158
+ }
159
+ /**
160
+ * Get free tier config
161
+ */
162
+ getFreeTierConfig() {
163
+ return this.freeTier;
164
+ }
165
+ /**
166
+ * Make a purchase
167
+ */
168
+ async purchase(params) {
169
+ const result = await this.adapter.purchase(params);
170
+ // Reload customer info after purchase
171
+ await this.loadCustomerInfo();
172
+ return result;
173
+ }
174
+ /**
175
+ * Check if offerings are loaded
176
+ */
177
+ hasLoadedOfferings() {
178
+ return this.offersCache.size > 0;
179
+ }
180
+ /**
181
+ * Check if customer info is loaded
182
+ */
183
+ hasLoadedCustomerInfo() {
184
+ return this.currentSubscription !== null;
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // Private Helpers
188
+ // ---------------------------------------------------------------------------
189
+ parseOffering(offerId, offering) {
190
+ const packages = [];
191
+ // Extract entitlements from metadata if available
192
+ const metadataEntitlements = this.extractEntitlementsFromMetadata(offering.metadata);
193
+ for (const pkg of offering.availablePackages) {
194
+ packages.push(this.parsePackage(pkg, metadataEntitlements));
195
+ }
196
+ return {
197
+ offerId,
198
+ metadata: offering.metadata ?? undefined,
199
+ packages,
200
+ };
201
+ }
202
+ parsePackage(pkg, metadataEntitlements) {
203
+ const product = pkg.product;
204
+ const defaultOption = product.subscriptionOptions
205
+ ? Object.values(product.subscriptionOptions)[0]
206
+ : undefined;
207
+ const parsedProduct = {
208
+ productId: product.identifier,
209
+ name: product.title,
210
+ description: product.description ?? undefined,
211
+ price: product.price,
212
+ priceString: product.priceString,
213
+ currency: product.currencyCode,
214
+ period: parseISO8601Period(product.normalPeriodDuration),
215
+ periodDuration: product.normalPeriodDuration ?? '',
216
+ trialPeriod: defaultOption?.trial?.periodDuration ?? undefined,
217
+ introPrice: defaultOption?.introPrice?.priceString ?? undefined,
218
+ introPricePeriod: defaultOption?.introPrice?.periodDuration ?? undefined,
219
+ introPriceCycles: defaultOption?.introPrice?.cycleCount,
220
+ };
221
+ return {
222
+ packageId: pkg.identifier,
223
+ name: product.title,
224
+ product: parsedProduct,
225
+ entitlements: metadataEntitlements,
226
+ };
227
+ }
228
+ extractEntitlementsFromMetadata(metadata) {
229
+ if (!metadata)
230
+ return [];
231
+ const entitlements = [];
232
+ // Single entitlement in metadata
233
+ if (metadata.entitlement && typeof metadata.entitlement === 'string') {
234
+ entitlements.push(metadata.entitlement);
235
+ }
236
+ // Array of entitlements in metadata
237
+ if (Array.isArray(metadata.entitlements)) {
238
+ for (const ent of metadata.entitlements) {
239
+ if (typeof ent === 'string') {
240
+ entitlements.push(ent);
241
+ }
242
+ else if (typeof ent === 'object' &&
243
+ ent !== null &&
244
+ typeof ent.identifier === 'string') {
245
+ entitlements.push(ent.identifier);
246
+ }
247
+ }
248
+ }
249
+ return entitlements;
250
+ }
251
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Subscription Singleton
3
+ *
4
+ * Global singleton manager for the SubscriptionService.
5
+ */
6
+ import type { SubscriptionAdapter } from '../types/adapter';
7
+ import type { FreeTierConfig } from '../types/subscription';
8
+ import { SubscriptionService } from './service';
9
+ /**
10
+ * Configuration for initializing the subscription singleton
11
+ */
12
+ export interface SubscriptionConfig {
13
+ /** RevenueCat adapter implementation */
14
+ adapter: SubscriptionAdapter;
15
+ /** Free tier configuration */
16
+ freeTier: FreeTierConfig;
17
+ }
18
+ /**
19
+ * Initialize the subscription singleton
20
+ *
21
+ * Call this once at app startup with your RevenueCat adapter.
22
+ *
23
+ * @param config Configuration with adapter and free tier info
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { initializeSubscription } from '@sudobility/subscription_lib';
28
+ * import { createWebRevenueCatAdapter } from './adapters/web';
29
+ *
30
+ * initializeSubscription({
31
+ * adapter: createWebRevenueCatAdapter(purchases),
32
+ * freeTier: { packageId: 'free', name: 'Free' }
33
+ * });
34
+ * ```
35
+ */
36
+ export declare function initializeSubscription(config: SubscriptionConfig): void;
37
+ /**
38
+ * Get the subscription service singleton
39
+ *
40
+ * @throws Error if not initialized
41
+ */
42
+ export declare function getSubscriptionInstance(): SubscriptionService;
43
+ /**
44
+ * Check if subscription singleton is initialized
45
+ */
46
+ export declare function isSubscriptionInitialized(): boolean;
47
+ /**
48
+ * Reset the subscription singleton (mainly for testing)
49
+ */
50
+ export declare function resetSubscription(): void;
51
+ /**
52
+ * Refresh subscription data (customer info and offerings)
53
+ *
54
+ * Call this after a purchase to ensure subscription state is up to date.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import { refreshSubscription } from '@sudobility/subscription_lib';
59
+ *
60
+ * // After purchase completes
61
+ * await refreshSubscription();
62
+ * ```
63
+ */
64
+ export declare function refreshSubscription(): Promise<void>;
65
+ //# sourceMappingURL=singleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/core/singleton.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,8BAA8B;IAC9B,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAKvE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,mBAAmB,CAO7D;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKzD"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Subscription Singleton
3
+ *
4
+ * Global singleton manager for the SubscriptionService.
5
+ */
6
+ import { SubscriptionService } from './service';
7
+ let instance = null;
8
+ /**
9
+ * Initialize the subscription singleton
10
+ *
11
+ * Call this once at app startup with your RevenueCat adapter.
12
+ *
13
+ * @param config Configuration with adapter and free tier info
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { initializeSubscription } from '@sudobility/subscription_lib';
18
+ * import { createWebRevenueCatAdapter } from './adapters/web';
19
+ *
20
+ * initializeSubscription({
21
+ * adapter: createWebRevenueCatAdapter(purchases),
22
+ * freeTier: { packageId: 'free', name: 'Free' }
23
+ * });
24
+ * ```
25
+ */
26
+ export function initializeSubscription(config) {
27
+ instance = new SubscriptionService({
28
+ adapter: config.adapter,
29
+ freeTier: config.freeTier,
30
+ });
31
+ }
32
+ /**
33
+ * Get the subscription service singleton
34
+ *
35
+ * @throws Error if not initialized
36
+ */
37
+ export function getSubscriptionInstance() {
38
+ if (!instance) {
39
+ throw new Error('Subscription not initialized. Call initializeSubscription() first.');
40
+ }
41
+ return instance;
42
+ }
43
+ /**
44
+ * Check if subscription singleton is initialized
45
+ */
46
+ export function isSubscriptionInitialized() {
47
+ return instance !== null;
48
+ }
49
+ /**
50
+ * Reset the subscription singleton (mainly for testing)
51
+ */
52
+ export function resetSubscription() {
53
+ instance = null;
54
+ }
55
+ /**
56
+ * Refresh subscription data (customer info and offerings)
57
+ *
58
+ * Call this after a purchase to ensure subscription state is up to date.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { refreshSubscription } from '@sudobility/subscription_lib';
63
+ *
64
+ * // After purchase completes
65
+ * await refreshSubscription();
66
+ * ```
67
+ */
68
+ export async function refreshSubscription() {
69
+ if (!instance) {
70
+ return;
71
+ }
72
+ await Promise.all([instance.loadOfferings(), instance.loadCustomerInfo()]);
73
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Hook Exports
3
+ */
4
+ export { useSubscriptions, type UseSubscriptionsResult, } from './useSubscriptions';
5
+ export { useUserSubscription, type UseUserSubscriptionResult, } from './useUserSubscription';
6
+ export { useSubscriptionPeriods, type UseSubscriptionPeriodsResult, } from './useSubscriptionPeriods';
7
+ export { useSubscriptionForPeriod, type UseSubscriptionForPeriodResult, } from './useSubscriptionForPeriod';
8
+ export { useSubscribable, type UseSubscribableResult } from './useSubscribable';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,gBAAgB,EAChB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,mBAAmB,EACnB,KAAK,yBAAyB,GAC/B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,sBAAsB,EACtB,KAAK,4BAA4B,GAClC,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,wBAAwB,EACxB,KAAK,8BAA8B,GACpC,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Hook Exports
3
+ */
4
+ export { useSubscriptions, } from './useSubscriptions';
5
+ export { useUserSubscription, } from './useUserSubscription';
6
+ export { useSubscriptionPeriods, } from './useSubscriptionPeriods';
7
+ export { useSubscriptionForPeriod, } from './useSubscriptionForPeriod';
8
+ export { useSubscribable } from './useSubscribable';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * useSubscribable Hook
3
+ *
4
+ * Get packages that the user can subscribe to or upgrade to.
5
+ */
6
+ /**
7
+ * Result of useSubscribable hook
8
+ */
9
+ export interface UseSubscribableResult {
10
+ /** Package IDs that the user can subscribe/upgrade to */
11
+ subscribablePackageIds: string[];
12
+ /** Whether data is being loaded */
13
+ isLoading: boolean;
14
+ /** Error if loading failed */
15
+ error: Error | null;
16
+ }
17
+ /**
18
+ * Hook to get packages that the user can subscribe to or upgrade to
19
+ *
20
+ * Upgrade eligibility rules:
21
+ * - If no current subscription: all packages are subscribable
22
+ * - If on free tier: all paid packages are subscribable
23
+ * - If has subscription:
24
+ * - Package period must be >= current period (monthly → yearly OK, yearly → monthly NOT OK)
25
+ * - Package level must be >= current level (basic → pro OK, pro → basic NOT OK)
26
+ * - Level is determined by price comparison within the same period
27
+ *
28
+ * @param offerId Offer identifier
29
+ * @returns List of subscribable package IDs
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const { subscribablePackageIds } = useSubscribable('default');
34
+ * const { packages } = useSubscriptionForPeriod('default', 'monthly');
35
+ *
36
+ * return (
37
+ * <div>
38
+ * {packages.map(pkg => (
39
+ * <SubscriptionTile
40
+ * key={pkg.packageId}
41
+ * enabled={subscribablePackageIds.includes(pkg.packageId)}
42
+ * {...pkg}
43
+ * />
44
+ * ))}
45
+ * </div>
46
+ * );
47
+ * ```
48
+ */
49
+ export declare function useSubscribable(offerId: string): UseSubscribableResult;
50
+ //# sourceMappingURL=useSubscribable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSubscribable.d.ts","sourceRoot":"","sources":["../../src/hooks/useSubscribable.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,yDAAyD;IACzD,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,mCAAmC;IACnC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAsDtE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * useSubscribable Hook
3
+ *
4
+ * Get packages that the user can subscribe to or upgrade to.
5
+ */
6
+ import { useMemo } from 'react';
7
+ import { useSubscriptions } from './useSubscriptions';
8
+ import { useUserSubscription } from './useUserSubscription';
9
+ import { findUpgradeablePackages } from '../utils/level-calculator';
10
+ import { getSubscriptionInstance, isSubscriptionInitialized, } from '../core/singleton';
11
+ /**
12
+ * Hook to get packages that the user can subscribe to or upgrade to
13
+ *
14
+ * Upgrade eligibility rules:
15
+ * - If no current subscription: all packages are subscribable
16
+ * - If on free tier: all paid packages are subscribable
17
+ * - If has subscription:
18
+ * - Package period must be >= current period (monthly → yearly OK, yearly → monthly NOT OK)
19
+ * - Package level must be >= current level (basic → pro OK, pro → basic NOT OK)
20
+ * - Level is determined by price comparison within the same period
21
+ *
22
+ * @param offerId Offer identifier
23
+ * @returns List of subscribable package IDs
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const { subscribablePackageIds } = useSubscribable('default');
28
+ * const { packages } = useSubscriptionForPeriod('default', 'monthly');
29
+ *
30
+ * return (
31
+ * <div>
32
+ * {packages.map(pkg => (
33
+ * <SubscriptionTile
34
+ * key={pkg.packageId}
35
+ * enabled={subscribablePackageIds.includes(pkg.packageId)}
36
+ * {...pkg}
37
+ * />
38
+ * ))}
39
+ * </div>
40
+ * );
41
+ * ```
42
+ */
43
+ export function useSubscribable(offerId) {
44
+ const { offer, isLoading: loadingOffer, error: offerError, } = useSubscriptions(offerId);
45
+ const { subscription, isLoading: loadingSub, error: subError, } = useUserSubscription();
46
+ const isLoading = loadingOffer || loadingSub;
47
+ const error = offerError || subError;
48
+ const subscribablePackageIds = useMemo(() => {
49
+ if (!offer) {
50
+ return [];
51
+ }
52
+ // Get all packages including free tier
53
+ let allPackages = [...offer.packages];
54
+ // Add free tier if initialized
55
+ if (isSubscriptionInitialized()) {
56
+ const service = getSubscriptionInstance();
57
+ const freeTier = service.getFreeTierPackage();
58
+ // Only add if not already in the list
59
+ if (!allPackages.some(p => p.packageId === freeTier.packageId)) {
60
+ allPackages = [freeTier, ...allPackages];
61
+ }
62
+ }
63
+ // No subscription or inactive = all packages subscribable
64
+ if (!subscription || !subscription.isActive) {
65
+ return allPackages.map(p => p.packageId);
66
+ }
67
+ // Pass both packageId and productId for matching
68
+ const current = {
69
+ packageId: subscription.packageId,
70
+ productId: subscription.productId,
71
+ };
72
+ // If we don't know the current package or product, return all (shouldn't happen)
73
+ if (!current.packageId && !current.productId) {
74
+ return allPackages.map(p => p.packageId);
75
+ }
76
+ // Calculate upgradeable packages
77
+ return findUpgradeablePackages(current, allPackages);
78
+ }, [offer, subscription]);
79
+ return { subscribablePackageIds, isLoading, error };
80
+ }