@umituz/react-native-subscription 2.37.39 → 2.37.40

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 (131) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditLimitCalculator.ts +1 -9
  3. package/src/domains/credits/application/DeductCreditsCommand.ts +13 -6
  4. package/src/domains/credits/application/RefundCreditsCommand.ts +1 -5
  5. package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +1 -5
  6. package/src/domains/credits/application/creditDocumentHelpers.ts +2 -9
  7. package/src/domains/credits/core/Credits.ts +0 -23
  8. package/src/domains/credits/core/CreditsMapper.ts +0 -6
  9. package/src/domains/credits/core/UserCreditsDocument.ts +0 -12
  10. package/src/domains/credits/infrastructure/CreditsRepositoryManager.ts +0 -21
  11. package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +2 -1
  12. package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +2 -2
  13. package/src/domains/credits/presentation/useCredits.ts +10 -9
  14. package/src/domains/paywall/components/PaywallContainer.types.ts +0 -28
  15. package/src/domains/paywall/components/PaywallModal.styles.ts +0 -4
  16. package/src/domains/paywall/entities/types.ts +0 -5
  17. package/src/domains/paywall/hooks/usePaywallActions.ts +1 -15
  18. package/src/domains/revenuecat/core/errors/RevenueCatError.ts +0 -6
  19. package/src/domains/revenuecat/core/errors/RevenueCatErrorHandler.ts +0 -24
  20. package/src/domains/revenuecat/core/errors/RevenueCatErrorMessages.ts +0 -18
  21. package/src/domains/revenuecat/core/errors/index.ts +0 -4
  22. package/src/domains/revenuecat/core/types/RevenueCatConfig.ts +3 -7
  23. package/src/domains/revenuecat/core/types/RevenueCatData.ts +4 -9
  24. package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +5 -65
  25. package/src/domains/revenuecat/core/types/index.ts +0 -4
  26. package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +1 -24
  27. package/src/domains/subscription/application/SubscriptionAuthListener.ts +5 -21
  28. package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +1 -5
  29. package/src/domains/subscription/application/SubscriptionSyncService.ts +11 -2
  30. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
  31. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +15 -2
  32. package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +9 -2
  33. package/src/domains/subscription/constants/thresholds.ts +0 -9
  34. package/src/domains/subscription/core/SubscriptionConstants.ts +0 -4
  35. package/src/domains/subscription/core/SubscriptionStatus.ts +11 -21
  36. package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +4 -7
  37. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
  38. package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +0 -13
  39. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -18
  40. package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +3 -17
  41. package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +0 -17
  42. package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +0 -19
  43. package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -6
  44. package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +0 -17
  45. package/src/domains/subscription/infrastructure/state/initializationState.ts +0 -25
  46. package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +0 -21
  47. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +2 -7
  48. package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +0 -5
  49. package/src/domains/subscription/infrastructure/utils/renewal/PackageTierComparator.ts +1 -0
  50. package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +0 -18
  51. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.styles.ts +0 -5
  52. package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +0 -5
  53. package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -5
  54. package/src/domains/subscription/presentation/stores/index.ts +0 -4
  55. package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +0 -13
  56. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +30 -21
  57. package/src/domains/subscription/presentation/usePaywallVisibility.ts +0 -9
  58. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +8 -11
  59. package/src/domains/subscription/utils/authGuards.ts +3 -0
  60. package/src/domains/trial/application/TrialService.ts +0 -9
  61. package/src/domains/trial/core/TrialTypes.ts +0 -8
  62. package/src/domains/wallet/domain/mappers/TransactionMapper.ts +0 -5
  63. package/src/domains/wallet/domain/types/transaction.types.ts +0 -7
  64. package/src/domains/wallet/index.ts +0 -7
  65. package/src/domains/wallet/infrastructure/config/walletConfig.ts +0 -11
  66. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +6 -3
  67. package/src/domains/wallet/presentation/hooks/useWallet.ts +0 -7
  68. package/src/domains/wallet/utils/transactionIconMap.ts +0 -10
  69. package/src/global.d.ts +0 -6
  70. package/src/index.ts +1 -4
  71. package/src/init/createSubscriptionInitModule.ts +12 -2
  72. package/src/init/index.ts +1 -5
  73. package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -11
  74. package/src/shared/application/FeedbackService.ts +3 -21
  75. package/src/shared/application/ports/ISubscriptionRepository.ts +0 -4
  76. package/src/shared/infrastructure/SubscriptionEventBus.ts +0 -13
  77. package/src/shared/infrastructure/firestore/collectionUtils.ts +1 -17
  78. package/src/shared/infrastructure/firestore/index.ts +0 -4
  79. package/src/shared/infrastructure/firestore/resultUtils.ts +0 -12
  80. package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +0 -17
  81. package/src/shared/infrastructure/react-query/queryConfig.ts +0 -15
  82. package/src/shared/utils/BaseError.ts +0 -5
  83. package/src/shared/utils/Result.ts +0 -20
  84. package/src/shared/utils/dateConverter.ts +6 -46
  85. package/src/utils/appUtils.ts +0 -16
  86. package/src/utils/creditMapper.ts +0 -7
  87. package/src/utils/dateUtils.compare.ts +0 -24
  88. package/src/utils/dateUtils.core.ts +0 -39
  89. package/src/utils/dateUtils.format.ts +0 -41
  90. package/src/utils/dateUtils.math.ts +0 -41
  91. package/src/utils/dateUtils.ts +0 -5
  92. package/src/utils/packagePeriodUtils.ts +0 -20
  93. package/src/utils/packageTypeDetector.ts +1 -21
  94. package/src/utils/premiumStatusUtils.ts +1 -14
  95. package/src/utils/priceUtils.ts +0 -35
  96. package/src/utils/tierUtils.ts +1 -8
  97. package/src/utils/types.ts +1 -25
  98. package/src/utils/validation.ts +1 -7
  99. package/src/domains/README.md +0 -52
  100. package/src/domains/config/domain/README.md +0 -37
  101. package/src/domains/config/domain/entities/README.md +0 -41
  102. package/src/domains/paywall/README.md +0 -101
  103. package/src/domains/paywall/entities/README.md +0 -40
  104. package/src/domains/paywall/hooks/README.md +0 -41
  105. package/src/domains/subscription/application/syncConstants.ts +0 -1
  106. package/src/domains/subscription/infrastructure/README.md +0 -41
  107. package/src/domains/subscription/infrastructure/config/README.md +0 -49
  108. package/src/domains/subscription/infrastructure/handlers/README.md +0 -41
  109. package/src/domains/subscription/infrastructure/hooks/README.md +0 -50
  110. package/src/domains/subscription/infrastructure/managers/README.md +0 -41
  111. package/src/domains/subscription/infrastructure/services/README.md +0 -42
  112. package/src/domains/subscription/infrastructure/utils/README.md +0 -41
  113. package/src/domains/subscription/presentation/components/README.md +0 -155
  114. package/src/domains/subscription/presentation/components/details/CreditRow.md +0 -92
  115. package/src/domains/subscription/presentation/components/details/DetailRow.md +0 -91
  116. package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.md +0 -93
  117. package/src/domains/subscription/presentation/components/details/PremiumStatusBadge.md +0 -91
  118. package/src/domains/subscription/presentation/components/details/README.md +0 -99
  119. package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.md +0 -90
  120. package/src/domains/subscription/presentation/components/feedback/README.md +0 -99
  121. package/src/domains/subscription/presentation/components/paywall/PaywallModal.md +0 -94
  122. package/src/domains/subscription/presentation/components/paywall/README.md +0 -54
  123. package/src/domains/subscription/presentation/components/sections/README.md +0 -99
  124. package/src/domains/subscription/presentation/components/sections/SubscriptionSection.md +0 -94
  125. package/src/domains/subscription/presentation/utils/README.md +0 -31
  126. package/src/domains/wallet/README.md +0 -51
  127. package/src/domains/wallet/domain/README.md +0 -41
  128. package/src/domains/wallet/infrastructure/README.md +0 -41
  129. package/src/domains/wallet/presentation/components/README.md +0 -41
  130. package/src/domains/wallet/presentation/hooks/README.md +0 -41
  131. package/src/shared/application/ports/README.md +0 -48
