@umituz/react-native-subscription 1.1.1 → 1.3.0

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 (89) hide show
  1. package/package.json +7 -23
  2. package/src/index.ts +50 -19
  3. package/src/presentation/hooks/__tests__/useUserTier.authenticated.test.ts +79 -0
  4. package/src/presentation/hooks/__tests__/useUserTier.guest.test.ts +70 -0
  5. package/src/presentation/hooks/__tests__/useUserTier.states.test.ts +167 -0
  6. package/src/presentation/hooks/usePremiumGate.ts +116 -0
  7. package/src/presentation/hooks/useUserTier.ts +78 -0
  8. package/src/presentation/hooks/useUserTierWithRepository.ts +171 -0
  9. package/src/utils/__tests__/authUtils.test.ts +52 -0
  10. package/src/utils/__tests__/edgeCases.test.ts +84 -0
  11. package/src/utils/__tests__/premiumUtils.test.ts +178 -0
  12. package/src/utils/__tests__/tierUtils.test.ts +148 -0
  13. package/src/utils/__tests__/validation.test.ts +108 -0
  14. package/src/utils/authUtils.ts +65 -0
  15. package/src/utils/premiumAsyncUtils.ts +60 -0
  16. package/src/utils/premiumStatusUtils.ts +79 -0
  17. package/src/utils/premiumUtils.ts +9 -0
  18. package/src/utils/tierUtils.ts +97 -0
  19. package/src/utils/types.ts +37 -0
  20. package/src/utils/userTierUtils.ts +81 -0
  21. package/src/utils/validation.ts +119 -0
  22. package/lib/application/ports/ISubscriptionRepository.d.ts +0 -25
  23. package/lib/application/ports/ISubscriptionRepository.d.ts.map +0 -1
  24. package/lib/application/ports/ISubscriptionRepository.js +0 -9
  25. package/lib/application/ports/ISubscriptionRepository.js.map +0 -1
  26. package/lib/application/ports/ISubscriptionService.d.ts +0 -28
  27. package/lib/application/ports/ISubscriptionService.d.ts.map +0 -1
  28. package/lib/application/ports/ISubscriptionService.js +0 -6
  29. package/lib/application/ports/ISubscriptionService.js.map +0 -1
  30. package/lib/domain/entities/SubscriptionStatus.d.ts +0 -31
  31. package/lib/domain/entities/SubscriptionStatus.d.ts.map +0 -1
  32. package/lib/domain/entities/SubscriptionStatus.js +0 -39
  33. package/lib/domain/entities/SubscriptionStatus.js.map +0 -1
  34. package/lib/domain/errors/SubscriptionError.d.ts +0 -18
  35. package/lib/domain/errors/SubscriptionError.d.ts.map +0 -1
  36. package/lib/domain/errors/SubscriptionError.js +0 -30
  37. package/lib/domain/errors/SubscriptionError.js.map +0 -1
  38. package/lib/domain/value-objects/SubscriptionConfig.d.ts +0 -15
  39. package/lib/domain/value-objects/SubscriptionConfig.d.ts.map +0 -1
  40. package/lib/domain/value-objects/SubscriptionConfig.js +0 -6
  41. package/lib/domain/value-objects/SubscriptionConfig.js.map +0 -1
  42. package/lib/index.d.ts +0 -33
  43. package/lib/index.d.ts.map +0 -1
  44. package/lib/index.js +0 -43
  45. package/lib/index.js.map +0 -1
  46. package/lib/infrastructure/services/ActivationHandler.d.ts +0 -20
  47. package/lib/infrastructure/services/ActivationHandler.d.ts.map +0 -1
  48. package/lib/infrastructure/services/ActivationHandler.js +0 -71
  49. package/lib/infrastructure/services/ActivationHandler.js.map +0 -1
  50. package/lib/infrastructure/services/SubscriptionService.d.ts +0 -22
  51. package/lib/infrastructure/services/SubscriptionService.d.ts.map +0 -1
  52. package/lib/infrastructure/services/SubscriptionService.js +0 -110
  53. package/lib/infrastructure/services/SubscriptionService.js.map +0 -1
  54. package/lib/presentation/hooks/useSubscription.d.ts +0 -33
  55. package/lib/presentation/hooks/useSubscription.d.ts.map +0 -1
  56. package/lib/presentation/hooks/useSubscription.js +0 -129
  57. package/lib/presentation/hooks/useSubscription.js.map +0 -1
  58. package/lib/utils/dateUtils.d.ts +0 -39
  59. package/lib/utils/dateUtils.d.ts.map +0 -1
  60. package/lib/utils/dateUtils.js +0 -117
  61. package/lib/utils/dateUtils.js.map +0 -1
  62. package/lib/utils/dateValidationUtils.d.ts +0 -20
  63. package/lib/utils/dateValidationUtils.d.ts.map +0 -1
  64. package/lib/utils/dateValidationUtils.js +0 -39
  65. package/lib/utils/dateValidationUtils.js.map +0 -1
  66. package/lib/utils/periodUtils.d.ts +0 -38
  67. package/lib/utils/periodUtils.d.ts.map +0 -1
  68. package/lib/utils/periodUtils.js +0 -70
  69. package/lib/utils/periodUtils.js.map +0 -1
  70. package/lib/utils/planDetectionUtils.d.ts +0 -17
  71. package/lib/utils/planDetectionUtils.d.ts.map +0 -1
  72. package/lib/utils/planDetectionUtils.js +0 -31
  73. package/lib/utils/planDetectionUtils.js.map +0 -1
  74. package/lib/utils/priceUtils.d.ts +0 -23
  75. package/lib/utils/priceUtils.d.ts.map +0 -1
  76. package/lib/utils/priceUtils.js +0 -29
  77. package/lib/utils/priceUtils.js.map +0 -1
  78. package/lib/utils/subscriptionConstants.d.ts +0 -62
  79. package/lib/utils/subscriptionConstants.d.ts.map +0 -1
  80. package/lib/utils/subscriptionConstants.js +0 -61
  81. package/lib/utils/subscriptionConstants.js.map +0 -1
  82. package/src/utils/dateUtils.test.ts +0 -116
  83. package/src/utils/dateUtils.ts +0 -147
  84. package/src/utils/periodUtils.ts +0 -104
  85. package/src/utils/planDetectionUtils.test.ts +0 -47
  86. package/src/utils/planDetectionUtils.ts +0 -40
  87. package/src/utils/priceUtils.test.ts +0 -35
  88. package/src/utils/priceUtils.ts +0 -31
  89. package/src/utils/subscriptionConstants.ts +0 -70
