@umituz/react-native-subscription 2.31.5 → 2.31.6

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 (43) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/core/Credits.ts +2 -3
  3. package/src/domains/credits/core/CreditsMapper.ts +2 -3
  4. package/src/domains/credits/core/UserCreditsDocument.ts +2 -4
  5. package/src/domains/credits/infrastructure/CreditsRepository.ts +1 -1
  6. package/src/domains/revenuecat/core/constants/RevenueCatConstants.ts +201 -0
  7. package/src/domains/revenuecat/core/constants/index.ts +5 -0
  8. package/src/domains/{subscription/core → revenuecat/core/errors}/RevenueCatError.ts +1 -1
  9. package/src/domains/revenuecat/core/errors/RevenueCatErrorHandler.ts +91 -0
  10. package/src/domains/revenuecat/core/errors/RevenueCatErrorMessages.ts +140 -0
  11. package/src/domains/revenuecat/core/errors/index.ts +7 -0
  12. package/src/domains/revenuecat/core/index.ts +7 -0
  13. package/src/domains/{subscription/core → revenuecat/core/types}/RevenueCatConfig.ts +6 -3
  14. package/src/domains/{subscription/core → revenuecat/core/types}/RevenueCatData.ts +4 -4
  15. package/src/domains/{subscription/core → revenuecat/core/types}/RevenueCatTypes.ts +11 -6
  16. package/src/domains/revenuecat/core/types/index.ts +7 -0
  17. package/src/domains/revenuecat/index.ts +7 -0
  18. package/src/domains/revenuecat/infrastructure/index.ts +5 -0
  19. package/src/domains/{subscription → revenuecat}/infrastructure/services/RevenueCatInitializer.ts +16 -5
  20. package/src/domains/{subscription → revenuecat}/infrastructure/services/RevenueCatInitializer.types.ts +1 -1
  21. package/src/domains/revenuecat/infrastructure/services/index.ts +7 -0
  22. package/src/domains/{subscription → revenuecat}/infrastructure/utils/ApiKeyResolver.ts +1 -1
  23. package/src/domains/subscription/application/SubscriptionAuthListener.ts +13 -2
  24. package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +2 -3
  25. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
  26. package/src/domains/subscription/application/statusChangeHandlers.ts +1 -1
  27. package/src/domains/subscription/application/syncConstants.ts +1 -1
  28. package/src/domains/subscription/core/SubscriptionStatus.ts +2 -2
  29. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +1 -1
  30. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
  31. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +14 -54
  32. package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +10 -40
  33. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +1 -1
  34. package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +1 -1
  35. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +1 -1
  36. package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +5 -5
  37. package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +5 -5
  38. package/src/domains/subscription/infrastructure/services/RevenueCatService.types.ts +3 -3
  39. package/src/domains/subscription/infrastructure/services/ServiceStateManager.ts +1 -1
  40. package/src/domains/subscription/infrastructure/services/revenueCatServiceInstance.ts +1 -1
  41. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +2 -2
  42. package/src/domains/subscription/core/RevenueCatConstants.ts +0 -73
  43. /package/src/domains/{subscription → revenuecat}/infrastructure/services/initializerConstants.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.31.5",
3
+ "version": "2.31.6",
4
4
  "description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -9,7 +9,6 @@ import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector
9
9
  // Types imported from SubscriptionConstants are used directly in UserCredits interface