@@ -1,7 +1,3 @@
1
- /**
2
- * RevenueCat Domain - Error Handling
3
- */
4
-
5
1
  export * from "./RevenueCatError";
6
2
  export * from "./RevenueCatErrorMessages";
7
3
  export * from "./RevenueCatErrorHandler";
@@ -1,10 +1,6 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
2
  import type { PackageType } from "./RevenueCatTypes";
3
3
 
4
- /**
5
- * RevenueCat Configuration
6
- * All callbacks receive data directly from RevenueCat SDK
7
- */
8
4
  export interface RevenueCatConfig {
9
5
  apiKey?: string;
10
6
  entitlementIdentifier: string;
@@ -15,14 +11,14 @@ export interface RevenueCatConfig {
15
11
  productId?: string,
16
12
  expiresAt?: string,
17
13
  willRenew?: boolean,
18
- periodType?: string // From RevenueCat SDK (NORMAL, INTRO, TRIAL)
14
+ periodType?: string
19
15
  ) => Promise<void> | void;
20
16
  onPurchaseCompleted?: (
21
17
  userId: string,
22
18
  productId: string,
23
19
  customerInfo: CustomerInfo,
24
- source?: string, // Purchase source tracking (app-specific)
25
- packageType?: PackageType | null // From PurchasesPackage.packageType - subscription duration (WEEKLY, MONTHLY, ANNUAL, etc.)
20
+ source?: string,
21
+ packageType?: PackageType | null
26
22
  ) => Promise<void> | void;
27
23
  onRestoreCompleted?: (
28
24
  userId: string,
@@ -1,19 +1,14 @@
1
1
  import type { Store, OwnershipType, PackageType } from "./RevenueCatTypes";
2
2
 
3
- /**
4
- * RevenueCat subscription data (Single Source of Truth)
5
- * Used across the subscription package for storing RevenueCat data in Firestore
6
- * All fields come directly from RevenueCat SDK - no manual definitions
7
- */
8
3
  export interface RevenueCatData {
9
4
  expirationDate: string | null;
10
5
  willRenew: boolean | null;
11
6
  originalTransactionId: string | null;
12
7
  isPremium: boolean;
13
- periodType: string | null; // From RevenueCat SDK (NORMAL, INTRO, TRIAL) - pricing type
14
- packageType: PackageType | null; // From PurchasesPackage.packageType - subscription duration (WEEKLY, MONTHLY, ANNUAL, etc.)
8
+ periodType: string | null;
9
+ packageType: PackageType | null;
15
10
  unsubscribeDetectedAt: string | null;
16
11
  billingIssueDetectedAt: string | null;
17
- store: Store | null; // From PurchasesEntitlementInfo['store']
18
- ownershipType: OwnershipType | null; // From PurchasesEntitlementInfo['ownershipType']
12
+ store: Store | null;
13
+ ownershipType: OwnershipType | null;
19
14
  }
@@ -1,39 +1,9 @@
1
- /**
2
- * RevenueCat Type Definitions
3
- * Proper typing for RevenueCat entitlements and errors
4
- */
5
-
6
1
  import type { CustomerInfo, PurchasesEntitlementInfo, PurchasesPackage } from "react-native-purchases";
7
2
 
8
- /**
9
- * Default entitlement identifier
10
- * Can be overridden in RevenueCatConfig
11
- */
12
- const DEFAULT_ENTITLEMENT_ID = "premium";
13
-
14
- /**
15
- * Store type - Directly from RevenueCat SDK
16
- * Automatically stays in sync with RevenueCat SDK updates
17
- */
18
3
  export type Store = PurchasesEntitlementInfo['store'];
19
-
20
- /**
21
- * OwnershipType - Directly from RevenueCat SDK
22
- * Automatically stays in sync with RevenueCat SDK updates
23
- */
24
4
  export type OwnershipType = PurchasesEntitlementInfo['ownershipType'];
25
-
26
- /**
27
- * PackageType - Directly from RevenueCat SDK
28
- * Represents subscription duration (WEEKLY, MONTHLY, ANNUAL, LIFETIME, etc.)
29
- * Automatically stays in sync with RevenueCat SDK updates
30
- */
31
5
  export type PackageType = PurchasesPackage['packageType'];
32
6
 
33
- /**
34
- * RevenueCat Entitlement Info
35
- * Represents active entitlement data from CustomerInfo
36
- */
37
7
  interface RevenueCatEntitlement {
38
8
  identifier: string;
39
9
  productIdentifier: string;
@@ -45,11 +15,10 @@ interface RevenueCatEntitlement {
45
15
  expirationDate: string | null;
46
16
  unsubscribeDetectedAt: string | null;
47
17
  billingIssueDetectedAt: string | null;
18
+ store: Store | null;
19
+ ownershipType: OwnershipType | null;
48
20
  }
49
21
 
50
- /**
51
- * RevenueCat Purchase Error with userCancelled flag
52
- */
53
22
  interface RevenueCatPurchaseErrorInfo extends Error {
54
23
  userCancelled?: boolean;
55
24
  code?: string;
@@ -57,12 +26,9 @@ interface RevenueCatPurchaseErrorInfo extends Error {
57
26
  underlyingErrorMessage?: string;
58
27
  }
59
28
 
60
- /**
61
- * Extract entitlement from CustomerInfo
62
- */
63
29
  export function getPremiumEntitlement(
64
30
  customerInfo: CustomerInfo,
65
- entitlementIdentifier: string = DEFAULT_ENTITLEMENT_ID
31
+ entitlementIdentifier: string
66
32
  ): RevenueCatEntitlement | null {
67
33
  const entitlement = customerInfo.entitlements.active[entitlementIdentifier];
68
34
  if (!entitlement) {
@@ -80,19 +46,15 @@ export function getPremiumEntitlement(
80
46
  expirationDate: entitlement.expirationDate,
81
47
  unsubscribeDetectedAt: entitlement.unsubscribeDetectedAt,
82
48
  billingIssueDetectedAt: entitlement.billingIssueDetectedAt,
49
+ store: entitlement.store ?? null,
50
+ ownershipType: entitlement.ownershipType ?? null,
83
51
  };
84
52
  }
85
53
 
86
- /**
87
- * Type guard for RevenueCat purchase error
88
- */
89
54
  function isRevenueCatPurchaseError(error: unknown): error is RevenueCatPurchaseErrorInfo {
90
55
  return error instanceof Error && ("userCancelled" in error || "code" in error);
91
56
  }
92
57
 
93
- /**
94
- * Extract error code from RevenueCat error
95
- */
96
58
  export function getErrorCode(error: unknown): string | null {
97
59
  if (!isRevenueCatPurchaseError(error)) {
98
60
  return null;
@@ -100,57 +62,35 @@ export function getErrorCode(error: unknown): string | null {
100
62
  return error.code || error.readableErrorCode || null;
101
63
  }
102
64
 
103
- /**
104
- * Check if error is a user cancellation
105
- * Checks both userCancelled flag and PURCHASE_CANCELLED_ERROR code
106
- */
107
65
  export function isUserCancelledError(error: unknown): boolean {
108
66
  if (!isRevenueCatPurchaseError(error)) {
109
67
  return false;
110
68
  }
111
-
112
- // Check userCancelled flag
113
69
  if (error.userCancelled === true) {
114
70
  return true;
115
71
  }
116
-
117
- // Check error code
118
72
  const code = getErrorCode(error);
119
73
  return code === "PURCHASE_CANCELLED_ERROR" || code === "1";
120
74
  }
121
75
 
122
- /**
123
- * Check if error is a network error
124
- */
125
76
  export function isNetworkError(error: unknown): boolean {
126
77
  const code = getErrorCode(error);
127
78
  return code === "NETWORK_ERROR" || code === "7";
128
79
  }
129
80
 
130
- /**
131
- * Check if error is already purchased
132
- */
133
81
  export function isAlreadyPurchasedError(error: unknown): boolean {
134
82
  const code = getErrorCode(error);
135
83
  return code === "PRODUCT_ALREADY_PURCHASED_ERROR" || code === "6";
136
84
  }
137
85
 
138
- /**
139
- * Check if error is invalid credentials
140
- */
141
86
  export function isInvalidCredentialsError(error: unknown): boolean {
142
87
  const code = getErrorCode(error);
143
88
  return code === "INVALID_CREDENTIALS_ERROR" || code === "9";
144
89
  }
145
90
 
146
- /**
147
- * Extract raw error message from error object
148
- * For user-friendly messages, use getErrorMessageForCode from errors module
149
- */
150
91
  export function getRawErrorMessage(error: unknown, fallback: string): string {
151
92
  if (error instanceof Error) {
152
93
  return error.message;
153
94
  }
154
95
  return fallback;
155
96
  }
156
-
@@ -1,7 +1,3 @@
1
- /**
2
- * RevenueCat Domain - Types
3
- */
4
-
5
1
  export * from "./RevenueCatTypes";
6
2
  export * from "./RevenueCatConfig";
7
3
  export * from "./RevenueCatData";
@@ -1,21 +1,10 @@
1
- /**
2
- * UserSwitchMutex
3
- * Prevents concurrent Purchases.logIn() calls that cause RevenueCat 429 errors
4
- */
5
-
6
1
  class UserSwitchMutexImpl {
7
2
  private activeSwitchPromise: Promise<any> | null = null;
8
3
  private activeUserId: string | null = null;
9
4
  private lastSwitchTime = 0;
10
- private readonly MIN_SWITCH_INTERVAL_MS = 1000; // Minimum 1 second between switches
5
+ private readonly MIN_SWITCH_INTERVAL_MS = 1000;
11
6
 
12
- /**
13
- * Acquires the lock for user switch operation
14
- * Returns existing promise if a switch is in progress for the same user
15
- * Waits if a switch is in progress for a different user
16
- */
17
7
  async acquire(userId: string): Promise<{ shouldProceed: boolean; existingPromise?: Promise<any> }> {
18
- // If a switch is in progress for the exact same user, return the existing promise
19
8
  if (this.activeSwitchPromise && this.activeUserId === userId) {
20
9
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
21
10
  console.log('[UserSwitchMutex] Switch already in progress for this user, returning existing promise');
@@ -23,7 +12,6 @@ class UserSwitchMutexImpl {
23
12
  return { shouldProceed: false, existingPromise: this.activeSwitchPromise };
24
13
  }
25
14
 
26
- // If a switch is in progress for a different user, wait for it to complete
27
15
  if (this.activeSwitchPromise) {
28
16
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
29
17
  console.log('[UserSwitchMutex] Waiting for active switch to complete...');
@@ -31,10 +19,8 @@ class UserSwitchMutexImpl {
31
19
  try {
32
20
  await this.activeSwitchPromise;
33
21
  } catch {
34
- // Ignore error, just wait for completion
35
22
  }
36
23
 
37
- // Add a small delay to avoid rapid-fire requests
38
24
  const timeSinceLastSwitch = Date.now() - this.lastSwitchTime;
39
25
  if (timeSinceLastSwitch < this.MIN_SWITCH_INTERVAL_MS) {
40
26
  const delayNeeded = this.MIN_SWITCH_INTERVAL_MS - timeSinceLastSwitch;
@@ -44,12 +30,10 @@ class UserSwitchMutexImpl {
44
30
  await new Promise<void>(resolve => setTimeout(() => resolve(), delayNeeded));
45
31
  }
46
32
 
47
- // Re-check after delay: another caller may have acquired the lock during our wait
48
33
  if (this.activeSwitchPromise) {
49
34
  if (this.activeUserId === userId) {
50
35
  return { shouldProceed: false, existingPromise: this.activeSwitchPromise };
51
36
  }
52
- // Another switch started for a different user — recurse to wait again
53
37
  return this.acquire(userId);
54
38
  }
55
39
  }
@@ -58,14 +42,10 @@ class UserSwitchMutexImpl {
58
42
  return { shouldProceed: true };
59
43
  }
60
44
 
61
- /**
62
- * Sets the active promise for the current switch operation
63
- */
64
45
  setPromise(promise: Promise<any>): void {
65
46
  this.activeSwitchPromise = promise;
66
47
  this.lastSwitchTime = Date.now();
67
48
 
68
- // Clear the lock when the promise completes (success or failure)
69
49
  promise
70
50
  .finally(() => {
71
51
  if (this.activeSwitchPromise === promise) {
@@ -75,9 +55,6 @@ class UserSwitchMutexImpl {
75
55
  });
76
56
  }
77
57
 
78
- /**
79
- * Resets the mutex state
80
- */
81
58
  reset(): void {
82
59
  this.activeSwitchPromise = null;
83
60
  this.activeUserId = null;
@@ -1,9 +1,5 @@
1
1
  import type { FirebaseAuthLike } from "./SubscriptionInitializerTypes";
2
2
 
3
- /**
4
- * Gets the current user ID from Firebase auth.
5
- * Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
6
- */
7
3
  export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
8
4
  const auth = getAuth();
9
5
  if (!auth) {
@@ -16,19 +12,12 @@ export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string
16
12
  }
17
13
 
18
14
  if (user.isAnonymous) {
19
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
20
- console.log(`[SubscriptionAuthListener] Anonymous user - using Firebase anonymous UID: ${user.uid}`);
21
- }
15
+ return undefined;
22
16
  }
23
17
 
24
18
  return user.uid;
25
19
  };
26
20
 
27
- /**
28
- * Sets up auth state listener that will re-initialize subscription
29
- * when user auth state changes (login/logout).
30
- * Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
31
- */
32
21
  export const setupAuthStateListener = (
33
22
  getAuth: () => FirebaseAuthLike | null,
34
23
  onUserChange: (userId: string | undefined) => void
@@ -39,16 +28,11 @@ export const setupAuthStateListener = (
39
28
  }
40
29
 
41
30
  return auth.onAuthStateChanged((user) => {
42
- const userId = user ? user.uid : undefined;
43
-
44
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
45
- console.log('[SubscriptionAuthListener] 🔔 Auth state changed:', {
46
- hasUser: !!user,
47
- isAnonymous: user?.isAnonymous,
48
- userId: userId || '(undefined - no user)',
49
- });
31
+ if (!user || user.isAnonymous) {
32
+ onUserChange(undefined);
33
+ return;
50
34
  }
51
35
 
52
- onUserChange(userId);
36
+ onUserChange(user.uid);
53
37
  });
54
38
  };
@@ -1,7 +1,3 @@
1
- /**
2
- * Subscription Initializer Types
3
- */
4
-
5
1
  import type { CreditsConfig } from "../../credits/core/Credits";
6
2
  import type { UserCreditsDocumentRead } from "../../credits/core/UserCreditsDocument";
7
3
  import type { PurchaseSource, PurchaseType } from "../core/SubscriptionConstants";
@@ -40,7 +36,7 @@ export interface InitializeCreditsMetadata {
40
36
  willRenew: boolean | null;
41
37
  originalTransactionId: string | null;
42
38
  isPremium: boolean;
43
- periodType: string | null; // Raw value from RevenueCat SDK
39
+ periodType: string | null;
44
40
  unsubscribeDetectedAt: string | null;
45
41
  billingIssueDetectedAt: string | null;
46
42
  store: Store | null;
@@ -15,8 +15,17 @@ export class SubscriptionSyncService {
15
15
  }
16
16
 
17
17
  async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource, packageType?: PackageType | null) {
18
- await this.processor.processPurchase(userId, productId, customerInfo, source, packageType);
19
- subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.PURCHASE_COMPLETED, { userId, productId });
18
+ try {
19
+ await this.processor.processPurchase(userId, productId, customerInfo, source, packageType);
20
+ subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.PURCHASE_COMPLETED, { userId, productId });
21
+ } catch (error) {
22
+ console.error('[SubscriptionSyncService] Purchase processing failed', {
23
+ userId,
24
+ productId,
25
+ error: error instanceof Error ? error.message : String(error)
26
+ });
27
+ throw error;
28
+ }
20
29
  }
21
30
 
22
31
  async handleRenewal(userId: string, productId: string, newExpirationDate: string, customerInfo: CustomerInfo) {
@@ -1,6 +1,6 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
2
  import type { RevenueCatData } from "../../revenuecat/core/types";
3
- import { PERIOD_TYPE, type PeriodType } from "../core/SubscriptionStatus";
3
+ import { PERIOD_TYPE, type PeriodType } from "../core/SubscriptionConstants";
4
4
 
5
5
  function validatePeriodType(periodType: string | undefined): PeriodType | null {
6
6
  if (!periodType) return null;
@@ -20,6 +20,14 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
20
20
  };
21
21
 
22
22
  const attemptInitWithRetry = async (revenueCatUserId?: string, attempt = 0): Promise<void> => {
23
+ // Abort if user changed since retry was scheduled
24
+ if (attempt > 0 && lastUserId !== revenueCatUserId) {
25
+ if (typeof __DEV__ !== 'undefined' && __DEV__) {
26
+ console.log('[BackgroundInitializer] Aborting retry - user changed');
27
+ }
28
+ return;
29
+ }
30
+
23
31
  try {
24
32
  await initializeInBackground(revenueCatUserId);
25
33
  lastUserId = revenueCatUserId;
@@ -49,7 +57,6 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
49
57
  };
50
58
 
51
59
  const debouncedInitialize = (revenueCatUserId?: string): void => {
52
- // Clear any pending initialization or retry
53
60
  if (debounceTimer) {
54
61
  clearTimeout(debounceTimer);
55
62
  }
@@ -58,7 +65,6 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
58
65
  retryTimer = null;
59
66
  }
60
67
 
61
- // If userId hasn't changed AND last init succeeded, skip
62
68
  if (lastUserId === revenueCatUserId && lastInitSucceeded) {
63
69
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
64
70
  console.log('[BackgroundInitializer] UserId unchanged and init succeeded, skipping');
@@ -70,6 +76,13 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
70
76
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
71
77
  console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', revenueCatUserId || '(undefined - anonymous)');
72
78
  }
79
+
80
+ // Reset subscription state on logout to prevent stale cache
81
+ if (!revenueCatUserId && lastUserId) {
82
+ await SubscriptionManager.reset();
83
+ lastInitSucceeded = false;
84
+ }
85
+
73
86
  void attemptInitWithRetry(revenueCatUserId);
74
87
  }, AUTH_STATE_DEBOUNCE_MS);
75
88
  };
@@ -5,6 +5,7 @@ import { SubscriptionSyncService } from "../SubscriptionSyncService";
5
5
  import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { PackageType } from "../../../revenuecat/core/types/RevenueCatTypes";
8
+ import { PURCHASE_SOURCE, PERIOD_TYPE, type PurchaseSource, type PeriodType } from "../../core/SubscriptionConstants";
8
9
 
9
10
  export function configureServices(config: SubscriptionInitConfig, apiKey: string): SubscriptionSyncService {
10
11
  const { entitlementId, credits, creditPackages, getFirebaseAuth, showAuthModal, onCreditsUpdated, getAnonymousUserId } = config;
@@ -31,7 +32,10 @@ export function configureServices(config: SubscriptionInitConfig, apiKey: string
31
32
  c: CustomerInfo,
32
33
  s?: string,
33
34
  pkgType?: PackageType | null
34
- ) => syncService.handlePurchase(u, p, c, s as any, pkgType),
35
+ ) => {
36
+ const validSource = s && Object.values(PURCHASE_SOURCE).includes(s as PurchaseSource) ? s as PurchaseSource : undefined;
37
+ return syncService.handlePurchase(u, p, c, validSource, pkgType);
38
+ },
35
39
  onRenewalDetected: (
36
40
  u: string,
37
41
  p: string,
@@ -45,7 +49,10 @@ export function configureServices(config: SubscriptionInitConfig, apiKey: string
45
49
  exp?: string,
46
50
  willR?: boolean,
47
51
  pt?: string
48
- ) => syncService.handlePremiumStatusChanged(u, isP, pId, exp, willR, pt as any),
52
+ ) => {
53
+ const validPeriodType = pt && Object.values(PERIOD_TYPE).includes(pt as PeriodType) ? pt as PeriodType : undefined;
54
+ return syncService.handlePremiumStatusChanged(u, isP, pId, exp, willR, validPeriodType);
55
+ },
49
56
  onCreditsUpdated,
50
57
  },
51
58
  apiKey,
@@ -1,10 +1 @@
1
- /**
2
- * Subscription Threshold Constants
3
- * Centralized threshold values for subscription UI logic
4
- */
5
-
6
- /**
7
- * Number of days before expiration to show warnings
8
- * Used across subscription UI components for consistency
9
- */
10
1
  export const EXPIRATION_WARNING_THRESHOLD_DAYS = 7;
@@ -6,8 +6,6 @@ export const USER_TIER = {
6
6
 
7
7
  export type UserTierType = (typeof USER_TIER)[keyof typeof USER_TIER];
8
8
 
9
- export const DEFAULT_ENTITLEMENT_ID = 'premium';
10
-
11
9
  export const SUBSCRIPTION_STATUS = {
12
10
  ACTIVE: 'active',
13
11
  TRIAL: 'trial',
@@ -63,5 +61,3 @@ export const PURCHASE_TYPE = {
63
61
  } as const;
64
62
 
65
63
  export type PurchaseType = (typeof PURCHASE_TYPE)[keyof typeof PURCHASE_TYPE];
66
-
67
-
@@ -1,21 +1,15 @@
1
1
  import { timezoneService } from "@umituz/react-native-design-system";
2
- import {
3
- SUBSCRIPTION_STATUS,
4
- PERIOD_TYPE,
5
- type PeriodType,
6
- type SubscriptionStatusType
2
+ import {
3
+ SUBSCRIPTION_STATUS,
4
+ type SubscriptionStatusType
7
5
  } from "./SubscriptionConstants";
8
- import {
9
- InactiveStatusHandler,
10
- TrialStatusHandler,
11
- ActiveStatusHandler
6
+ import {
7
+ InactiveStatusHandler,
8
+ TrialStatusHandler,
9
+ ActiveStatusHandler
12
10
  } from "./SubscriptionStatusHandlers";
13
11
 
14
- export {
15
- PERIOD_TYPE,
16
- type PeriodType,
17
- type SubscriptionStatusType
18
- };
12
+ export type { SubscriptionStatusType };
19
13
 
20
14
  export interface SubscriptionStatus {
21
15
  isPremium: boolean;
@@ -25,7 +19,7 @@ export interface SubscriptionStatus {
25
19
  customerId?: string | null;
26
20
  syncedAt?: string | null;
27
21
  status?: SubscriptionStatusType;
28
- periodType?: string; // Raw value from RevenueCat SDK (NORMAL, INTRO, TRIAL)
22
+ periodType?: string;
29
23
  isTrialing?: boolean;
30
24
  }
31
25
 
@@ -41,7 +35,7 @@ export const createDefaultSubscriptionStatus = (): SubscriptionStatus => ({
41
35
 
42
36
  export const isSubscriptionValid = (status: SubscriptionStatus | null): boolean => {
43
37
  if (!status || !status.isPremium) return false;
44
- if (!status.expiresAt) return true; // Lifetime
38
+ if (!status.expiresAt) return true;
45
39
  return timezoneService.isFuture(new Date(status.expiresAt));
46
40
  };
47
41
 
@@ -49,18 +43,14 @@ export interface StatusResolverInput {
49
43
  isPremium: boolean;
50
44
  willRenew?: boolean;
51
45
  isExpired?: boolean;
52
- periodType?: string; // Raw value from RevenueCat SDK (NORMAL, INTRO, TRIAL)
46
+ periodType?: string;
53
47
  }
54
48
 
55
- // Singleton Chain Instance
56
49
  const inactiveHandler = new InactiveStatusHandler();
57
50
  inactiveHandler
58
51
  .setNext(new TrialStatusHandler())
59
52
  .setNext(new ActiveStatusHandler());
60
53
 
61
- /**
62
- * Resolves subscription status using Chain of Responsibility Pattern.
63
- */
64
54
  export const resolveSubscriptionStatus = (input: StatusResolverInput): SubscriptionStatusType => {
65
55
  return inactiveHandler.handle(input);
66
56
  };
@@ -1,7 +1,7 @@
1
- import {
2
- SUBSCRIPTION_STATUS,
3
- PERIOD_TYPE,
4
- type SubscriptionStatusType
1
+ import {
2
+ SUBSCRIPTION_STATUS,
3
+ PERIOD_TYPE,
4
+ type SubscriptionStatusType
5
5
  } from "./SubscriptionConstants";
6
6
  import type { StatusResolverInput } from "./SubscriptionStatus";
7
7
 
@@ -20,7 +20,6 @@ abstract class BaseStatusHandler {
20
20
  }
21
21
  }
22
22
 
23
- /** Handles Expired or No-Premium cases */
24
23
  export class InactiveStatusHandler extends BaseStatusHandler {
25
24
  handle(input: StatusResolverInput): SubscriptionStatusType {
26
25
  const isExpired = input.isExpired === true;
@@ -32,7 +31,6 @@ export class InactiveStatusHandler extends BaseStatusHandler {
32
31
  }
33
32
  }
34
33
 
35
- /** Handles Trial-related states */
36
34
  export class TrialStatusHandler extends BaseStatusHandler {
37
35
  handle(input: StatusResolverInput): SubscriptionStatusType {
38
36
  if (input.periodType === PERIOD_TYPE.TRIAL) {
@@ -42,7 +40,6 @@ export class TrialStatusHandler extends BaseStatusHandler {
42
40
  }
43
41
  }
44
42
 
45
- /** Handles Canceled-Active states */
46
43
  export class ActiveStatusHandler extends BaseStatusHandler {
47
44
  handle(input: StatusResolverInput): SubscriptionStatusType {
48
45
  if (input.willRenew === false) {
@@ -38,7 +38,7 @@ export class PurchaseStatusResolver {
38
38
  isSandbox: entitlement.isSandbox ?? false,
39
39
  periodType: entitlement.periodType ?? null,
40
40
  packageType: detectedPackageType,
41
- store: null,
41
+ store: entitlement.store ?? null,
42
42
  gracePeriodExpiresDate: null,
43
43
  unsubscribeDetectedAt: toDate(entitlement.unsubscribeDetectedAt),
44
44
  };
@@ -1,19 +1,6 @@
1
- /**
2
- * Subscription Query Keys
3
- * TanStack Query keys and constants for subscription state
4
- */
5
-
6
- /** Query cache time constants */
7
-
8
-
9
- /**
10
- * Query keys for TanStack Query
11
- */
12
1
  export const SUBSCRIPTION_QUERY_KEYS = {
13
2
  packages: ["subscription", "packages"] as const,
14
3
  initialized: (userId: string) =>
15
4
  ["subscription", "initialized", userId] as const,
16
5
  customerInfo: ["subscription", "customerInfo"] as const,
17
6
  } as const;
18
-
19
-