@@ -1,62 +0,0 @@
1
- /**
2
- * Subscription Constants
3
- * Centralized constants for subscription operations
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only constants, no logic
7
- * - DRY: All constants in one place
8
- * - KISS: Simple, clear constant definitions
9
- */
10
- /**
11
- * Subscription plan types
12
- */
13
- export declare const SUBSCRIPTION_PLAN_TYPES: {
14
- readonly WEEKLY: "weekly";
15
- readonly MONTHLY: "monthly";
16
- readonly YEARLY: "yearly";
17
- readonly UNKNOWN: "unknown";
18
- };
19
- export type SubscriptionPlanType = (typeof SUBSCRIPTION_PLAN_TYPES)[keyof typeof SUBSCRIPTION_PLAN_TYPES];
20
- /**
21
- * Minimum expected subscription durations in days
22
- * Used to detect sandbox accelerated timers
23
- * Includes 1 day tolerance for clock skew
24
- */
25
- export declare const MIN_SUBSCRIPTION_DURATIONS_DAYS: {
26
- readonly WEEKLY: 6;
27
- readonly MONTHLY: 28;
28
- readonly YEARLY: 360;
29
- readonly UNKNOWN: 28;
30
- };
31
- /**
32
- * Subscription period multipliers
33
- * Days to add for each subscription type
34
- */
35
- export declare const SUBSCRIPTION_PERIOD_DAYS: {
36
- readonly WEEKLY: 7;
37
- };
38
- /**
39
- * Date calculation constants
40
- */
41
- export declare const DATE_CONSTANTS: {
42
- readonly MILLISECONDS_PER_DAY: number;
43
- readonly DEFAULT_LOCALE: "en-US";
44
- };
45
- /**
46
- * Subscription period unit mappings
47
- * Maps RevenueCat period units to our internal types
48
- */
49
- export declare const SUBSCRIPTION_PERIOD_UNITS: {
50
- readonly WEEK: "WEEK";
51
- readonly MONTH: "MONTH";
52
- readonly YEAR: "YEAR";
53
- };
54
- /**
55
- * Product ID keywords for plan detection
56
- */
57
- export declare const PRODUCT_ID_KEYWORDS: {
58
- readonly WEEKLY: readonly ["weekly", "week"];
59
- readonly MONTHLY: readonly ["monthly", "month"];
60
- readonly YEARLY: readonly ["yearly", "year", "annual"];
61
- };
62
- //# sourceMappingURL=subscriptionConstants.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"subscriptionConstants.d.ts","sourceRoot":"","sources":["../../src/utils/subscriptionConstants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;;;CAK1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAC9B,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzE;;;;GAIG;AACH,eAAO,MAAM,+BAA+B;;;;;CAKlC,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,wBAAwB;;CAE3B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,cAAc;;;CAGjB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,yBAAyB;;;;CAI5B,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC"}
@@ -1,61 +0,0 @@
1
- /**
2
- * Subscription Constants
3
- * Centralized constants for subscription operations
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only constants, no logic
7
- * - DRY: All constants in one place
8
- * - KISS: Simple, clear constant definitions
9
- */
10
- /**
11
- * Subscription plan types
12
- */
13
- export const SUBSCRIPTION_PLAN_TYPES = {
14
- WEEKLY: 'weekly',
15
- MONTHLY: 'monthly',
16
- YEARLY: 'yearly',
17
- UNKNOWN: 'unknown',
18
- };
19
- /**
20
- * Minimum expected subscription durations in days
21
- * Used to detect sandbox accelerated timers
22
- * Includes 1 day tolerance for clock skew
23
- */
24
- export const MIN_SUBSCRIPTION_DURATIONS_DAYS = {
25
- WEEKLY: 6,
26
- MONTHLY: 28,
27
- YEARLY: 360,
28
- UNKNOWN: 28,
29
- };
30
- /**
31
- * Subscription period multipliers
32
- * Days to add for each subscription type
33
- */
34
- export const SUBSCRIPTION_PERIOD_DAYS = {
35
- WEEKLY: 7,
36
- };
37
- /**
38
- * Date calculation constants
39
- */
40
- export const DATE_CONSTANTS = {
41
- MILLISECONDS_PER_DAY: 1000 * 60 * 60 * 24,
42
- DEFAULT_LOCALE: 'en-US',
43
- };
44
- /**
45
- * Subscription period unit mappings
46
- * Maps RevenueCat period units to our internal types
47
- */
48
- export const SUBSCRIPTION_PERIOD_UNITS = {
49
- WEEK: 'WEEK',
50
- MONTH: 'MONTH',
51
- YEAR: 'YEAR',
52
- };
53
- /**
54
- * Product ID keywords for plan detection
55
- */
56
- export const PRODUCT_ID_KEYWORDS = {
57
- WEEKLY: ['weekly', 'week'],
58
- MONTHLY: ['monthly', 'month'],
59
- YEARLY: ['yearly', 'year', 'annual'],
60
- };
61
- //# sourceMappingURL=subscriptionConstants.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"subscriptionConstants.js","sourceRoot":"","sources":["../../src/utils/subscriptionConstants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;CACV,CAAC;AAKX;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,EAAE;IACX,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,EAAE;CACH,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,MAAM,EAAE,CAAC;CACD,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,oBAAoB,EAAE,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE;IACzC,cAAc,EAAE,OAAO;CACf,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;CACJ,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;CAC5B,CAAC"}
@@ -1,116 +0,0 @@
1
- /**
2
- * Tests for Date Utilities
3
- */
4
-
5
- import {
6
- formatExpirationDate,
7
- calculateExpirationDate,
8
- } from '../utils/dateUtils';
9
- import { SUBSCRIPTION_PLAN_TYPES } from '../utils/subscriptionConstants';
10
-
11
- describe('Date Utils', () => {
12
- describe('formatExpirationDate', () => {
13
- it('should return null for null expiration', () => {
14
- expect(formatExpirationDate(null)).toBeNull();
15
- });
16
-
17
- it('should format date correctly', () => {
18
- const date = '2024-12-25T00:00:00.000Z';
19
- const formatted = formatExpirationDate(date);
20
-
21
- expect(formatted).toMatch(/December 25, 2024/);
22
- });
23
-
24
- it('should use custom locale', () => {
25
- const date = '2024-12-25T00:00:00.000Z';
26
- const formatted = formatExpirationDate(date, 'tr-TR');
27
-
28
- expect(formatted).toBeTruthy();
29
- expect(typeof formatted).toBe('string');
30
- });
31
-
32
- it('should return null for invalid date', () => {
33
- const formatted = formatExpirationDate('invalid-date');
34
- expect(formatted).toBeNull();
35
- });
36
- });
37
-
38
- describe('calculateExpirationDate', () => {
39
- beforeEach(() => {
40
- // Mock current date for consistent testing
41
- jest.useFakeTimers();
42
- jest.setSystemTime(new Date('2024-01-15T00:00:00.000Z'));
43
- });
44
-
45
- afterEach(() => {
46
- jest.useRealTimers();
47
- });
48
-
49
- it('should return null for null productId', () => {
50
- expect(calculateExpirationDate(null)).toBeNull();
51
- expect(calculateExpirationDate('')).toBeNull();
52
- expect(calculateExpirationDate(undefined)).toBeNull();
53
- });
54
-
55
- it('should calculate weekly expiration', () => {
56
- const result = calculateExpirationDate('com.app.weekly');
57
- const expectedDate = new Date('2024-01-22T00:00:00.000Z');
58
-
59
- expect(result).toBe(expectedDate.toISOString());
60
- });
61
-
62
- it('should calculate monthly expiration', () => {
63
- const result = calculateExpirationDate('com.app.monthly');
64
- const expectedDate = new Date('2024-02-15T00:00:00.000Z');
65
-
66
- expect(result).toBe(expectedDate.toISOString());
67
- });
68
-
69
- it('should calculate yearly expiration', () => {
70
- const result = calculateExpirationDate('com.app.yearly');
71
- const expectedDate = new Date('2025-01-15T00:00:00.000Z');
72
-
73
- expect(result).toBe(expectedDate.toISOString());
74
- });
75
-
76
- it('should default to monthly for unknown plan', () => {
77
- const result = calculateExpirationDate('com.app.unknown');
78
- const expectedDate = new Date('2024-02-15T00:00:00.000Z');
79
-
80
- expect(result).toBe(expectedDate.toISOString());
81
- });
82
-
83
- it('should trust valid RevenueCat date', () => {
84
- const futureDate = new Date('2024-02-20T00:00:00.000Z');
85
- const result = calculateExpirationDate('com.app.monthly', futureDate.toISOString());
86
-
87
- expect(result).toBe(futureDate.toISOString());
88
- });
89
-
90
- it('should ignore RevenueCat date if too short (sandbox)', () => {
91
- const nearFutureDate = new Date('2024-01-16T00:00:00.000Z'); // Only 1 day
92
- const result = calculateExpirationDate('com.app.monthly', nearFutureDate.toISOString());
93
-
94
- // Should calculate manually instead of using RevenueCat date
95
- const expectedDate = new Date('2024-02-15T00:00:00.000Z');
96
- expect(result).toBe(expectedDate.toISOString());
97
- });
98
-
99
- it('should ignore past RevenueCat date', () => {
100
- const pastDate = new Date('2024-01-10T00:00:00.000Z');
101
- const result = calculateExpirationDate('com.app.monthly', pastDate.toISOString());
102
-
103
- // Should calculate manually instead of using RevenueCat date
104
- const expectedDate = new Date('2024-02-15T00:00:00.000Z');
105
- expect(result).toBe(expectedDate.toISOString());
106
- });
107
-
108
- it('should handle invalid RevenueCat date', () => {
109
- const result = calculateExpirationDate('com.app.monthly', 'invalid-date');
110
-
111
- // Should calculate manually instead of using RevenueCat date
112
- const expectedDate = new Date('2024-02-15T00:00:00.000Z');
113
- expect(result).toBe(expectedDate.toISOString());
114
- });
115
- });
116
- });
@@ -1,147 +0,0 @@
1
- /**
2
- * Date Utilities
3
- * Subscription date-related helper functions
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only date formatting and calculation
7
- * - DRY: No code duplication
8
- * - KISS: Simple, clear implementations
9
- */
10
-
11
- import { DATE_CONSTANTS } from './subscriptionConstants';
12
- import { extractPlanFromProductId } from './planDetectionUtils';
13
- import {
14
- SUBSCRIPTION_PLAN_TYPES,
15
- MIN_SUBSCRIPTION_DURATIONS_DAYS,
16
- SUBSCRIPTION_PERIOD_DAYS,
17
- type SubscriptionPlanType,
18
- } from './subscriptionConstants';
19
-
20
- /**
21
- * Format expiration date for display
22
- */
23
- export function formatExpirationDate(
24
- expiresAt: string | null,
25
- locale: string = DATE_CONSTANTS.DEFAULT_LOCALE,
26
- ): string | null {
27
- if (!expiresAt) {
28
- return null;
29
- }
30
-
31
- try {
32
- const date = new Date(expiresAt);
33
- if (isNaN(date.getTime())) {
34
- return null;
35
- }
36
- return date.toLocaleDateString(locale, {
37
- year: 'numeric',
38
- month: 'long',
39
- day: 'numeric',
40
- });
41
- } catch {
42
- return null;
43
- }
44
- }
45
-
46
- /**
47
- * Calculate expiration date based on subscription plan
48
- *
49
- * This function handles:
50
- * - RevenueCat sandbox accelerated timers (detects and recalculates)
51
- * - Production dates (trusts RevenueCat's date if valid)
52
- * - Monthly subscriptions: Same day next month (e.g., Nov 10 → Dec 10)
53
- * - Yearly subscriptions: Same day next year (e.g., Nov 10, 2024 → Nov 10, 2025)
54
- * - Weekly subscriptions: +7 days
55
- *
56
- * @param productId - Product identifier (e.g., "com.company.app.monthly")
57
- * @param revenueCatExpiresAt - Optional expiration date from RevenueCat API
58
- * @returns ISO date string for expiration, or null if invalid
59
- *
60
- * @example
61
- * // Monthly subscription purchased on Nov 10, 2024
62
- * calculateExpirationDate('com.company.app.monthly', null)
63
- * // Returns: '2024-12-10T...' (Dec 10, 2024)
64
- *
65
- * @example
66
- * // Yearly subscription purchased on Nov 10, 2024
67
- * calculateExpirationDate('com.company.app.yearly', null)
68
- * // Returns: '2025-11-10T...' (Nov 10, 2025)
69
- */
70
- export function calculateExpirationDate(
71
- productId: string | null | undefined,
72
- revenueCatExpiresAt?: string | null,
73
- ): string | null {
74
- if (!productId) return null;
75
-
76
- const plan = extractPlanFromProductId(productId);
77
- const now = new Date();
78
-
79
- // Check if RevenueCat's date is valid and not sandbox accelerated
80
- if (revenueCatExpiresAt) {
81
- try {
82
- const rcDate = new Date(revenueCatExpiresAt);
83
-
84
- // Only trust if date is in the future
85
- if (rcDate > now) {
86
- // Detect sandbox accelerated timers by checking duration
87
- const durationMs = rcDate.getTime() - now.getTime();
88
- const durationDays =
89
- durationMs / DATE_CONSTANTS.MILLISECONDS_PER_DAY;
90
-
91
- // Get minimum expected duration for this plan type
92
- const minDurationMap: Record<SubscriptionPlanType, number> = {
93
- [SUBSCRIPTION_PLAN_TYPES.WEEKLY]:
94
- MIN_SUBSCRIPTION_DURATIONS_DAYS.WEEKLY,
95
- [SUBSCRIPTION_PLAN_TYPES.MONTHLY]:
96
- MIN_SUBSCRIPTION_DURATIONS_DAYS.MONTHLY,
97
- [SUBSCRIPTION_PLAN_TYPES.YEARLY]:
98
- MIN_SUBSCRIPTION_DURATIONS_DAYS.YEARLY,
99
- [SUBSCRIPTION_PLAN_TYPES.UNKNOWN]:
100
- MIN_SUBSCRIPTION_DURATIONS_DAYS.UNKNOWN,
101
- };
102
-
103
- const minDuration = minDurationMap[plan];
104
-
105
- // If duration is reasonable, trust RevenueCat's date (production)
106
- if (durationDays >= minDuration) {
107
- return rcDate.toISOString();
108
- }
109
- // Otherwise, fall through to manual calculation (sandbox accelerated)
110
- }
111
- } catch {
112
- // Invalid date, fall through to calculation
113
- }
114
- }
115
-
116
- // Calculate production-equivalent expiration date
117
- // Use current date as base to preserve the day of month/year
118
- const calculatedDate = new Date(now);
119
-
120
- switch (plan) {
121
- case SUBSCRIPTION_PLAN_TYPES.WEEKLY:
122
- // Weekly: +7 days
123
- calculatedDate.setDate(
124
- calculatedDate.getDate() + SUBSCRIPTION_PERIOD_DAYS.WEEKLY,
125
- );
126
- break;
127
-
128
- case SUBSCRIPTION_PLAN_TYPES.MONTHLY:
129
- // Monthly: Same day next month
130
- // This handles edge cases like Jan 31 → Feb 28/29 correctly
131
- calculatedDate.setMonth(calculatedDate.getMonth() + 1);
132
- break;
133
-
134
- case SUBSCRIPTION_PLAN_TYPES.YEARLY:
135
- // Yearly: Same day next year
136
- // This handles leap years correctly (Feb 29 → Feb 28/29)
137
- calculatedDate.setFullYear(calculatedDate.getFullYear() + 1);
138
- break;
139
-
140
- default:
141
- // Unknown plan type - default to 1 month
142
- calculatedDate.setMonth(calculatedDate.getMonth() + 1);
143
- break;
144
- }
145
-
146
- return calculatedDate.toISOString();
147
- }
@@ -1,104 +0,0 @@
1
- /**
2
- * Period Utilities
3
- * Subscription period-related helper functions
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only period-related operations
7
- * - DRY: No code duplication
8
- * - KISS: Simple, clear implementations
9
- */
10
-
11
- import { SUBSCRIPTION_PERIOD_UNITS } from './subscriptionConstants';
12
-
13
- /**
14
- * Get subscription period text from RevenueCat package
15
- * Extracts readable period text from PurchasesPackage or subscription period object
16
- *
17
- * @param input - RevenueCat PurchasesPackage or subscription period object
18
- * @returns Human-readable period text (e.g., "month", "year", "2 months")
19
- *
20
- * @example
21
- * // From PurchasesPackage
22
- * getPeriodText(pkg) // Returns: "month"
23
- *
24
- * @example
25
- * // From subscription period object
26
- * getPeriodText({ unit: "MONTH", numberOfUnits: 1 }) // Returns: "month"
27
- * getPeriodText({ unit: "YEAR", numberOfUnits: 1 }) // Returns: "year"
28
- * getPeriodText({ unit: "MONTH", numberOfUnits: 3 }) // Returns: "3 months"
29
- */
30
- export function getPeriodText(
31
- input:
32
- | {
33
- product?: {
34
- subscriptionPeriod?:
35
- | {
36
- unit: string;
37
- numberOfUnits: number;
38
- }
39
- | string
40
- | null;
41
- };
42
- }
43
- | {
44
- unit: string;
45
- numberOfUnits: number;
46
- }
47
- | null
48
- | undefined,
49
- ): string {
50
- if (!input) return '';
51
-
52
- // Extract subscription period from PurchasesPackage or use directly
53
- let subscriptionPeriod:
54
- | {
55
- unit: string;
56
- numberOfUnits: number;
57
- }
58
- | null
59
- | undefined;
60
-
61
- // Check if input is PurchasesPackage (has product property)
62
- if ('product' in input && input.product?.subscriptionPeriod) {
63
- const period = input.product.subscriptionPeriod;
64
- // Type guard: check if period is an object with unit and numberOfUnits
65
- // RevenueCat's subscriptionPeriod can be string | null | object
66
- if (
67
- typeof period === 'object' &&
68
- period !== null &&
69
- 'unit' in period &&
70
- 'numberOfUnits' in period
71
- ) {
72
- subscriptionPeriod = {
73
- unit: period.unit as string,
74
- numberOfUnits: period.numberOfUnits as number,
75
- };
76
- }
77
- // If period is string, we can't extract unit/numberOfUnits, return empty
78
- } else if ('unit' in input && 'numberOfUnits' in input) {
79
- // Input is already a subscription period object
80
- subscriptionPeriod = {
81
- unit: input.unit,
82
- numberOfUnits: input.numberOfUnits,
83
- };
84
- }
85
-
86
- if (!subscriptionPeriod) return '';
87
-
88
- const { unit, numberOfUnits } = subscriptionPeriod;
89
-
90
- if (unit === SUBSCRIPTION_PERIOD_UNITS.MONTH) {
91
- return numberOfUnits === 1 ? 'month' : `${numberOfUnits} months`;
92
- }
93
-
94
- if (unit === SUBSCRIPTION_PERIOD_UNITS.YEAR) {
95
- return numberOfUnits === 1 ? 'year' : `${numberOfUnits} years`;
96
- }
97
-
98
- if (unit === SUBSCRIPTION_PERIOD_UNITS.WEEK) {
99
- return numberOfUnits === 1 ? 'week' : `${numberOfUnits} weeks`;
100
- }
101
-
102
- return '';
103
- }
104
-
@@ -1,47 +0,0 @@
1
- /**
2
- * Tests for Plan Detection Utilities
3
- */
4
-
5
- import { extractPlanFromProductId } from '../utils/planDetectionUtils';
6
- import { SUBSCRIPTION_PLAN_TYPES } from '../utils/subscriptionConstants';
7
-
8
- describe('Plan Detection Utils', () => {
9
- describe('extractPlanFromProductId', () => {
10
- it('should return UNKNOWN for null/undefined/empty productId', () => {
11
- expect(extractPlanFromProductId(null)).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
12
- expect(extractPlanFromProductId(undefined)).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
13
- expect(extractPlanFromProductId('')).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
14
- });
15
-
16
- it('should detect weekly plans', () => {
17
- expect(extractPlanFromProductId('com.app.weekly')).toBe(SUBSCRIPTION_PLAN_TYPES.WEEKLY);
18
- expect(extractPlanFromProductId('com.app.week')).toBe(SUBSCRIPTION_PLAN_TYPES.WEEKLY);
19
- expect(extractPlanFromProductId('WEEKLY_PREMIUM')).toBe(SUBSCRIPTION_PLAN_TYPES.WEEKLY);
20
- });
21
-
22
- it('should detect monthly plans', () => {
23
- expect(extractPlanFromProductId('com.app.monthly')).toBe(SUBSCRIPTION_PLAN_TYPES.MONTHLY);
24
- expect(extractPlanFromProductId('com.app.month')).toBe(SUBSCRIPTION_PLAN_TYPES.MONTHLY);
25
- expect(extractPlanFromProductId('MONTHLY_PREMIUM')).toBe(SUBSCRIPTION_PLAN_TYPES.MONTHLY);
26
- });
27
-
28
- it('should detect yearly plans', () => {
29
- expect(extractPlanFromProductId('com.app.yearly')).toBe(SUBSCRIPTION_PLAN_TYPES.YEARLY);
30
- expect(extractPlanFromProductId('com.app.year')).toBe(SUBSCRIPTION_PLAN_TYPES.YEARLY);
31
- expect(extractPlanFromProductId('com.app.annual')).toBe(SUBSCRIPTION_PLAN_TYPES.YEARLY);
32
- expect(extractPlanFromProductId('YEARLY_PREMIUM')).toBe(SUBSCRIPTION_PLAN_TYPES.YEARLY);
33
- });
34
-
35
- it('should be case insensitive', () => {
36
- expect(extractPlanFromProductId('WEEKLY')).toBe(SUBSCRIPTION_PLAN_TYPES.WEEKLY);
37
- expect(extractPlanFromProductId('Monthly')).toBe(SUBSCRIPTION_PLAN_TYPES.MONTHLY);
38
- expect(extractPlanFromProductId('YEARLY')).toBe(SUBSCRIPTION_PLAN_TYPES.YEARLY);
39
- });
40
-
41
- it('should return UNKNOWN for unrecognized patterns', () => {
42
- expect(extractPlanFromProductId('com.app.lifetime')).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
43
- expect(extractPlanFromProductId('com.app.premium')).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
44
- expect(extractPlanFromProductId('random_string')).toBe(SUBSCRIPTION_PLAN_TYPES.UNKNOWN);
45
- });
46
- });
47
- });
@@ -1,40 +0,0 @@
1
- /**
2
- * Plan Detection Utilities
3
- * Utilities for detecting subscription plan types from product IDs
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only plan detection logic
7
- * - DRY: No code duplication
8
- * - KISS: Simple, clear implementations
9
- */
10
-
11
- import {
12
- SUBSCRIPTION_PLAN_TYPES,
13
- PRODUCT_ID_KEYWORDS,
14
- type SubscriptionPlanType,
15
- } from './subscriptionConstants';
16
-
17
- /**
18
- * Extract subscription plan type from product ID
19
- * Example: "com.company.app.weekly" → "weekly"
20
- * @internal
21
- */
22
- export function extractPlanFromProductId(
23
- productId: string | null | undefined,
24
- ): SubscriptionPlanType {
25
- if (!productId) return SUBSCRIPTION_PLAN_TYPES.UNKNOWN;
26
-
27
- const lower = productId.toLowerCase();
28
-
29
- if (PRODUCT_ID_KEYWORDS.WEEKLY.some((keyword) => lower.includes(keyword))) {
30
- return SUBSCRIPTION_PLAN_TYPES.WEEKLY;
31
- }
32
- if (PRODUCT_ID_KEYWORDS.MONTHLY.some((keyword) => lower.includes(keyword))) {
33
- return SUBSCRIPTION_PLAN_TYPES.MONTHLY;
34
- }
35
- if (PRODUCT_ID_KEYWORDS.YEARLY.some((keyword) => lower.includes(keyword))) {
36
- return SUBSCRIPTION_PLAN_TYPES.YEARLY;
37
- }
38
-
39
- return SUBSCRIPTION_PLAN_TYPES.UNKNOWN;
40
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Tests for Price Utilities
3
- */
4
-
5
- import { formatPrice } from '../utils/priceUtils';
6
-
7
- describe('Price Utils', () => {
8
- describe('formatPrice', () => {
9
- it('should format USD price', () => {
10
- expect(formatPrice(9.99, 'USD')).toBe('$9.99');
11
- });
12
-
13
- it('should format EUR price', () => {
14
- expect(formatPrice(19.99, 'EUR')).toMatch(/€19\.99/);
15
- });
16
-
17
- it('should format TRY price', () => {
18
- const result = formatPrice(229.99, 'TRY');
19
- expect(result).toBeTruthy();
20
- expect(result).toContain('229.99');
21
- });
22
-
23
- it('should format whole numbers', () => {
24
- expect(formatPrice(10, 'USD')).toBe('$10.00');
25
- });
26
-
27
- it('should handle zero price', () => {
28
- expect(formatPrice(0, 'USD')).toBe('$0.00');
29
- });
30
-
31
- it('should format large numbers', () => {
32
- expect(formatPrice(999.99, 'USD')).toBe('$999.99');
33
- });
34
- });
35
- });
@@ -1,31 +0,0 @@
1
- /**
2
- * Price Utilities
3
- * Subscription price-related helper functions
4
- *
5
- * Following SOLID, DRY, KISS principles:
6
- * - Single Responsibility: Only price-related operations
7
- * - DRY: No code duplication
8
- * - KISS: Simple, clear implementations
9
- */
10
-
11
- import { DATE_CONSTANTS } from './subscriptionConstants';
12
-
13
- /**
14
- * Format price for display
15
- * Formats a price value with currency code using Intl.NumberFormat
16
- *
17
- * @param price - Price value (e.g., 9.99)
18
- * @param currencyCode - ISO 4217 currency code (e.g., "USD", "EUR", "TRY")
19
- * @returns Formatted price string (e.g., "$9.99", "€9.99", "₺9.99")
20
- *
21
- * @example
22
- * formatPrice(9.99, "USD") // Returns: "$9.99"
23
- * formatPrice(229.99, "TRY") // Returns: "₺229.99"
24
- */
25
- export function formatPrice(price: number, currencyCode: string): string {
26
- return new Intl.NumberFormat(DATE_CONSTANTS.DEFAULT_LOCALE, {
27
- style: 'currency',
28
- currency: currencyCode,
29
- }).format(price);
30
- }
31
-