@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.
- package/package.json +1 -1
- package/src/domains/credits/application/CreditLimitCalculator.ts +1 -9
- package/src/domains/credits/application/DeductCreditsCommand.ts +13 -6
- package/src/domains/credits/application/RefundCreditsCommand.ts +1 -5
- package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +1 -5
- package/src/domains/credits/application/creditDocumentHelpers.ts +2 -9
- package/src/domains/credits/core/Credits.ts +0 -23
- package/src/domains/credits/core/CreditsMapper.ts +0 -6
- package/src/domains/credits/core/UserCreditsDocument.ts +0 -12
- package/src/domains/credits/infrastructure/CreditsRepositoryManager.ts +0 -21
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +2 -1
- package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +2 -2
- package/src/domains/credits/presentation/useCredits.ts +10 -9
- package/src/domains/paywall/components/PaywallContainer.types.ts +0 -28
- package/src/domains/paywall/components/PaywallModal.styles.ts +0 -4
- package/src/domains/paywall/entities/types.ts +0 -5
- package/src/domains/paywall/hooks/usePaywallActions.ts +1 -15
- package/src/domains/revenuecat/core/errors/RevenueCatError.ts +0 -6
- package/src/domains/revenuecat/core/errors/RevenueCatErrorHandler.ts +0 -24
- package/src/domains/revenuecat/core/errors/RevenueCatErrorMessages.ts +0 -18
- package/src/domains/revenuecat/core/errors/index.ts +0 -4
- package/src/domains/revenuecat/core/types/RevenueCatConfig.ts +3 -7
- package/src/domains/revenuecat/core/types/RevenueCatData.ts +4 -9
- package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +5 -65
- package/src/domains/revenuecat/core/types/index.ts +0 -4
- package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +1 -24
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +5 -21
- package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +1 -5
- package/src/domains/subscription/application/SubscriptionSyncService.ts +11 -2
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +15 -2
- package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +9 -2
- package/src/domains/subscription/constants/thresholds.ts +0 -9
- package/src/domains/subscription/core/SubscriptionConstants.ts +0 -4
- package/src/domains/subscription/core/SubscriptionStatus.ts +11 -21
- package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +4 -7
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
- package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +0 -13
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -18
- package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +3 -17
- package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +0 -17
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +0 -19
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -6
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +0 -17
- package/src/domains/subscription/infrastructure/state/initializationState.ts +0 -25
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +0 -21
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +2 -7
- package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +0 -5
- package/src/domains/subscription/infrastructure/utils/renewal/PackageTierComparator.ts +1 -0
- package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +0 -18
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.styles.ts +0 -5
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +0 -5
- package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -5
- package/src/domains/subscription/presentation/stores/index.ts +0 -4
- package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +0 -13
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +30 -21
- package/src/domains/subscription/presentation/usePaywallVisibility.ts +0 -9
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +8 -11
- package/src/domains/subscription/utils/authGuards.ts +3 -0
- package/src/domains/trial/application/TrialService.ts +0 -9
- package/src/domains/trial/core/TrialTypes.ts +0 -8
- package/src/domains/wallet/domain/mappers/TransactionMapper.ts +0 -5
- package/src/domains/wallet/domain/types/transaction.types.ts +0 -7
- package/src/domains/wallet/index.ts +0 -7
- package/src/domains/wallet/infrastructure/config/walletConfig.ts +0 -11
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +6 -3
- package/src/domains/wallet/presentation/hooks/useWallet.ts +0 -7
- package/src/domains/wallet/utils/transactionIconMap.ts +0 -10
- package/src/global.d.ts +0 -6
- package/src/index.ts +1 -4
- package/src/init/createSubscriptionInitModule.ts +12 -2
- package/src/init/index.ts +1 -5
- package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -11
- package/src/shared/application/FeedbackService.ts +3 -21
- package/src/shared/application/ports/ISubscriptionRepository.ts +0 -4
- package/src/shared/infrastructure/SubscriptionEventBus.ts +0 -13
- package/src/shared/infrastructure/firestore/collectionUtils.ts +1 -17
- package/src/shared/infrastructure/firestore/index.ts +0 -4
- package/src/shared/infrastructure/firestore/resultUtils.ts +0 -12
- package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +0 -17
- package/src/shared/infrastructure/react-query/queryConfig.ts +0 -15
- package/src/shared/utils/BaseError.ts +0 -5
- package/src/shared/utils/Result.ts +0 -20
- package/src/shared/utils/dateConverter.ts +6 -46
- package/src/utils/appUtils.ts +0 -16
- package/src/utils/creditMapper.ts +0 -7
- package/src/utils/dateUtils.compare.ts +0 -24
- package/src/utils/dateUtils.core.ts +0 -39
- package/src/utils/dateUtils.format.ts +0 -41
- package/src/utils/dateUtils.math.ts +0 -41
- package/src/utils/dateUtils.ts +0 -5
- package/src/utils/packagePeriodUtils.ts +0 -20
- package/src/utils/packageTypeDetector.ts +1 -21
- package/src/utils/premiumStatusUtils.ts +1 -14
- package/src/utils/priceUtils.ts +0 -35
- package/src/utils/tierUtils.ts +1 -8
- package/src/utils/types.ts +1 -25
- package/src/utils/validation.ts +1 -7
- package/src/domains/README.md +0 -52
- package/src/domains/config/domain/README.md +0 -37
- package/src/domains/config/domain/entities/README.md +0 -41
- package/src/domains/paywall/README.md +0 -101
- package/src/domains/paywall/entities/README.md +0 -40
- package/src/domains/paywall/hooks/README.md +0 -41
- package/src/domains/subscription/application/syncConstants.ts +0 -1
- package/src/domains/subscription/infrastructure/README.md +0 -41
- package/src/domains/subscription/infrastructure/config/README.md +0 -49
- package/src/domains/subscription/infrastructure/handlers/README.md +0 -41
- package/src/domains/subscription/infrastructure/hooks/README.md +0 -50
- package/src/domains/subscription/infrastructure/managers/README.md +0 -41
- package/src/domains/subscription/infrastructure/services/README.md +0 -42
- package/src/domains/subscription/infrastructure/utils/README.md +0 -41
- package/src/domains/subscription/presentation/components/README.md +0 -155
- package/src/domains/subscription/presentation/components/details/CreditRow.md +0 -92
- package/src/domains/subscription/presentation/components/details/DetailRow.md +0 -91
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.md +0 -93
- package/src/domains/subscription/presentation/components/details/PremiumStatusBadge.md +0 -91
- package/src/domains/subscription/presentation/components/details/README.md +0 -99
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.md +0 -90
- package/src/domains/subscription/presentation/components/feedback/README.md +0 -99
- package/src/domains/subscription/presentation/components/paywall/PaywallModal.md +0 -94
- package/src/domains/subscription/presentation/components/paywall/README.md +0 -54
- package/src/domains/subscription/presentation/components/sections/README.md +0 -99
- package/src/domains/subscription/presentation/components/sections/SubscriptionSection.md +0 -94
- package/src/domains/subscription/presentation/utils/README.md +0 -31
- package/src/domains/wallet/README.md +0 -51
- package/src/domains/wallet/domain/README.md +0 -41
- package/src/domains/wallet/infrastructure/README.md +0 -41
- package/src/domains/wallet/presentation/components/README.md +0 -41
- package/src/domains/wallet/presentation/hooks/README.md +0 -41
- package/src/shared/application/ports/README.md +0 -48
|
@@ -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
|
|
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,
|
|
25
|
-
packageType?: PackageType | null
|
|
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;
|
|
14
|
-
packageType: PackageType | null;
|
|
8
|
+
periodType: string | null;
|
|
9
|
+
packageType: PackageType | null;
|
|
15
10
|
unsubscribeDetectedAt: string | null;
|
|
16
11
|
billingIssueDetectedAt: string | null;
|
|
17
|
-
store: Store | null;
|
|
18
|
-
ownershipType: OwnershipType | null;
|
|
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
|
|
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,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;
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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(
|
|
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;
|
|
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
|
-
|
|
19
|
-
|
|
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/
|
|
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
|
-
) =>
|
|
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
|
-
) =>
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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;
|
|
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
|
-
|