@umituz/react-native-subscription 3.1.34 → 3.1.36
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/CreditsInitializer.ts +34 -39
- package/src/domains/credits/application/DeductCreditsCommand.ts +13 -12
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +1 -1
- package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +1 -1
- package/src/domains/credits/presentation/useCredits.types.ts +1 -1
- package/src/domains/paywall/components/PaywallScreen.tsx +12 -11
- package/src/domains/paywall/hooks/usePaywallActions.ts +4 -3
- package/src/domains/paywall/hooks/usePaywallActions.utils.ts +14 -19
- package/src/domains/paywall/hooks/usePaywallPurchase.ts +10 -17
- package/src/domains/paywall/hooks/usePaywallRestore.ts +8 -15
- package/src/domains/revenuecat/core/errors/RevenueCatError.ts +1 -1
- package/src/domains/revenuecat/infrastructure/services/ConfigurationStateManager.ts +1 -1
- package/src/domains/revenuecat/infrastructure/services/RevenueCatInitializer.ts +7 -6
- package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +8 -10
- package/src/domains/revenuecat/infrastructure/services/initializerConstants.ts +1 -1
- package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +17 -34
- package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +4 -5
- package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +19 -29
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +17 -29
- package/src/domains/subscription/application/sync/CreditDocumentOperations.ts +16 -17
- package/src/domains/subscription/application/sync/PurchaseSyncHandler.ts +20 -23
- package/src/domains/subscription/application/sync/RenewalSyncHandler.ts +8 -7
- package/src/domains/subscription/application/sync/StatusChangeSyncHandler.ts +4 -3
- package/src/domains/subscription/application/sync/SyncProcessorLogger.ts +40 -65
- package/src/domains/subscription/application/sync/UserIdResolver.ts +5 -1
- package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +1 -1
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
- package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts +7 -6
- package/src/domains/subscription/infrastructure/handlers/package-operations/PackagePurchaser.ts +8 -7
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +4 -3
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +21 -28
- package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/managerOperations.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/packageHandlerFactory.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/premiumStatusChecker.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +1 -1
- package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +10 -9
- package/src/domains/subscription/infrastructure/services/OfferingsFetcher.ts +14 -21
- package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +9 -8
- package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +1 -1
- package/src/domains/subscription/infrastructure/services/RevenueCatService.types.ts +5 -4
- package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +15 -29
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseErrorHandler.ts +4 -2
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +27 -33
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +5 -1
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +11 -17
- package/src/domains/subscription/presentation/providers/SubscriptionFlowProvider.tsx +11 -12
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -1
- package/src/domains/subscription/presentation/useSyncStatusListener.ts +10 -9
- package/src/domains/wallet/presentation/components/TransactionList.tsx +1 -1
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +2 -2
- package/src/domains/wallet/presentation/screens/WalletScreen.tsx +1 -1
- package/src/init/createSubscriptionInitModule.ts +4 -1
- package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Purchases, { type CustomerInfo } from "react-native-purchases";
|
|
8
|
-
import type { InitializeResult } from "
|
|
8
|
+
import type { InitializeResult } from "../../../shared/application/ports/IRevenueCatService";
|
|
9
9
|
import type { InitializerDeps } from "./RevenueCatInitializer.types";
|
|
10
10
|
import { FAILED_INITIALIZATION_RESULT } from "./initializerConstants";
|
|
11
11
|
import { UserSwitchMutex } from "./UserSwitchMutex";
|
|
@@ -16,8 +16,9 @@ import {
|
|
|
16
16
|
buildSuccessResult,
|
|
17
17
|
fetchOfferingsSafe,
|
|
18
18
|
} from "./userSwitchHelpers";
|
|
19
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
const logger = createLogger("UserSwitchCore");
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Handle user switch operation with mutex protection.
|
|
@@ -31,9 +32,7 @@ export async function handleUserSwitch(
|
|
|
31
32
|
const { shouldProceed, existingPromise } = await UserSwitchMutex.acquire(mutexKey);
|
|
32
33
|
|
|
33
34
|
if (!shouldProceed && existingPromise) {
|
|
34
|
-
|
|
35
|
-
console.log('[UserSwitchCore] Using result from active switch operation');
|
|
36
|
-
}
|
|
35
|
+
logger.debug("Using result from active switch operation");
|
|
37
36
|
return existingPromise as Promise<InitializeResult>;
|
|
38
37
|
}
|
|
39
38
|
|
|
@@ -54,38 +53,28 @@ async function performUserSwitch(
|
|
|
54
53
|
const normalizedUserId = normalizeUserId(userId);
|
|
55
54
|
const normalizedCurrentUserId = isAnonymousId(currentAppUserId) ? null : currentAppUserId;
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
}
|
|
56
|
+
logger.debug("performUserSwitch", {
|
|
57
|
+
providedUserId: userId,
|
|
58
|
+
normalizedUserId: normalizedUserId || '(null - anonymous)',
|
|
59
|
+
currentAppUserId,
|
|
60
|
+
normalizedCurrentUserId: normalizedCurrentUserId || '(null - anonymous)',
|
|
61
|
+
needsSwitch: normalizedCurrentUserId !== normalizedUserId,
|
|
62
|
+
});
|
|
66
63
|
|
|
67
64
|
let customerInfo: CustomerInfo;
|
|
68
65
|
|
|
69
66
|
if (normalizedCurrentUserId !== normalizedUserId) {
|
|
70
67
|
if (normalizedUserId) {
|
|
71
|
-
|
|
72
|
-
console.log('[UserSwitchCore] Calling Purchases.logIn() to switch from anonymous to:', normalizedUserId);
|
|
73
|
-
}
|
|
68
|
+
logger.debug("Calling Purchases.logIn() to switch from anonymous to", normalizedUserId);
|
|
74
69
|
const result = await Purchases.logIn(normalizedUserId!);
|
|
75
70
|
customerInfo = result.customerInfo;
|
|
76
|
-
|
|
77
|
-
console.log('[UserSwitchCore] Purchases.logIn() successful, created:', result.created);
|
|
78
|
-
}
|
|
71
|
+
logger.debug("Purchases.logIn() successful, created", result.created);
|
|
79
72
|
} else {
|
|
80
|
-
|
|
81
|
-
console.log('[UserSwitchCore] User is anonymous, fetching customer info');
|
|
82
|
-
}
|
|
73
|
+
logger.debug("User is anonymous, fetching customer info");
|
|
83
74
|
customerInfo = await Purchases.getCustomerInfo();
|
|
84
75
|
}
|
|
85
76
|
} else {
|
|
86
|
-
|
|
87
|
-
console.log('[UserSwitchCore] No user switch needed, fetching current customer info');
|
|
88
|
-
}
|
|
77
|
+
logger.debug("No user switch needed, fetching current customer info");
|
|
89
78
|
customerInfo = await Purchases.getCustomerInfo();
|
|
90
79
|
}
|
|
91
80
|
|
|
@@ -93,9 +82,7 @@ async function performUserSwitch(
|
|
|
93
82
|
deps.setCurrentUserId(normalizedUserId || undefined);
|
|
94
83
|
const offerings = await fetchOfferingsSafe();
|
|
95
84
|
|
|
96
|
-
|
|
97
|
-
console.log('[UserSwitchCore] User switch completed successfully');
|
|
98
|
-
}
|
|
85
|
+
logger.debug("User switch completed successfully");
|
|
99
86
|
|
|
100
87
|
return buildSuccessResult(deps, customerInfo, offerings);
|
|
101
88
|
} catch (error) {
|
|
@@ -106,11 +93,7 @@ async function performUserSwitch(
|
|
|
106
93
|
// Ignore error in error handler
|
|
107
94
|
}
|
|
108
95
|
|
|
109
|
-
|
|
110
|
-
userId,
|
|
111
|
-
currentAppUserId,
|
|
112
|
-
error
|
|
113
|
-
});
|
|
96
|
+
logger.error("Failed during user switch or fetch", error, { userId, currentAppUserId });
|
|
114
97
|
return FAILED_INITIALIZATION_RESULT;
|
|
115
98
|
}
|
|
116
99
|
}
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Purchases, { type CustomerInfo, type PurchasesOfferings } from "react-native-purchases";
|
|
8
|
-
import type { InitializeResult } from "
|
|
8
|
+
import type { InitializeResult } from "../../../shared/application/ports/IRevenueCatService";
|
|
9
9
|
import type { InitializerDeps } from "./RevenueCatInitializer.types";
|
|
10
10
|
import { ANONYMOUS_CACHE_KEY } from "../../../subscription/core/SubscriptionConstants";
|
|
11
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
const logger = createLogger("UserSwitchHelpers");
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Normalize user ID to null if empty or anonymous cache key.
|
|
@@ -47,9 +48,7 @@ export async function fetchOfferingsSafe(): Promise<PurchasesOfferings | null> {
|
|
|
47
48
|
try {
|
|
48
49
|
return await Purchases.getOfferings();
|
|
49
50
|
} catch (error) {
|
|
50
|
-
|
|
51
|
-
console.warn('[UserSwitchHelpers] Offerings fetch failed (non-fatal):', error);
|
|
52
|
-
}
|
|
51
|
+
logger.warn("Offerings fetch failed (non-fatal)", error);
|
|
53
52
|
return null;
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Purchases, { type CustomerInfo } from "react-native-purchases";
|
|
8
|
-
import type { InitializeResult } from "
|
|
8
|
+
import type { InitializeResult } from "../../../shared/application/ports/IRevenueCatService";
|
|
9
9
|
import type { InitializerDeps } from "./RevenueCatInitializer.types";
|
|
10
10
|
import { FAILED_INITIALIZATION_RESULT } from "./initializerConstants";
|
|
11
11
|
import { getPremiumEntitlement } from "../../core/types/RevenueCatTypes";
|
|
@@ -15,8 +15,9 @@ import {
|
|
|
15
15
|
buildSuccessResult,
|
|
16
16
|
fetchOfferingsSafe,
|
|
17
17
|
} from "./userSwitchHelpers";
|
|
18
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
const logger = createLogger("UserSwitchInitializer");
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Handle initial SDK configuration with API key and user ID.
|
|
@@ -29,14 +30,12 @@ export async function handleInitialConfiguration(
|
|
|
29
30
|
try {
|
|
30
31
|
const normalizedUserId = normalizeUserId(userId);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
39
|
-
}
|
|
33
|
+
logger.debug("handleInitialConfiguration", {
|
|
34
|
+
providedUserId: userId,
|
|
35
|
+
normalizedUserId: normalizedUserId || '(null - anonymous)',
|
|
36
|
+
apiKeyPrefix: apiKey.substring(0, 5) + '...',
|
|
37
|
+
isTestKey: apiKey.startsWith('test_'),
|
|
38
|
+
});
|
|
40
39
|
|
|
41
40
|
Purchases.setLogLevel(
|
|
42
41
|
(typeof __DEV__ !== 'undefined' && __DEV__)
|
|
@@ -48,32 +47,25 @@ export async function handleInitialConfiguration(
|
|
|
48
47
|
deps.setInitialized(true);
|
|
49
48
|
deps.setCurrentUserId(normalizedUserId || undefined);
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
console.log('[UserSwitchInitializer] Purchases.configure() successful');
|
|
53
|
-
}
|
|
50
|
+
logger.debug("Purchases.configure() successful");
|
|
54
51
|
|
|
55
52
|
const [customerInfo, offerings] = await Promise.all([
|
|
56
53
|
Purchases.getCustomerInfo(),
|
|
57
54
|
fetchOfferingsSafe(),
|
|
58
55
|
]);
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
}
|
|
57
|
+
const currentUserId = await Purchases.getAppUserID();
|
|
58
|
+
logger.debug("Initial configuration completed", {
|
|
59
|
+
revenueCatUserId: currentUserId,
|
|
60
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
61
|
+
offeringsCount: offerings?.all ? Object.keys(offerings.all).length : 0,
|
|
62
|
+
});
|
|
68
63
|
|
|
69
64
|
await syncPremiumStatusIfConfigured(deps, normalizedUserId, customerInfo);
|
|
70
65
|
|
|
71
66
|
return buildSuccessResult(deps, customerInfo, offerings);
|
|
72
67
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
userId,
|
|
75
|
-
error
|
|
76
|
-
});
|
|
68
|
+
logger.error("SDK configuration failed", error, { userId });
|
|
77
69
|
return FAILED_INITIALIZATION_RESULT;
|
|
78
70
|
}
|
|
79
71
|
}
|
|
@@ -89,9 +81,7 @@ export async function fetchCurrentUserData(deps: InitializerDeps): Promise<Initi
|
|
|
89
81
|
]);
|
|
90
82
|
return buildSuccessResult(deps, customerInfo, offerings);
|
|
91
83
|
} catch (error) {
|
|
92
|
-
|
|
93
|
-
error
|
|
94
|
-
});
|
|
84
|
+
logger.error("Failed to fetch customer info for initialized user", error);
|
|
95
85
|
return FAILED_INITIALIZATION_RESULT;
|
|
96
86
|
}
|
|
97
87
|
}
|
|
@@ -138,6 +128,6 @@ async function syncPremiumStatusIfConfigured(
|
|
|
138
128
|
});
|
|
139
129
|
}
|
|
140
130
|
} catch (error) {
|
|
141
|
-
|
|
131
|
+
logger.error("Premium status sync callback failed", error);
|
|
142
132
|
}
|
|
143
133
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
|
|
2
2
|
import { getCurrentUserId, setupAuthStateListener } from "../SubscriptionAuthListener";
|
|
3
3
|
import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
|
|
4
|
+
import { createLogger } from "../../../shared/utils/logger";
|
|
5
|
+
|
|
6
|
+
const logger = createLogger("BackgroundInitializer");
|
|
4
7
|
|
|
5
8
|
const AUTH_STATE_DEBOUNCE_MS = 500;
|
|
6
9
|
const MAX_RETRY_ATTEMPTS = 3;
|
|
@@ -16,18 +19,14 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
16
19
|
let lastUserId: string | undefined = undefined;
|
|
17
20
|
|
|
18
21
|
const initializeInBackground = async (revenueCatUserId?: string): Promise<void> => {
|
|
19
|
-
|
|
20
|
-
console.log('[BackgroundInitializer] initializeInBackground called with userId:', revenueCatUserId || '(undefined - anonymous)');
|
|
21
|
-
}
|
|
22
|
+
logger.debug("initializeInBackground called with userId", revenueCatUserId || '(undefined - anonymous)');
|
|
22
23
|
await SubscriptionManager.initialize(revenueCatUserId);
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
const attemptInitWithRetry = async (revenueCatUserId: string | undefined, attempt: number, sequenceId: number): Promise<void> => {
|
|
26
27
|
// Abort if this is no longer the active sequence (e.g., user changed)
|
|
27
28
|
if (sequenceId !== currentSequenceId) {
|
|
28
|
-
|
|
29
|
-
console.log('[BackgroundInitializer] Aborting retry - sequence changed');
|
|
30
|
-
}
|
|
29
|
+
logger.debug("Aborting retry - sequence changed");
|
|
31
30
|
return;
|
|
32
31
|
}
|
|
33
32
|
|
|
@@ -38,23 +37,20 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
38
37
|
}
|
|
39
38
|
} catch (error) {
|
|
40
39
|
if (sequenceId !== currentSequenceId) return;
|
|
41
|
-
|
|
40
|
+
|
|
42
41
|
lastInitSucceeded = false;
|
|
43
|
-
|
|
42
|
+
logger.error("Initialization failed", error, {
|
|
44
43
|
userId: revenueCatUserId,
|
|
45
44
|
attempt: attempt + 1,
|
|
46
45
|
maxAttempts: MAX_RETRY_ATTEMPTS,
|
|
47
|
-
error: error instanceof Error ? error.message : String(error)
|
|
48
46
|
});
|
|
49
47
|
|
|
50
48
|
if (attempt < MAX_RETRY_ATTEMPTS - 1) {
|
|
51
|
-
|
|
52
|
-
console.log('[BackgroundInitializer] Scheduling retry', { attempt: attempt + 2 });
|
|
53
|
-
}
|
|
49
|
+
logger.debug("Scheduling retry", { attempt: attempt + 2 });
|
|
54
50
|
retryTimer = setTimeout(() => {
|
|
55
51
|
// Fire and forget promise, but safe because of sequenceId check
|
|
56
52
|
attemptInitWithRetry(revenueCatUserId, attempt + 1, sequenceId).catch(err => {
|
|
57
|
-
|
|
53
|
+
logger.error("Retry failed unhandled", err);
|
|
58
54
|
});
|
|
59
55
|
}, RETRY_DELAY_MS * (attempt + 1));
|
|
60
56
|
}
|
|
@@ -71,9 +67,7 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
if (lastUserId === revenueCatUserId && lastInitSucceeded) {
|
|
74
|
-
|
|
75
|
-
console.log('[BackgroundInitializer] UserId unchanged and init succeeded, skipping');
|
|
76
|
-
}
|
|
70
|
+
logger.debug("UserId unchanged and init succeeded, skipping");
|
|
77
71
|
return;
|
|
78
72
|
}
|
|
79
73
|
|
|
@@ -83,15 +77,11 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
83
77
|
const sequenceId = currentSequenceId;
|
|
84
78
|
|
|
85
79
|
if (!revenueCatUserId && !lastUserId) {
|
|
86
|
-
|
|
87
|
-
console.log('[BackgroundInitializer] No user and no previous user, waiting for auth');
|
|
88
|
-
}
|
|
80
|
+
logger.debug("No user and no previous user, waiting for auth");
|
|
89
81
|
return;
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', revenueCatUserId || '(undefined - anonymous)');
|
|
94
|
-
}
|
|
84
|
+
logger.debug("Auth state listener triggered, reinitializing with userId", revenueCatUserId || '(undefined - anonymous)');
|
|
95
85
|
|
|
96
86
|
// Important: Always reset on user change, not just on logout.
|
|
97
87
|
// This ensures previous user's cached state is cleared before init.
|
|
@@ -104,7 +94,7 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
104
94
|
|
|
105
95
|
// Start the retry chain
|
|
106
96
|
attemptInitWithRetry(revenueCatUserId, 0, sequenceId).catch(err => {
|
|
107
|
-
|
|
97
|
+
logger.error("Init sequence failed unhandled", err);
|
|
108
98
|
});
|
|
109
99
|
}, AUTH_STATE_DEBOUNCE_MS);
|
|
110
100
|
};
|
|
@@ -117,17 +107,15 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
117
107
|
const initialRevenueCatUserId = getCurrentUserId(() => auth);
|
|
118
108
|
lastUserId = initialRevenueCatUserId;
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
console.log('[BackgroundInitializer] Initial RevenueCat userId:', initialRevenueCatUserId || '(undefined - anonymous)');
|
|
122
|
-
}
|
|
110
|
+
logger.debug("Initial RevenueCat userId", initialRevenueCatUserId || '(undefined - anonymous)');
|
|
123
111
|
|
|
124
112
|
if (initialRevenueCatUserId) {
|
|
125
113
|
currentSequenceId++;
|
|
126
114
|
attemptInitWithRetry(initialRevenueCatUserId, 0, currentSequenceId).catch(err => {
|
|
127
|
-
|
|
115
|
+
logger.error("Initial sequence failed unhandled", err);
|
|
128
116
|
});
|
|
129
|
-
} else
|
|
130
|
-
|
|
117
|
+
} else {
|
|
118
|
+
logger.debug("No user available yet, waiting for auth state");
|
|
131
119
|
}
|
|
132
120
|
|
|
133
121
|
const unsubscribe = setupAuthStateListener(() => auth, debouncedInitialize);
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
import { getCreditsRepository } from "../../../credits/infrastructure/CreditsRepositoryManager";
|
|
7
7
|
import type { PremiumStatusChangedEvent } from "../../core/SubscriptionEvents";
|
|
8
|
+
import { createLogger } from "../../../shared/utils/logger";
|
|
9
|
+
|
|
10
|
+
const logger = createLogger("CreditDocumentOperations");
|
|
8
11
|
|
|
9
12
|
export class CreditDocumentOperations {
|
|
10
13
|
async expireSubscription(userId: string): Promise<void> {
|
|
@@ -14,14 +17,12 @@ export class CreditDocumentOperations {
|
|
|
14
17
|
async syncPremiumStatus(userId: string, event: PremiumStatusChangedEvent): Promise<void> {
|
|
15
18
|
const repo = getCreditsRepository();
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
}
|
|
20
|
+
logger.debug("syncPremiumStatus: Starting", {
|
|
21
|
+
userId,
|
|
22
|
+
isPremium: event.isPremium,
|
|
23
|
+
productId: event.productId,
|
|
24
|
+
willRenew: event.willRenew,
|
|
25
|
+
});
|
|
25
26
|
|
|
26
27
|
// Ensure premium user has a credits document (recovery)
|
|
27
28
|
if (event.isPremium) {
|
|
@@ -32,8 +33,8 @@ export class CreditDocumentOperations {
|
|
|
32
33
|
event.expirationDate ?? null,
|
|
33
34
|
event.periodType ?? null,
|
|
34
35
|
);
|
|
35
|
-
if (
|
|
36
|
-
|
|
36
|
+
if (created) {
|
|
37
|
+
logger.debug("Recovery: created missing credits document for premium user", {
|
|
37
38
|
userId,
|
|
38
39
|
productId: event.productId,
|
|
39
40
|
});
|
|
@@ -53,12 +54,10 @@ export class CreditDocumentOperations {
|
|
|
53
54
|
ownershipType: event.ownershipType ?? null,
|
|
54
55
|
});
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
62
|
-
}
|
|
57
|
+
logger.debug("syncPremiumStatus: Completed", {
|
|
58
|
+
userId,
|
|
59
|
+
isPremium: event.isPremium,
|
|
60
|
+
productId: event.productId,
|
|
61
|
+
});
|
|
63
62
|
}
|
|
64
63
|
}
|
|
@@ -9,6 +9,9 @@ import { extractRevenueCatData } from "../SubscriptionSyncUtils";
|
|
|
9
9
|
import { generatePurchaseId } from "../syncIdGenerators";
|
|
10
10
|
import type { PurchaseCompletedEvent } from "../../core/SubscriptionEvents";
|
|
11
11
|
import { UserIdResolver } from "./UserIdResolver";
|
|
12
|
+
import { createLogger } from "../../../shared/utils/logger";
|
|
13
|
+
|
|
14
|
+
const logger = createLogger("PurchaseSyncHandler");
|
|
12
15
|
|
|
13
16
|
export class PurchaseSyncHandler {
|
|
14
17
|
private purchaseInProgress = false;
|
|
@@ -25,14 +28,12 @@ export class PurchaseSyncHandler {
|
|
|
25
28
|
async processPurchase(event: PurchaseCompletedEvent): Promise<void> {
|
|
26
29
|
this.purchaseInProgress = true;
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
35
|
-
}
|
|
31
|
+
logger.debug("Starting credit initialization", {
|
|
32
|
+
productId: event.productId,
|
|
33
|
+
source: event.source,
|
|
34
|
+
packageType: event.packageType,
|
|
35
|
+
activeEntitlements: Object.keys(event.customerInfo.entitlements.active),
|
|
36
|
+
});
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
39
|
// Extract revenue cat data
|
|
@@ -46,14 +47,12 @@ export class PurchaseSyncHandler {
|
|
|
46
47
|
// Resolve user ID
|
|
47
48
|
const creditsUserId = await this.userIdResolver.resolveCreditsUserId(event.userId);
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
}
|
|
50
|
+
logger.debug("Calling initializeCredits", {
|
|
51
|
+
creditsUserId,
|
|
52
|
+
purchaseId,
|
|
53
|
+
productId: event.productId,
|
|
54
|
+
revenueCatUserId: revenueCatData.revenueCatUserId,
|
|
55
|
+
});
|
|
57
56
|
|
|
58
57
|
// Initialize credits
|
|
59
58
|
const result = await getCreditsRepository().initializeCredits(
|
|
@@ -69,13 +68,11 @@ export class PurchaseSyncHandler {
|
|
|
69
68
|
throw new Error(`[PurchaseSyncHandler] Credit initialization failed: ${result.error?.message ?? 'unknown'}`);
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
}
|
|
71
|
+
logger.debug("Credits initialized successfully", {
|
|
72
|
+
creditsUserId,
|
|
73
|
+
purchaseId,
|
|
74
|
+
credits: result.data?.credits,
|
|
75
|
+
});
|
|
79
76
|
} finally {
|
|
80
77
|
this.purchaseInProgress = false;
|
|
81
78
|
}
|
|
@@ -9,6 +9,9 @@ import { extractRevenueCatData } from "../SubscriptionSyncUtils";
|
|
|
9
9
|
import { generateRenewalId } from "../syncIdGenerators";
|
|
10
10
|
import type { RenewalDetectedEvent } from "../../core/SubscriptionEvents";
|
|
11
11
|
import { UserIdResolver } from "./UserIdResolver";
|
|
12
|
+
import { createLogger } from "../../../shared/utils/logger";
|
|
13
|
+
|
|
14
|
+
const logger = createLogger("RenewalSyncHandler");
|
|
12
15
|
|
|
13
16
|
export class RenewalSyncHandler {
|
|
14
17
|
private renewalInProgress = false;
|
|
@@ -55,13 +58,11 @@ export class RenewalSyncHandler {
|
|
|
55
58
|
throw new Error(`[RenewalSyncHandler] Credit initialization failed: ${result.error?.message ?? 'unknown'}`);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
}
|
|
61
|
+
logger.debug("Renewal credits allocated successfully", {
|
|
62
|
+
creditsUserId,
|
|
63
|
+
purchaseId,
|
|
64
|
+
productId: event.productId,
|
|
65
|
+
});
|
|
65
66
|
} finally {
|
|
66
67
|
this.renewalInProgress = false;
|
|
67
68
|
}
|
|
@@ -8,6 +8,9 @@ import type { PremiumStatusChangedEvent } from "../../core/SubscriptionEvents";
|
|
|
8
8
|
import { UserIdResolver } from "./UserIdResolver";
|
|
9
9
|
import { CreditDocumentOperations } from "./CreditDocumentOperations";
|
|
10
10
|
import { PurchaseSyncHandler } from "./PurchaseSyncHandler";
|
|
11
|
+
import { createLogger } from "../../../shared/utils/logger";
|
|
12
|
+
|
|
13
|
+
const logger = createLogger("StatusChangeSyncHandler");
|
|
11
14
|
|
|
12
15
|
export class StatusChangeSyncHandler {
|
|
13
16
|
constructor(
|
|
@@ -19,9 +22,7 @@ export class StatusChangeSyncHandler {
|
|
|
19
22
|
async processStatusChange(event: PremiumStatusChangedEvent): Promise<void> {
|
|
20
23
|
// If purchase is in progress, only do recovery sync
|
|
21
24
|
if (this.purchaseHandler.isProcessing()) {
|
|
22
|
-
|
|
23
|
-
console.log("[StatusChangeSyncHandler] Purchase in progress - running recovery only");
|
|
24
|
-
}
|
|
25
|
+
logger.debug("Purchase in progress - running recovery only");
|
|
25
26
|
if (event.isPremium && event.productId) {
|
|
26
27
|
const creditsUserId = await this.userIdResolver.resolveCreditsUserId(event.userId);
|
|
27
28
|
await this.creditOps.syncPremiumStatus(creditsUserId, event);
|