10
10
  import type {
11
11
  SubscriptionStatusType,
12
- PeriodType,
13
12
  PackageType,
14
13
  Platform,
15
14
  PurchaseSource,
@@ -36,8 +35,8 @@ export interface UserCredits {
36
35
  packageType: PackageType | null;
37
36
  originalTransactionId: string | null;
38
37
 
39
- // Trial fields
40
- periodType: PeriodType | null;
38
+ // Trial fields - periodType comes from RevenueCat SDK
39
+ periodType: string | null;
41
40
  isTrialing: boolean | null;
42
41
  trialStartDate: Date | null;
43
42
  trialEndDate: Date | null;
@@ -1,6 +1,5 @@
1
1
  import type { UserCredits } from "./Credits";
2
- import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionStatus";
3
- import type { PeriodType, SubscriptionStatusType } from "../../subscription/core/SubscriptionConstants";
2
+ import { resolveSubscriptionStatus, type SubscriptionStatusType } from "../../subscription/core/SubscriptionStatus";
4
3
  import type { UserCreditsDocumentRead } from "./UserCreditsDocument";
5
4
  import { toSafeDate } from "../../../utils/dateUtils";
6
5
 
@@ -10,7 +9,7 @@ import { toSafeDate } from "../../../utils/dateUtils";
10
9
  function validateSubscription(
11
10
  doc: UserCreditsDocumentRead,
12
11
  expirationDate: Date | null,
13
- periodType: PeriodType | null
12
+ periodType: string | null
14
13
  ): { isPremium: boolean; status: SubscriptionStatusType } {
15
14
  const isPremium = doc.isPremium;
16
15
  const willRenew = doc.willRenew ?? false;
@@ -2,17 +2,15 @@ import type {
2
2
  PurchaseSource,
3
3
  PurchaseType,
4
4
  SubscriptionStatusType,
5
- PeriodType,
6
5
  PackageType,
7
6
  Platform
8
7
  } from "../../subscription/core/SubscriptionConstants";
9
- import type { Store, OwnershipType } from "../../subscription/core/RevenueCatTypes";
8
+ import type { Store, OwnershipType } from "../../revenuecat/core/types";
10
9
 
11
10
  export type {
12
11
  PurchaseSource,
13
12
  PurchaseType,
14
13
  SubscriptionStatusType,
15
- PeriodType,
16
14
  Store,
17
15
  OwnershipType
18
16
  };
@@ -55,7 +53,7 @@ export interface UserCreditsDocumentRead {
55
53
  ownershipType: OwnershipType | null;
56
54
 
57
55
  // Trial fields
58
- periodType: PeriodType | null;
56
+ periodType: string | null;
59
57
  isTrialing: boolean | null;
60
58
  trialStartDate: FirestoreTimestamp | null;
61
59
  trialEndDate: FirestoreTimestamp | null;
@@ -4,7 +4,7 @@ import type { CreditsConfig, CreditsResult, DeductCreditsResult } from "../core/
4
4
  import type { UserCreditsDocumentRead, PurchaseSource } from "../core/UserCreditsDocument";
5
5
  import { initializeCreditsTransaction } from "../application/CreditsInitializer";
6
6
  import { mapCreditsDocumentToEntity } from "../core/CreditsMapper";
7
- import type { RevenueCatData } from "../../subscription/core/RevenueCatData";
7
+ import type { RevenueCatData } from "../../revenuecat/core/types";
8
8
  import { deductCreditsOperation } from "../application/DeductCreditsCommand";
9
9
  import { calculateCreditLimit } from "../application/CreditLimitCalculator";
10
10
  import { PURCHASE_TYPE, type PurchaseType } from "../../subscription/core/SubscriptionConstants";
@@ -0,0 +1,201 @@
1
+ /**
2
+ * RevenueCat Constants
3
+ * Error codes, messages, and logging constants
4
+ */
5
+
6
+ import Purchases from "react-native-purchases";
7
+
8
+ export const REVENUECAT_LOG_PREFIX = "[RevenueCat]";
9
+
10
+ /**
11
+ * RevenueCat Error Code Type
12
+ * Re-export for type safety
13
+ */
14
+ export type PurchasesErrorCode = typeof Purchases.PURCHASES_ERROR_CODE[keyof typeof Purchases.PURCHASES_ERROR_CODE];
15
+
16
+ /**
17
+ * Error Message Configuration
18
+ */
19
+ export interface ErrorMessage {
20
+ title: string;
21
+ message: string;
22
+ shouldShowAlert?: boolean;
23
+ }
24
+
25
+ /**
26
+ * Error Code to Enum Mapping
27
+ * Maps both string keys and numeric codes to Purchases error enum values
28
+ */
29
+ const ERROR_CODE_MAP = new Map<string, PurchasesErrorCode>([
30
+ ["PURCHASE_CANCELLED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
31
+ ["PURCHASE_NOT_ALLOWED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
32
+ ["PURCHASE_INVALID_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR],
33
+ ["PRODUCT_ALREADY_PURCHASED_ERROR", Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR],
34
+ ["PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR", Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR],
35
+ ["NETWORK_ERROR", Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR],
36
+ ["UNKNOWN_ERROR", Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR],
37
+ ["RECEIPT_ALREADY_IN_USE_ERROR", Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR],
38
+ ["INVALID_CREDENTIALS_ERROR", Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR],
39
+ ["UNEXPECTED_BACKEND_RESPONSE_ERROR", Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR],
40
+ ["CONFIGURATION_ERROR", Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR],
41
+ ["STORE_PROBLEM_ERROR", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
42
+ ["PAYMENT_PENDING_ERROR", Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR],
43
+ // Numeric codes as fallback
44
+ ["1", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
45
+ ["2", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
46
+ ["3", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
47
+ ["4", Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR],
48
+ ["5", Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR],
49
+ ["6", Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR],
50
+ ["7", Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR],
51
+ ["8", Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR],
52
+ ["9", Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR],
53
+ ["10", Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR],
54
+ ["16", Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR],
55
+ ["20", Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR],
56
+ ["0", Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR],
57
+ ]);
58
+
59
+ /**
60
+ * User-friendly error messages mapped by error code enum
61
+ * Strategy Pattern: Each error code has its own message configuration
62
+ */
63
+ const ERROR_MESSAGES_MAP = new Map<PurchasesErrorCode, ErrorMessage>([
64
+ [
65
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR,
66
+ {
67
+ title: "Purchase Cancelled",
68
+ message: "The purchase was cancelled.",
69
+ shouldShowAlert: false, // Don't show alert for user cancellation
70
+ },
71
+ ],
72
+ [
73
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR,
74
+ {
75
+ title: "Purchase Not Allowed",
76
+ message: "In-app purchases are disabled on this device.",
77
+ shouldShowAlert: true,
78
+ },
79
+ ],
80
+ [
81
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR,
82
+ {
83
+ title: "Invalid Purchase",
84
+ message: "The purchase is invalid. Please contact support.",
85
+ shouldShowAlert: true,
86
+ },
87
+ ],
88
+ [
89
+ Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR,
90
+ {
91
+ title: "Already Purchased",
92
+ message: "You already own this subscription. Restoring your purchase...",
93
+ shouldShowAlert: true,
94
+ },
95
+ ],
96
+ [
97
+ Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR,
98
+ {
99
+ title: "Product Unavailable",
100
+ message: "This product is not available for purchase at this time.",
101
+ shouldShowAlert: true,
102
+ },
103
+ ],
104
+ [
105
+ Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR,
106
+ {
107
+ title: "Network Error",
108
+ message: "Please check your internet connection and try again.",
109
+ shouldShowAlert: true,
110
+ },
111
+ ],
112
+ [
113
+ Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR,
114
+ {
115
+ title: "Unknown Error",
116
+ message: "An unexpected error occurred. Please try again.",
117
+ shouldShowAlert: true,
118
+ },
119
+ ],
120
+ [
121
+ Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR,
122
+ {
123
+ title: "Receipt Already Used",
124
+ message: "This receipt is already associated with another account.",
125
+ shouldShowAlert: true,
126
+ },
127
+ ],
128
+ [
129
+ Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR,
130
+ {
131
+ title: "Configuration Error",
132
+ message: "The app is not configured correctly. Please contact support.",
133
+ shouldShowAlert: true,
134
+ },
135
+ ],
136
+ [
137
+ Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR,
138
+ {
139
+ title: "Server Error",
140
+ message: "The server returned an unexpected response. Please try again later.",
141
+ shouldShowAlert: true,
142
+ },
143
+ ],
144
+ [
145
+ Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR,
146
+ {
147
+ title: "Configuration Error",
148
+ message: "RevenueCat is not configured correctly. Please contact support.",
149
+ shouldShowAlert: true,
150
+ },
151
+ ],
152
+ [
153
+ Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR,
154
+ {
155
+ title: "Store Error",
156
+ message: "There was a problem with the app store. Please try again.",
157
+ shouldShowAlert: true,
158
+ },
159
+ ],
160
+ [
161
+ Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR,
162
+ {
163
+ title: "Payment Pending",
164
+ message: "Your payment is still being processed. Please check back later.",
165
+ shouldShowAlert: true,
166
+ },
167
+ ],
168
+ ]);
169
+
170
+ /**
171
+ * Default error message for unknown errors
172
+ */
173
+ const DEFAULT_ERROR_MESSAGE: ErrorMessage = {
174
+ title: "Error",
175
+ message: "An error occurred. Please try again.",
176
+ shouldShowAlert: true,
177
+ };
178
+
179
+ /**
180
+ * Get error message configuration for a given error code
181
+ * Uses Strategy Pattern with Map lookup - O(1) complexity
182
+ *
183
+ * @param errorCode - Error code string from RevenueCat error
184
+ * @returns ErrorMessage configuration
185
+ */
186
+ export function getErrorMessageForCode(errorCode: string | null | undefined): ErrorMessage {
187
+ if (!errorCode) {
188
+ return DEFAULT_ERROR_MESSAGE;
189
+ }
190
+
191
+ // Try to map string code to enum value
192
+ const enumCode = ERROR_CODE_MAP.get(errorCode);
193
+ if (enumCode) {
194
+ const message = ERROR_MESSAGES_MAP.get(enumCode);
195
+ if (message) {
196
+ return message;
197
+ }
198
+ }
199
+
200
+ return DEFAULT_ERROR_MESSAGE;
201
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * RevenueCat Domain - Constants
3
+ */
4
+
5
+ export { REVENUECAT_LOG_PREFIX } from "./RevenueCatConstants";
@@ -3,7 +3,7 @@
3
3
  * Domain-specific error types for RevenueCat operations
4
4
  */
5
5
 
6
- import { BaseError } from "../../../shared/utils/BaseError";
6
+ import { BaseError } from "../../../../shared/utils/BaseError";
7
7
 
8
8
  export class RevenueCatError extends BaseError {
9
9
  constructor(message: string, code: string = 'REVENUE_CAT_ERROR', cause?: Error) {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * RevenueCat Error Handler
3
+ * Error code mapping and message resolution utilities
4
+ */
5
+
6
+ import Purchases from "react-native-purchases";
7
+ import {
8
+ ERROR_MESSAGES_MAP,
9
+ DEFAULT_ERROR_MESSAGE,
10
+ type ErrorMessage,
11
+ type PurchasesErrorCode,
12
+ } from "./RevenueCatErrorMessages";
13
+
14
+ /**
15
+ * Error Code to Enum Mapping
16
+ * Maps both string keys and numeric codes to Purchases error enum values
17
+ */
18
+ const ERROR_CODE_MAP = new Map<string, PurchasesErrorCode>([
19
+ // String error codes
20
+ ["PURCHASE_CANCELLED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
21
+ ["PURCHASE_NOT_ALLOWED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
22
+ ["PURCHASE_INVALID_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR],
23
+ ["PRODUCT_ALREADY_PURCHASED_ERROR", Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR],
24
+ ["PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR", Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR],
25
+ ["NETWORK_ERROR", Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR],
26
+ ["UNKNOWN_ERROR", Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR],
27
+ ["RECEIPT_ALREADY_IN_USE_ERROR", Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR],
28
+ ["INVALID_CREDENTIALS_ERROR", Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR],
29
+ ["UNEXPECTED_BACKEND_RESPONSE_ERROR", Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR],
30
+ ["CONFIGURATION_ERROR", Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR],
31
+ ["STORE_PROBLEM_ERROR", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
32
+ ["PAYMENT_PENDING_ERROR", Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR],
33
+
34
+ // Numeric error codes as fallback
35
+ ["1", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
36
+ ["2", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
37
+ ["3", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
38
+ ["4", Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR],
39
+ ["5", Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR],
40
+ ["6", Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR],
41
+ ["7", Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR],
42
+ ["8", Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR],
43
+ ["9", Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR],
44
+ ["10", Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR],
45
+ ["16", Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR],
46
+ ["20", Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR],
47
+ ["0", Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR],
48
+ ]);
49
+
50
+ /**
51
+ * Get error message configuration for a given error code
52
+ * Strategy Pattern with Map lookup - O(1) complexity
53
+ *
54
+ * @param errorCode - Error code string from RevenueCat error
55
+ * @returns ErrorMessage configuration
56
+ */
57
+ export function getErrorMessageForCode(errorCode: string | null | undefined): ErrorMessage {
58
+ if (!errorCode) {
59
+ return DEFAULT_ERROR_MESSAGE;
60
+ }
61
+
62
+ const enumCode = ERROR_CODE_MAP.get(errorCode);
63
+ if (enumCode) {
64
+ const message = ERROR_MESSAGES_MAP.get(enumCode);
65
+ if (message) {
66
+ return message;
67
+ }
68
+ }
69
+
70
+ return DEFAULT_ERROR_MESSAGE;
71
+ }
72
+
73
+ /**
74
+ * Get error message from RevenueCat error object
75
+ *
76
+ * @param error - RevenueCat error object
77
+ * @returns ErrorMessage configuration
78
+ */
79
+ export function getErrorMessage(error: unknown): ErrorMessage {
80
+ if (!error || typeof error !== "object") {
81
+ return DEFAULT_ERROR_MESSAGE;
82
+ }
83
+
84
+ const errorCode = "code" in error
85
+ ? String(error.code)
86
+ : "readableErrorCode" in error
87
+ ? String(error.readableErrorCode)
88
+ : null;
89
+
90
+ return getErrorMessageForCode(errorCode);
91
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * RevenueCat Error Messages
3
+ * User-friendly error message configurations
4
+ */
5
+
6
+ import Purchases from "react-native-purchases";
7
+
8
+ /**
9
+ * Error Message Configuration
10
+ */
11
+ export interface ErrorMessage {
12
+ title: string;
13
+ message: string;
14
+ shouldShowAlert?: boolean;
15
+ }
16
+
17
+ /**
18
+ * RevenueCat Error Code Type
19
+ */
20
+ export type PurchasesErrorCode = typeof Purchases.PURCHASES_ERROR_CODE[keyof typeof Purchases.PURCHASES_ERROR_CODE];
21
+
22
+ /**
23
+ * User-friendly error messages mapped by error code enum
24
+ * Strategy Pattern: Each error code has its own message configuration
25
+ */
26
+ export const ERROR_MESSAGES_MAP = new Map<PurchasesErrorCode, ErrorMessage>([
27
+ [
28
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR,
29
+ {
30
+ title: "Purchase Cancelled",
31
+ message: "The purchase was cancelled.",
32
+ shouldShowAlert: false,
33
+ },
34
+ ],
35
+ [
36
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR,
37
+ {
38
+ title: "Purchase Not Allowed",
39
+ message: "In-app purchases are disabled on this device.",
40
+ shouldShowAlert: true,
41
+ },
42
+ ],
43
+ [
44
+ Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR,
45
+ {
46
+ title: "Invalid Purchase",
47
+ message: "The purchase is invalid. Please contact support.",
48
+ shouldShowAlert: true,
49
+ },
50
+ ],
51
+ [
52
+ Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR,
53
+ {
54
+ title: "Already Purchased",
55
+ message: "You already own this subscription. Restoring your purchase...",
56
+ shouldShowAlert: true,
57
+ },
58
+ ],
59
+ [
60
+ Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR,
61
+ {
62
+ title: "Product Unavailable",
63
+ message: "This product is not available for purchase at this time.",
64
+ shouldShowAlert: true,
65
+ },
66
+ ],
67
+ [
68
+ Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR,
69
+ {
70
+ title: "Network Error",
71
+ message: "Please check your internet connection and try again.",
72
+ shouldShowAlert: true,
73
+ },
74
+ ],
75
+ [
76
+ Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR,
77
+ {
78
+ title: "Unknown Error",
79
+ message: "An unexpected error occurred. Please try again.",
80
+ shouldShowAlert: true,
81
+ },
82
+ ],
83
+ [
84
+ Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR,
85
+ {
86
+ title: "Receipt Already Used",
87
+ message: "This receipt is already associated with another account.",
88
+ shouldShowAlert: true,
89
+ },
90
+ ],
91
+ [
92
+ Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR,
93
+ {
94
+ title: "Configuration Error",
95
+ message: "The app is not configured correctly. Please contact support.",
96
+ shouldShowAlert: true,
97
+ },
98
+ ],
99
+ [
100
+ Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR,
101
+ {
102
+ title: "Server Error",
103
+ message: "The server returned an unexpected response. Please try again later.",
104
+ shouldShowAlert: true,
105
+ },
106
+ ],
107
+ [
108
+ Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR,
109
+ {
110
+ title: "Configuration Error",
111
+ message: "RevenueCat is not configured correctly. Please contact support.",
112
+ shouldShowAlert: true,
113
+ },
114
+ ],
115
+ [
116
+ Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR,
117
+ {
118
+ title: "Store Error",
119
+ message: "There was a problem with the app store. Please try again.",
120
+ shouldShowAlert: true,
121
+ },
122
+ ],
123
+ [
124
+ Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR,
125
+ {
126
+ title: "Payment Pending",
127
+ message: "Your payment is still being processed. Please check back later.",
128
+ shouldShowAlert: true,
129
+ },
130
+ ],
131
+ ]);
132
+
133
+ /**
134
+ * Default error message for unknown errors
135
+ */
136
+ export const DEFAULT_ERROR_MESSAGE: ErrorMessage = {
137
+ title: "Error",
138
+ message: "An error occurred. Please try again.",
139
+ shouldShowAlert: true,
140
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RevenueCat Domain - Error Handling
3
+ */
4
+
5
+ export * from "./RevenueCatError";
6
+ export * from "./RevenueCatErrorMessages";
7
+ export * from "./RevenueCatErrorHandler";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RevenueCat Domain - Core Layer
3
+ */
4
+
5
+ export * from "./errors";
6
+ export * from "./types";
7
+ export * from "./constants";
@@ -1,6 +1,9 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
- import type { PurchaseSource } from "./SubscriptionConstants";
3
2
 
3
+ /**
4
+ * RevenueCat Configuration
5
+ * All callbacks receive data directly from RevenueCat SDK
6
+ */
4
7
  export interface RevenueCatConfig {
5
8
  apiKey?: string;
6
9
  entitlementIdentifier: string;
@@ -11,13 +14,13 @@ export interface RevenueCatConfig {
11
14
  productId?: string,
12
15
  expiresAt?: string,
13
16
  willRenew?: boolean,
14
- periodType?: "NORMAL" | "INTRO" | "TRIAL"
17
+ periodType?: string // From RevenueCat SDK (NORMAL, INTRO, TRIAL)
15
18
  ) => Promise<void> | void;
16
19
  onPurchaseCompleted?: (
17
20
  userId: string,
18
21
  productId: string,
19
22
  customerInfo: CustomerInfo,
20
- source?: PurchaseSource
23
+ source?: string // Purchase source tracking (app-specific)
21
24
  ) => Promise<void> | void;
22
25
  onRestoreCompleted?: (
23
26
  userId: string,
@@ -1,18 +1,18 @@
1
- import type { PeriodType } from "./SubscriptionConstants";
2
1
  import type { Store, OwnershipType } from "./RevenueCatTypes";
3
2
 
4
3
  /**
5
4
  * RevenueCat subscription data (Single Source of Truth)
6
5
  * Used across the subscription package for storing RevenueCat data in Firestore
6
+ * All fields come directly from RevenueCat SDK - no manual definitions
7
7
  */
8
8
  export interface RevenueCatData {
9
9
  expirationDate: string | null;
10
10
  willRenew: boolean | null;
11
11
  originalTransactionId: string | null;
12
12
  isPremium: boolean;
13
- periodType: PeriodType | null;
13
+ periodType: string | null; // From RevenueCat SDK (NORMAL, INTRO, TRIAL)
14
14
  unsubscribeDetectedAt: string | null;
15
15
  billingIssueDetectedAt: string | null;
16
- store: Store | null;
17
- ownershipType: OwnershipType | null;
16
+ store: Store | null; // From PurchasesEntitlementInfo['store']
17
+ ownershipType: OwnershipType | null; // From PurchasesEntitlementInfo['ownershipType']
18
18
  }
@@ -4,16 +4,21 @@
4
4
  */
5
5
 
6
6
  import type { CustomerInfo, PurchasesEntitlementInfo } from "react-native-purchases";
7
- import { DEFAULT_ENTITLEMENT_ID } from "./SubscriptionConstants";
8
7
 
9
8
  /**
10
- * Store type extracted from RevenueCat's PurchasesEntitlementInfo
9
+ * Default entitlement identifier
10
+ * Can be overridden in RevenueCatConfig
11
+ */
12
+ export const DEFAULT_ENTITLEMENT_ID = "premium";
13
+
14
+ /**
15
+ * Store type - Directly from RevenueCat SDK
11
16
  * Automatically stays in sync with RevenueCat SDK updates
12
17
  */
13
18
  export type Store = PurchasesEntitlementInfo['store'];
14
19
 
15
20
  /**
16
- * OwnershipType extracted from RevenueCat's PurchasesEntitlementInfo
21
+ * OwnershipType - Directly from RevenueCat SDK
17
22
  * Automatically stays in sync with RevenueCat SDK updates
18
23
  */
19
24
  export type OwnershipType = PurchasesEntitlementInfo['ownershipType'];
@@ -132,10 +137,10 @@ export function isInvalidCredentialsError(error: unknown): boolean {
132
137
  }
133
138
 
134
139
  /**
135
- * Extract error message safely
136
- * Returns user-friendly message if available, otherwise raw error message
140
+ * Extract raw error message from error object
141
+ * For user-friendly messages, use getErrorMessageForCode from errors module
137
142
  */
138
- export function getErrorMessage(error: unknown, fallback: string): string {
143
+ export function getRawErrorMessage(error: unknown, fallback: string): string {
139
144
  if (error instanceof Error) {
140
145
  return error.message;
141
146
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RevenueCat Domain - Types
3
+ */
4
+
5
+ export * from "./RevenueCatTypes";
6
+ export * from "./RevenueCatConfig";
7
+ export * from "./RevenueCatData";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RevenueCat Domain
3
+ * Bounded Context for RevenueCat SDK Integration
4
+ */
5
+
6
+ export * from "./core";
7
+ export * from "./infrastructure";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * RevenueCat Domain - Infrastructure Layer
3
+ */
4
+
5
+ export * from "./services";
@@ -94,14 +94,23 @@ export async function initializeSDK(
94
94
  try {
95
95
  const currentAppUserId = await Purchases.getAppUserID();
96
96
  let customerInfo;
97
+
98
+ // Handle user switching
97
99
  if (currentAppUserId !== userId) {
98
- const result = await Purchases.logIn(userId);
99
- customerInfo = result.customerInfo;
100
+ if (userId) {
101
+ // Switch to authenticated user
102
+ const result = await Purchases.logIn(userId);
103
+ customerInfo = result.customerInfo;
104
+ } else {
105
+ // User logged out - switch to anonymous
106
+ customerInfo = await Purchases.logOut();
107
+ }
100
108
  } else {
101
109
  customerInfo = await Purchases.getCustomerInfo();
102
110
  }
111
+
103
112
  deps.setInitialized(true);
104
- deps.setCurrentUserId(userId);
113
+ deps.setCurrentUserId(userId ?? null);
105
114
  const offerings = await Purchases.getOfferings();
106
115
  return buildSuccessResult(deps, customerInfo, offerings);
107
116
  } catch {
@@ -133,9 +142,11 @@ export async function initializeSDK(
133
142
  }
134
143
 
135
144
  try {
136
- await Purchases.configure({ apiKey: key, appUserID: userId });
145
+ // Configure with null appUserID for anonymous users (generates RevenueCat anonymous ID)
146
+ // For authenticated users, use their userId
147
+ await Purchases.configure({ apiKey: key, appUserID: userId ?? null });
137
148
  deps.setInitialized(true);
138
- deps.setCurrentUserId(userId);
149
+ deps.setCurrentUserId(userId ?? null);
139
150
 
140
151
  const [customerInfo, offerings] = await Promise.all([
141
152
  Purchases.getCustomerInfo(),
@@ -1,4 +1,4 @@
1
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
1
+ import type { RevenueCatConfig } from "../../core/types";
2
2
 
3
3
  export interface InitializerDeps {
4
4
  config: RevenueCatConfig;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * RevenueCat Domain - Infrastructure Services
3
+ * Pure RevenueCat SDK Integration
4
+ */
5
+
6
+ export * from "./RevenueCatInitializer";
7
+ export * from "./RevenueCatInitializer.types";
@@ -1,4 +1,4 @@
1
- import type { RevenueCatConfig } from '../../core/RevenueCatConfig';
1
+ import type { RevenueCatConfig } from '../../core/types';
2
2
 
3
3
  export function resolveApiKey(config: RevenueCatConfig): string | null {
4
4
  return config.apiKey || null;
@@ -2,16 +2,25 @@ import type { FirebaseAuthLike } from "./SubscriptionInitializerTypes";
2
2
 
3
3
  /**
4
4
  * Gets the current user ID from Firebase auth.
5
+ * Returns undefined for anonymous users to prevent RevenueCat from using anonymous Firebase UIDs.
5
6
  */
6
7
  export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
7
8
  const auth = getAuth();
8
9
  if (!auth) return undefined;
9
- return auth.currentUser?.uid;
10
+
11
+ const user = auth.currentUser;
12
+ if (!user) return undefined;
13
+
14
+ // Don't return userId for anonymous users - let RevenueCat use its own anonymous ID
15
+ if (user.isAnonymous) return undefined;
16
+
17
+ return user.uid;
10
18
  };
11
19
 
12
20
  /**
13
21
  * Sets up auth state listener that will re-initialize subscription
14
22
  * when user auth state changes (login/logout).
23
+ * Returns undefined for anonymous users to prevent RevenueCat from using anonymous Firebase UIDs.
15
24
  */
16
25
  export const setupAuthStateListener = (
17
26
  getAuth: () => FirebaseAuthLike | null,
@@ -21,6 +30,8 @@ export const setupAuthStateListener = (
21
30
  if (!auth) return null;
22
31
 
23
32
  return auth.onAuthStateChanged((user) => {
24
- onUserChange(user?.uid);
33
+ // Don't pass userId for anonymous users - let RevenueCat use its own anonymous ID
34
+ const userId = (user && !user.isAnonymous) ? user.uid : undefined;
35
+ onUserChange(userId);
25
36
  });
26
37
  };
@@ -5,8 +5,7 @@
5
5
  import type { CreditsConfig } from "../../credits/core/Credits";
6
6
  import type { UserCreditsDocumentRead } from "../../credits/core/UserCreditsDocument";
7
7
  import type { PurchaseSource, PurchaseType } from "../core/SubscriptionConstants";
8
- import type { PeriodType } from "../core/SubscriptionStatus";
9
- import type { Store, OwnershipType } from "../core/RevenueCatTypes";
8
+ import type { Store, OwnershipType } from "../../revenuecat/core/types";
10
9
 
11
10
  export interface FirebaseAuthLike {
12
11
  currentUser: { uid: string; isAnonymous: boolean } | null;
@@ -41,7 +40,7 @@ export interface InitializeCreditsMetadata {
41
40
  willRenew: boolean | null;
42
41
  originalTransactionId: string | null;
43
42
  isPremium: boolean;
44
- periodType: PeriodType | null;
43
+ periodType: string | null; // Raw value from RevenueCat SDK
45
44
  unsubscribeDetectedAt: string | null;
46
45
  billingIssueDetectedAt: string | null;
47
46
  store: Store | null;
@@ -1,5 +1,5 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
- import type { RevenueCatData } from "../core/RevenueCatData";
2
+ import type { RevenueCatData } from "../../revenuecat/core/types";
3
3
  import { PERIOD_TYPE, type PeriodType } from "../core/SubscriptionStatus";
4
4
 
5
5
  function validatePeriodType(periodType: string | undefined): PeriodType | null {
@@ -1,4 +1,4 @@
1
- import type { RevenueCatData } from "../core/RevenueCatData";
1
+ import type { RevenueCatData } from "../../revenuecat/core/types";
2
2
  import type { PeriodType } from "../core/SubscriptionConstants";
3
3
  import { PURCHASE_SOURCE, PURCHASE_TYPE } from "../core/SubscriptionConstants";
4
4
  import { getCreditsRepository } from "../../credits/infrastructure/CreditsRepositoryManager";
@@ -1,4 +1,4 @@
1
- import type { RevenueCatData } from "../core/RevenueCatData";
1
+ import type { RevenueCatData } from "../../revenuecat/core/types";
2
2
 
3
3
  export const NO_SUBSCRIPTION_PRODUCT_ID = 'no_subscription';
4
4
 
@@ -26,7 +26,7 @@ export interface SubscriptionStatus {
26
26
  customerId?: string | null;
27
27
  syncedAt?: string | null;
28
28
  status?: SubscriptionStatusType;
29
- periodType?: PeriodType;
29
+ periodType?: string; // Raw value from RevenueCat SDK (NORMAL, INTRO, TRIAL)
30
30
  isTrialing?: boolean;
31
31
  }
32
32
 
@@ -55,7 +55,7 @@ export interface StatusResolverInput {
55
55
  isPremium: boolean;
56
56
  willRenew?: boolean;
57
57
  isExpired?: boolean;
58
- periodType?: PeriodType;
58
+ periodType?: string; // Raw value from RevenueCat SDK (NORMAL, INTRO, TRIAL)
59
59
  }
60
60
 
61
61
  // Singleton Chain Instance
@@ -5,7 +5,7 @@
5
5
 
6
6
  import type { PurchasesPackage, CustomerInfo } from "react-native-purchases";
7
7
  import type { IRevenueCatService } from "../../../../shared/application/ports/IRevenueCatService";
8
- import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
8
+ import { getPremiumEntitlement } from "../../../revenuecat/core/types";
9
9
  import { PurchaseStatusResolver, type PremiumStatus } from "./PurchaseStatusResolver";
10
10
 
11
11
  export interface RestoreResultInfo {
@@ -1,5 +1,5 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
- import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
2
+ import { getPremiumEntitlement } from "../../../revenuecat/core/types";
3
3
  import { toDate } from "../../../../shared/utils/dateConverter";
4
4
 
5
5
  export interface PremiumStatus {
@@ -6,18 +6,18 @@
6
6
  */
7
7
 
8
8
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
9
- import Purchases, { type PurchasesPackage } from "react-native-purchases";
9
+ import type { PurchasesPackage } from "react-native-purchases";
10
10
  import { useAlert } from "@umituz/react-native-design-system";
11
11
  import {
12
12
  useAuthStore,
13
13
  selectUserId,
14
+ selectIsAnonymous,
14
15
  } from "@umituz/react-native-auth";
15
16
  import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
16
17
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
17
18
  import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionStatus";
18
19
  import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
19
- import { ERROR_MESSAGES } from "../../core/RevenueCatConstants";
20
- import type { RevenueCatPurchaseErrorInfo } from "../../core/RevenueCatTypes";
20
+ import { getErrorMessage } from "../../../revenuecat/core/errors";
21
21
 
22
22
  /** Purchase mutation result - simplified for presentation layer */
23
23
  export interface PurchaseMutationResult {
@@ -33,6 +33,7 @@ export interface PurchaseMutationResult {
33
33
  */
34
34
  export const usePurchasePackage = () => {
35
35
  const userId = useAuthStore(selectUserId);
36
+ const isAnonymous = useAuthStore(selectIsAnonymous);
36
37
  const queryClient = useQueryClient();
37
38
  const { showSuccess, showError } = useAlert();
38
39
 
@@ -42,6 +43,10 @@ export const usePurchasePackage = () => {
42
43
  throw new Error("User not authenticated");
43
44
  }
44
45
 
46
+ if (isAnonymous) {
47
+ throw new Error("Anonymous users cannot purchase subscriptions");
48
+ }
49
+
45
50
  const productId = pkg.product.identifier;
46
51
  const success = await SubscriptionManager.purchasePackage(pkg);
47
52
 
@@ -66,60 +71,15 @@ export const usePurchasePackage = () => {
66
71
  }
67
72
  },
68
73
  onError: (error) => {
69
- let title = "Purchase Error";
70
- let message = "Unable to complete purchase. Please try again.";
71
-
72
- if (error instanceof Error) {
73
- // Type assertion for RevenueCat error
74
- const rcError = error as RevenueCatPurchaseErrorInfo;
75
- const errorCode = rcError.code || rcError.readableErrorCode;
74
+ // Use map-based lookup - O(1) complexity
75
+ const errorInfo = getErrorMessage(error);
76
76
 
77
- // Get user-friendly message from constants if available
78
- if (errorCode && errorCode in ERROR_MESSAGES) {
79
- const errorInfo = ERROR_MESSAGES[errorCode];
80
- title = errorInfo.title;
81
- message = errorInfo.message;
82
- } else {
83
- // Fallback to specific error code checks using Purchases enum
84
- const code = errorCode;
85
-
86
- if (code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
87
- return; // Don't show error for user cancellation
88
- } else if (code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR) {
89
- title = "Purchase Not Allowed";
90
- message = "In-app purchases are disabled on this device.";
91
- } else if (code === Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR) {
92
- title = "Invalid Purchase";
93
- message = "The purchase is invalid. Please contact support.";
94
- } else if (code === Purchases.PURCHASES_ERROR_CODE.PRODUCT_ALREADY_PURCHASED_ERROR) {
95
- title = "Already Purchased";
96
- message = "You already own this subscription. Restoring...";
97
- } else if (code === Purchases.PURCHASES_ERROR_CODE.PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR) {
98
- title = "Product Unavailable";
99
- message = "This product is not available for purchase.";
100
- } else if (code === Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR) {
101
- title = "Network Error";
102
- message = "Please check your internet connection and try again.";
103
- } else if (code === Purchases.PURCHASES_ERROR_CODE.RECEIPT_ALREADY_IN_USE_ERROR) {
104
- title = "Receipt Already Used";
105
- message = "This receipt is already associated with another account.";
106
- } else if (code === Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR) {
107
- title = "Configuration Error";
108
- message = "App is not configured correctly. Please contact support.";
109
- } else if (code === Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR) {
110
- title = "Server Error";
111
- message = "The server returned an unexpected response. Please try again later.";
112
- } else if (code === Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR) {
113
- title = "Configuration Error";
114
- message = "RevenueCat is not configured correctly. Please contact support.";
115
- } else {
116
- // Use error message if no specific code matched
117
- message = error.message || message;
118
- }
119
- }
77
+ // Don't show alert for user cancellation
78
+ if (!errorInfo.shouldShowAlert) {
79
+ return;
120
80
  }
121
81
 
122
- showError(title, message);
82
+ showError(errorInfo.title, errorInfo.message);
123
83
  },
124
84
  });
125
85
  };
@@ -6,18 +6,17 @@
6
6
  */
7
7
 
8
8
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
9
- import Purchases from "react-native-purchases";
10
9
  import { useAlert } from "@umituz/react-native-design-system";
11
10
  import {
12
11
  useAuthStore,
13
12
  selectUserId,
13
+ selectIsAnonymous,
14
14
  } from "@umituz/react-native-auth";
15
15
  import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
16
16
  import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
17
17
  import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionStatus";
18
18
  import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
19
- import { ERROR_MESSAGES } from "../../core/RevenueCatConstants";
20
- import type { RevenueCatPurchaseErrorInfo } from "../../core/RevenueCatTypes";
19
+ import { getErrorMessage } from "../../../revenuecat/core/errors";
21
20
 
22
21
  interface RestoreResult {
23
22
  success: boolean;
@@ -31,6 +30,7 @@ interface RestoreResult {
31
30
  */
32
31
  export const useRestorePurchase = () => {
33
32
  const userId = useAuthStore(selectUserId);
33
+ const isAnonymous = useAuthStore(selectIsAnonymous);
34
34
  const queryClient = useQueryClient();
35
35
  const { showSuccess, showInfo, showError } = useAlert();
36
36
 
@@ -40,6 +40,10 @@ export const useRestorePurchase = () => {
40
40
  throw new Error("User not authenticated");
41
41
  }
42
42
 
43
+ if (isAnonymous) {
44
+ throw new Error("Anonymous users cannot restore purchases");
45
+ }
46
+
43
47
  const result = await SubscriptionManager.restore();
44
48
  return result;
45
49
  },
@@ -67,43 +71,9 @@ export const useRestorePurchase = () => {
67
71
  }
68
72
  },
69
73
  onError: (error) => {
70
- let title = "Restore Error";
71
- let message = "Unable to restore purchases. Please try again.";
72
-
73
- if (error instanceof Error) {
74
- // Type assertion for RevenueCat error
75
- const rcError = error as RevenueCatPurchaseErrorInfo;
76
- const errorCode = rcError.code || rcError.readableErrorCode;
77
-
78
- // Get user-friendly message from constants if available
79
- if (errorCode && errorCode in ERROR_MESSAGES) {
80
- const errorInfo = ERROR_MESSAGES[errorCode];
81
- title = errorInfo.title;
82
- message = errorInfo.message;
83
- } else {
84
- // Fallback to specific error code checks
85
- const code = errorCode;
86
-
87
- if (code === Purchases.PURCHASES_ERROR_CODE.NETWORK_ERROR) {
88
- title = "Network Error";
89
- message = "Please check your internet connection and try again.";
90
- } else if (code === Purchases.PURCHASES_ERROR_CODE.INVALID_CREDENTIALS_ERROR) {
91
- title = "Configuration Error";
92
- message = "App is not configured correctly. Please contact support.";
93
- } else if (code === Purchases.PURCHASES_ERROR_CODE.UNEXPECTED_BACKEND_RESPONSE_ERROR) {
94
- title = "Server Error";
95
- message = "The server returned an unexpected response. Please try again later.";
96
- } else if (code === Purchases.PURCHASES_ERROR_CODE.CONFIGURATION_ERROR) {
97
- title = "Configuration Error";
98
- message = "RevenueCat is not configured correctly. Please contact support.";
99
- } else {
100
- // Use error message if no specific code matched
101
- message = error.message || message;
102
- }
103
- }
104
- }
105
-
106
- showError(title, message);
74
+ // Use map-based lookup - O(1) complexity
75
+ const errorInfo = getErrorMessage(error);
76
+ showError(errorInfo.title, errorInfo.message);
107
77
  },
108
78
  });
109
79
  };
@@ -1,4 +1,4 @@
1
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
1
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
2
2
  import type { PremiumStatus } from "../handlers/PurchaseStatusResolver";
3
3
 
4
4
  export interface SubscriptionManagerConfig {
@@ -1,7 +1,7 @@
1
1
  import type { IRevenueCatService } from "../../../../shared/application/ports/IRevenueCatService";
2
2
  import { initializeRevenueCatService, getRevenueCatService } from "../services/RevenueCatService";
3
3
  import { ensureServiceAvailable } from "./subscriptionManagerUtils";
4
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
4
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
5
5
 
6
6
  export const performServiceInitialization = async (config: RevenueCatConfig, userId: string): Promise<{ service: IRevenueCatService; success: boolean }> => {
7
7
  await initializeRevenueCatService(config);
@@ -7,7 +7,7 @@ import Purchases, {
7
7
  type CustomerInfo,
8
8
  type CustomerInfoUpdateListener,
9
9
  } from "react-native-purchases";
10
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
10
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
11
11
  import { syncPremiumStatus } from "../utils/PremiumStatusSyncer";
12
12
  import {
13
13
  detectRenewal,
@@ -4,16 +4,16 @@ import {
4
4
  RevenueCatPurchaseError,
5
5
  RevenueCatInitializationError,
6
6
  RevenueCatNetworkError,
7
- } from "../../core/RevenueCatError";
8
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
7
+ } from "../../../revenuecat/core/errors";
8
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
9
9
  import {
10
10
  isUserCancelledError,
11
11
  isNetworkError,
12
12
  isAlreadyPurchasedError,
13
13
  isInvalidCredentialsError,
14
- getErrorMessage,
14
+ getRawErrorMessage,
15
15
  getErrorCode,
16
- } from "../../core/RevenueCatTypes";
16
+ } from "../../../revenuecat/core/types";
17
17
  import { syncPremiumStatus, notifyPurchaseCompleted } from "../utils/PremiumStatusSyncer";
18
18
  import { getSavedPurchase, clearSavedPurchase } from "../../presentation/useAuthAwarePurchase";
19
19
  import { handleRestore } from "./RestoreHandler";
@@ -120,7 +120,7 @@ export async function handlePurchase(
120
120
 
121
121
  // Generic error with code
122
122
  const errorCode = getErrorCode(error);
123
- const errorMessage = getErrorMessage(error, "Purchase failed");
123
+ const errorMessage = getRawErrorMessage(error, "Purchase failed");
124
124
  const enhancedMessage = errorCode
125
125
  ? `${errorMessage} (Code: ${errorCode})`
126
126
  : errorMessage;
@@ -4,14 +4,14 @@ import {
4
4
  RevenueCatRestoreError,
5
5
  RevenueCatInitializationError,
6
6
  RevenueCatNetworkError,
7
- } from "../../core/RevenueCatError";
8
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
7
+ } from "../../../revenuecat/core/errors";
8
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
9
9
  import {
10
- getErrorMessage,
10
+ getRawErrorMessage,
11
11
  getErrorCode,
12
12
  isNetworkError,
13
13
  isInvalidCredentialsError,
14
- } from "../../core/RevenueCatTypes";
14
+ } from "../../../revenuecat/core/types";
15
15
  import { syncPremiumStatus, notifyRestoreCompleted } from "../utils/PremiumStatusSyncer";
16
16
 
17
17
  export interface RestoreHandlerDeps {
@@ -53,7 +53,7 @@ export async function handleRestore(deps: RestoreHandlerDeps, userId: string): P
53
53
 
54
54
  // Generic error with code
55
55
  const errorCode = getErrorCode(error);
56
- const errorMessage = getErrorMessage(error, "Restore failed");
56
+ const errorMessage = getRawErrorMessage(error, "Restore failed");
57
57
  const enhancedMessage = errorCode
58
58
  ? `${errorMessage} (Code: ${errorCode})`
59
59
  : errorMessage;
@@ -1,9 +1,9 @@
1
1
  import Purchases from "react-native-purchases";
2
2
  import type { PurchasesOffering, PurchasesPackage, CustomerInfo } from "react-native-purchases";
3
3
  import type { IRevenueCatService, InitializeResult, PurchaseResult, RestoreResult } from "../../../../shared/application/ports/IRevenueCatService";
4
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
5
- import { resolveApiKey } from "../utils/ApiKeyResolver";
6
- import { initializeSDK } from "./RevenueCatInitializer";
4
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
5
+ import { resolveApiKey } from "../../../revenuecat/infrastructure/utils/ApiKeyResolver";
6
+ import { initializeSDK } from "../../../revenuecat/infrastructure/services/RevenueCatInitializer";
7
7
  import { fetchOfferings } from "./OfferingsFetcher";
8
8
  import { handlePurchase } from "./PurchaseHandler";
9
9
  import { handleRestore } from "./RestoreHandler";
@@ -1,4 +1,4 @@
1
- import type { RevenueCatConfig } from '../../core/RevenueCatConfig';
1
+ import type { RevenueCatConfig } from '../../../revenuecat/core/types';
2
2
 
3
3
  export class ServiceStateManager {
4
4
  private isInitializedFlag = false;
@@ -1,5 +1,5 @@
1
1
  import { RevenueCatService } from "./RevenueCatService.types";
2
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
2
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
3
3
 
4
4
  let revenueCatServiceInstance: RevenueCatService | null = null;
5
5
 
@@ -4,9 +4,9 @@
4
4
  */
5
5
 
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
- import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
7
+ import type { RevenueCatConfig } from "../../../revenuecat/core/types";
8
8
  import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
- import { getPremiumEntitlement } from "../../core/RevenueCatTypes";
9
+ import { getPremiumEntitlement } from "../../../revenuecat/core/types";
10
10
 
11
11
  export async function syncPremiumStatus(
12
12
  config: RevenueCatConfig,
@@ -1,73 +0,0 @@
1
- /**
2
- * RevenueCat Constants
3
- * Error codes, messages, and logging constants
4
- */
5
-
6
- import Purchases from "react-native-purchases";
7
-
8
- export const REVENUECAT_LOG_PREFIX = "[RevenueCat]";
9
-
10
- /**
11
- * RevenueCat Error Code Type
12
- * Re-export for type safety
13
- */
14
- export type PurchasesErrorCode = typeof Purchases.PURCHASES_ERROR_CODE[keyof typeof Purchases.PURCHASES_ERROR_CODE];
15
-
16
- /**
17
- * User-friendly error messages for RevenueCat errors
18
- * Maps error codes to human-readable messages
19
- */
20
- export const ERROR_MESSAGES: Record<string, { title: string; message: string }> = {
21
- PURCHASE_CANCELLED_ERROR: {
22
- title: "Purchase Cancelled",
23
- message: "The purchase was cancelled.",
24
- },
25
- PURCHASE_NOT_ALLOWED_ERROR: {
26
- title: "Purchase Not Allowed",
27
- message: "In-app purchases are disabled on this device.",
28
- },
29
- PURCHASE_INVALID_ERROR: {
30
- title: "Invalid Purchase",
31
- message: "The purchase is invalid. Please contact support.",
32
- },
33
- PRODUCT_ALREADY_PURCHASED_ERROR: {
34
- title: "Already Purchased",
35
- message: "You already own this subscription. Restoring your purchase...",
36
- },
37
- PRODUCT_NOT_AVAILABLE_FOR_PURCHASE_ERROR: {
38
- title: "Product Unavailable",
39
- message: "This product is not available for purchase at this time.",
40
- },
41
- NETWORK_ERROR: {
42
- title: "Network Error",
43
- message: "Please check your internet connection and try again.",
44
- },
45
- UNKNOWN_ERROR: {
46
- title: "Unknown Error",
47
- message: "An unexpected error occurred. Please try again.",
48
- },
49
- RECEIPT_ALREADY_IN_USE_ERROR: {
50
- title: "Receipt Already Used",
51
- message: "This receipt is already associated with another account.",
52
- },
53
- INVALID_CREDENTIALS_ERROR: {
54
- title: "Configuration Error",
55
- message: "The app is not configured correctly. Please contact support.",
56
- },
57
- UNEXPECTED_BACKEND_RESPONSE_ERROR: {
58
- title: "Server Error",
59
- message: "The server returned an unexpected response. Please try again later.",
60
- },
61
- CONFIGURATION_ERROR: {
62
- title: "Configuration Error",
63
- message: "RevenueCat is not configured correctly. Please contact support.",
64
- },
65
- STORE_PROBLEM_ERROR: {
66
- title: "Store Error",
67
- message: "There was a problem with the app store. Please try again.",
68
- },
69
- PAYMENT_PENDING_ERROR: {
70
- title: "Payment Pending",
71
- message: "Your payment is still being processed. Please check back later.",
72
- },
73
- };