@umituz/react-native-subscription 3.1.33 → 3.1.35
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/presentation/deduct-credit/useDeductCredit.ts +9 -16
- package/src/domains/credits/presentation/useCredits.ts +2 -2
- 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/infrastructure/services/RevenueCatInitializer.ts +6 -5
- package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +8 -10
- package/src/domains/revenuecat/infrastructure/services/userSwitchCore.ts +16 -33
- package/src/domains/revenuecat/infrastructure/services/userSwitchHelpers.ts +3 -4
- package/src/domains/revenuecat/infrastructure/services/userSwitchInitializer.ts +18 -28
- 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 +39 -64
- package/src/domains/subscription/application/sync/UserIdResolver.ts +5 -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 +20 -27
- 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 +8 -7
- package/src/domains/subscription/infrastructure/services/RevenueCatService.types.ts +4 -3
- 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/useSyncStatusListener.ts +10 -9
- package/src/init/createSubscriptionInitModule.ts +4 -1
- package/src/shared/infrastructure/SubscriptionEventBus.ts +4 -1
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* Handles resolution of RevenueCat user ID to credits user ID
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
7
|
+
|
|
8
|
+
const logger = createLogger("UserIdResolver");
|
|
9
|
+
|
|
6
10
|
export class UserIdResolver {
|
|
7
11
|
constructor(private getAnonymousUserId: () => Promise<string>) {}
|
|
8
12
|
|
|
@@ -14,7 +18,7 @@ export class UserIdResolver {
|
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
// Fallback to anonymous user ID
|
|
17
|
-
|
|
21
|
+
logger.warn("revenueCatUserId is empty/null, falling back to anonymousUserId");
|
|
18
22
|
const anonymousId = await this.getAnonymousUserId();
|
|
19
23
|
const trimmedAnonymous = anonymousId?.trim();
|
|
20
24
|
|
package/src/domains/subscription/infrastructure/handlers/package-operations/PackageFetcher.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
2
2
|
import type { IRevenueCatService } from "../../../../../shared/application/ports/IRevenueCatService";
|
|
3
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
4
|
+
|
|
5
|
+
const logger = createLogger("PackageFetcher");
|
|
3
6
|
|
|
4
7
|
export async function fetchPackages(
|
|
5
8
|
service: IRevenueCatService
|
|
@@ -20,12 +23,10 @@ export async function fetchPackages(
|
|
|
20
23
|
return [];
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
}
|
|
26
|
+
logger.debug("Returning packages", {
|
|
27
|
+
count: packages.length,
|
|
28
|
+
packageIds: packages.map(p => p.product.identifier),
|
|
29
|
+
});
|
|
29
30
|
|
|
30
31
|
return packages;
|
|
31
32
|
} catch (error) {
|
package/src/domains/subscription/infrastructure/handlers/package-operations/PackagePurchaser.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
2
2
|
import type { IRevenueCatService } from "../../../../../shared/application/ports/IRevenueCatService";
|
|
3
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
4
|
+
|
|
5
|
+
const logger = createLogger("PackagePurchaser");
|
|
3
6
|
|
|
4
7
|
export async function executePurchase(
|
|
5
8
|
service: IRevenueCatService,
|
|
6
9
|
pkg: PurchasesPackage,
|
|
7
10
|
userId: string
|
|
8
11
|
): Promise<boolean> {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
});
|
|
15
|
-
}
|
|
12
|
+
logger.debug("executePurchase called", {
|
|
13
|
+
productId: pkg.product.identifier,
|
|
14
|
+
userId,
|
|
15
|
+
serviceInitialized: service.isInitialized(),
|
|
16
|
+
});
|
|
16
17
|
|
|
17
18
|
if (!service.isInitialized()) {
|
|
18
19
|
throw new Error("Service not initialized");
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
} from "@umituz/react-native-auth";
|
|
8
8
|
import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
|
|
9
9
|
import { getErrorMessage } from "../../../revenuecat/core/errors/RevenueCatErrorHandler";
|
|
10
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
11
|
+
|
|
12
|
+
const logger = createLogger("usePurchasePackage");
|
|
10
13
|
|
|
11
14
|
interface PurchaseMutationResult {
|
|
12
15
|
success: boolean;
|
|
@@ -24,9 +27,7 @@ export const usePurchasePackage = () => {
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const productId = pkg.product.identifier;
|
|
27
|
-
|
|
28
|
-
console.log(`[Purchase] Initializing and purchasing. User: ${userId}`);
|
|
29
|
-
}
|
|
30
|
+
logger.debug("Initializing and purchasing", { userId, productId });
|
|
30
31
|
|
|
31
32
|
const success = await SubscriptionManager.purchasePackage(pkg, userId);
|
|
32
33
|
|
|
@@ -10,6 +10,9 @@ import { getPackagesOperation, purchasePackageOperation, restoreOperation } from
|
|
|
10
10
|
import { performServiceInitialization } from "./initializationHandler";
|
|
11
11
|
import { initializationState } from "../state/initializationState";
|
|
12
12
|
import { ANONYMOUS_CACHE_KEY } from "../../core/SubscriptionConstants";
|
|
13
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
14
|
+
|
|
15
|
+
const logger = createLogger("SubscriptionManager");
|
|
13
16
|
|
|
14
17
|
class SubscriptionManagerImpl {
|
|
15
18
|
private managerConfig: SubscriptionManagerConfig | null = null;
|
|
@@ -23,14 +26,14 @@ class SubscriptionManagerImpl {
|
|
|
23
26
|
|
|
24
27
|
private ensureConfigured(): void {
|
|
25
28
|
if (!this.managerConfig) {
|
|
26
|
-
throw new Error('
|
|
29
|
+
throw new Error('SubscriptionManager: Not configured. Call configure() first.');
|
|
27
30
|
}
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
private ensurePackageHandlerInitialized(): void {
|
|
31
34
|
if (this.packageHandler) return;
|
|
32
35
|
if (!this.serviceInstance || !this.managerConfig) {
|
|
33
|
-
throw new Error('
|
|
36
|
+
throw new Error('SubscriptionManager: Cannot create package handler without service and config');
|
|
34
37
|
}
|
|
35
38
|
this.packageHandler = createPackageHandler(this.serviceInstance, this.managerConfig);
|
|
36
39
|
}
|
|
@@ -40,20 +43,16 @@ class SubscriptionManagerImpl {
|
|
|
40
43
|
|
|
41
44
|
const actualUserId: string = (userId && userId.length > 0) ? userId : '';
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
}
|
|
46
|
+
logger.debug("initialize called", {
|
|
47
|
+
providedUserId: userId,
|
|
48
|
+
actualUserId: actualUserId || '(empty - RevenueCat will generate anonymous ID)',
|
|
49
|
+
});
|
|
49
50
|
|
|
50
51
|
const cacheKey = actualUserId || ANONYMOUS_CACHE_KEY;
|
|
51
52
|
const { shouldInit, existingPromise } = this.initCache.tryAcquireInitialization(cacheKey);
|
|
52
53
|
|
|
53
54
|
if (!shouldInit && existingPromise) {
|
|
54
|
-
|
|
55
|
-
console.log('[SubscriptionManager] Using cached initialization for:', cacheKey);
|
|
56
|
-
}
|
|
55
|
+
logger.debug("Using cached initialization for", cacheKey);
|
|
57
56
|
return existingPromise;
|
|
58
57
|
}
|
|
59
58
|
|
|
@@ -67,11 +66,9 @@ class SubscriptionManagerImpl {
|
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
private async performInitialization(userId: string): Promise<boolean> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
});
|
|
74
|
-
}
|
|
69
|
+
logger.debug("performInitialization", {
|
|
70
|
+
userId: userId || '(empty - anonymous)',
|
|
71
|
+
});
|
|
75
72
|
|
|
76
73
|
const config = this.managerConfig!;
|
|
77
74
|
const { service, success } = await performServiceInitialization(config.config, userId);
|
|
@@ -83,9 +80,7 @@ class SubscriptionManagerImpl {
|
|
|
83
80
|
initializationState.markInitialized(notifyUserId);
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
console.log('[SubscriptionManager] Initialization completed:', { success });
|
|
88
|
-
}
|
|
83
|
+
logger.debug("Initialization completed", { success });
|
|
89
84
|
|
|
90
85
|
return success;
|
|
91
86
|
}
|
|
@@ -107,14 +102,12 @@ class SubscriptionManagerImpl {
|
|
|
107
102
|
if (explicitUserId) {
|
|
108
103
|
await this.initialize(explicitUserId);
|
|
109
104
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
}
|
|
105
|
+
logger.debug("purchasePackage: init complete, starting purchase", {
|
|
106
|
+
productId: pkg.product.identifier,
|
|
107
|
+
hasPackageHandler: !!this.packageHandler,
|
|
108
|
+
hasService: !!this.serviceInstance,
|
|
109
|
+
serviceInitialized: this.serviceInstance?.isInitialized() ?? false,
|
|
110
|
+
});
|
|
118
111
|
this.ensurePackageHandlerInitialized();
|
|
119
112
|
const resolvedUserId = explicitUserId || getCurrentUserIdOrThrow(this.initCache);
|
|
120
113
|
const handler = this.packageHandler!;
|
|
@@ -2,6 +2,9 @@ import Purchases, { type CustomerInfo } from "react-native-purchases";
|
|
|
2
2
|
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
3
3
|
import { ListenerState } from "./listeners/ListenerState";
|
|
4
4
|
import { processCustomerInfo } from "./listeners/CustomerInfoHandler";
|
|
5
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
6
|
+
|
|
7
|
+
const logger = createLogger("CustomerInfoListenerManager");
|
|
5
8
|
|
|
6
9
|
export class CustomerInfoListenerManager {
|
|
7
10
|
private state = new ListenerState();
|
|
@@ -33,7 +36,7 @@ export class CustomerInfoListenerManager {
|
|
|
33
36
|
this._createAndAttachListener(config);
|
|
34
37
|
return true;
|
|
35
38
|
} catch (error) {
|
|
36
|
-
|
|
39
|
+
logger.error("Failed to setup listener", error);
|
|
37
40
|
this.state.currentUserId = null;
|
|
38
41
|
return false;
|
|
39
42
|
}
|
|
@@ -41,13 +44,11 @@ export class CustomerInfoListenerManager {
|
|
|
41
44
|
|
|
42
45
|
private _createAndAttachListener(config: RevenueCatConfig): void {
|
|
43
46
|
this.state.listener = async (customerInfo: CustomerInfo) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
50
|
-
}
|
|
47
|
+
logger.debug("LISTENER TRIGGERED", {
|
|
48
|
+
userId: this.state.currentUserId,
|
|
49
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
50
|
+
entitlementsCount: Object.keys(customerInfo.entitlements.all).length,
|
|
51
|
+
});
|
|
51
52
|
|
|
52
53
|
const capturedUserId = this.state.currentUserId;
|
|
53
54
|
if (!capturedUserId) {
|
|
@@ -67,7 +68,7 @@ export class CustomerInfoListenerManager {
|
|
|
67
68
|
}
|
|
68
69
|
// else: User switched during async operation, discard stale renewal state
|
|
69
70
|
} catch (error) {
|
|
70
|
-
|
|
71
|
+
logger.error("processCustomerInfo failed", error);
|
|
71
72
|
}
|
|
72
73
|
};
|
|
73
74
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import Purchases, { type PurchasesOffering } from "react-native-purchases";
|
|
2
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
3
|
+
|
|
4
|
+
const logger = createLogger("OfferingsFetcher");
|
|
2
5
|
|
|
3
6
|
interface OfferingsFetcherDeps {
|
|
4
7
|
isInitialized: () => boolean;
|
|
@@ -14,15 +17,13 @@ export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<Purcha
|
|
|
14
17
|
try {
|
|
15
18
|
const offerings = await Purchases.getOfferings();
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
}
|
|
20
|
+
logger.debug("Offerings received", {
|
|
21
|
+
attempt,
|
|
22
|
+
hasCurrent: !!offerings.current,
|
|
23
|
+
currentId: offerings.current?.identifier,
|
|
24
|
+
allOfferingsCount: Object.keys(offerings.all).length,
|
|
25
|
+
allOfferingIds: Object.keys(offerings.all),
|
|
26
|
+
});
|
|
26
27
|
|
|
27
28
|
if (offerings.current) {
|
|
28
29
|
return offerings.current;
|
|
@@ -35,28 +36,20 @@ export async function fetchOfferings(deps: OfferingsFetcherDeps): Promise<Purcha
|
|
|
35
36
|
|
|
36
37
|
// No offerings found - retry after delay (RevenueCat may still be syncing)
|
|
37
38
|
if (attempt < MAX_FETCH_RETRIES) {
|
|
38
|
-
|
|
39
|
-
console.log('[OfferingsFetcher] No offerings found, retrying...', { attempt: attempt + 1 });
|
|
40
|
-
}
|
|
39
|
+
logger.debug("No offerings found, retrying...", { attempt: attempt + 1 });
|
|
41
40
|
await new Promise<void>(resolve => setTimeout(resolve, FETCH_RETRY_DELAY_MS));
|
|
42
41
|
continue;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
console.warn('[OfferingsFetcher] No offerings found after all retries');
|
|
47
|
-
}
|
|
44
|
+
logger.warn("No offerings found after all retries");
|
|
48
45
|
return null;
|
|
49
46
|
} catch (error) {
|
|
50
47
|
if (attempt < MAX_FETCH_RETRIES) {
|
|
51
|
-
|
|
52
|
-
console.warn('[OfferingsFetcher] Fetch failed, retrying...', { attempt: attempt + 1, error });
|
|
53
|
-
}
|
|
48
|
+
logger.warn("Fetch failed, retrying...", error, { attempt: attempt + 1 });
|
|
54
49
|
await new Promise<void>(resolve => setTimeout(resolve, FETCH_RETRY_DELAY_MS));
|
|
55
50
|
continue;
|
|
56
51
|
}
|
|
57
|
-
|
|
58
|
-
console.warn('[OfferingsFetcher] Failed to fetch offerings after all retries:', error);
|
|
59
|
-
}
|
|
52
|
+
logger.warn("Failed to fetch offerings after all retries", error);
|
|
60
53
|
return null;
|
|
61
54
|
}
|
|
62
55
|
}
|
|
@@ -5,6 +5,9 @@ import { isUserCancelledError, isAlreadyPurchasedError } from "../../../revenuec
|
|
|
5
5
|
import { validatePurchaseReady, isConsumableProduct } from "./purchase/PurchaseValidator";
|
|
6
6
|
import { executePurchase } from "./purchase/PurchaseExecutor";
|
|
7
7
|
import { handleAlreadyPurchasedError, handlePurchaseError } from "./purchase/PurchaseErrorHandler";
|
|
8
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
9
|
+
|
|
10
|
+
const logger = createLogger("PurchaseHandler");
|
|
8
11
|
|
|
9
12
|
export interface PurchaseHandlerDeps {
|
|
10
13
|
config: RevenueCatConfig;
|
|
@@ -21,13 +24,11 @@ export async function handlePurchase(
|
|
|
21
24
|
const consumableIds = deps.config.consumableProductIdentifiers || [];
|
|
22
25
|
const isConsumable = isConsumableProduct(pkg, consumableIds);
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
30
|
-
}
|
|
27
|
+
logger.debug("handlePurchase", {
|
|
28
|
+
productId: pkg.product.identifier,
|
|
29
|
+
userId,
|
|
30
|
+
isConsumable,
|
|
31
|
+
});
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
34
|
const result = await executePurchase(deps.config, userId, pkg, isConsumable);
|
|
@@ -8,6 +8,9 @@ import { handlePurchase } from "./PurchaseHandler";
|
|
|
8
8
|
import { handleRestore } from "./RestoreHandler";
|
|
9
9
|
import { CustomerInfoListenerManager } from "./CustomerInfoListenerManager";
|
|
10
10
|
import { ServiceStateManager } from "./ServiceStateManager";
|
|
11
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
12
|
+
|
|
13
|
+
const logger = createLogger("RevenueCatService");
|
|
11
14
|
|
|
12
15
|
export class RevenueCatService implements IRevenueCatService {
|
|
13
16
|
private stateManager: ServiceStateManager;
|
|
@@ -83,9 +86,7 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
83
86
|
try {
|
|
84
87
|
await Purchases.logOut();
|
|
85
88
|
} catch (error) {
|
|
86
|
-
|
|
87
|
-
console.warn('[RevenueCatService] logOut failed during reset', { error });
|
|
88
|
-
}
|
|
89
|
+
logger.warn("logOut failed during reset", error);
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -4,6 +4,9 @@ import { syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
|
4
4
|
import { detectRenewal } from "../../utils/renewal/RenewalDetector";
|
|
5
5
|
import { updateRenewalState } from "../../utils/renewal/RenewalStateUpdater";
|
|
6
6
|
import type { RenewalState } from "../../utils/renewal/types";
|
|
7
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
8
|
+
|
|
9
|
+
const logger = createLogger("CustomerInfoHandler");
|
|
7
10
|
|
|
8
11
|
async function handleRenewal(
|
|
9
12
|
userId: string,
|
|
@@ -17,11 +20,7 @@ async function handleRenewal(
|
|
|
17
20
|
try {
|
|
18
21
|
await onRenewalDetected({ userId, productId, newExpirationDate: expirationDate, customerInfo });
|
|
19
22
|
} catch (error) {
|
|
20
|
-
|
|
21
|
-
userId,
|
|
22
|
-
productId,
|
|
23
|
-
error: error instanceof Error ? error.message : String(error)
|
|
24
|
-
});
|
|
23
|
+
logger.error("Renewal callback failed", error, { userId, productId });
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -38,13 +37,7 @@ async function handlePlanChange(
|
|
|
38
37
|
try {
|
|
39
38
|
await onPlanChanged({ userId, newProductId, previousProductId, isUpgrade, customerInfo });
|
|
40
39
|
} catch (error) {
|
|
41
|
-
|
|
42
|
-
userId,
|
|
43
|
-
newProductId,
|
|
44
|
-
previousProductId,
|
|
45
|
-
isUpgrade,
|
|
46
|
-
error: error instanceof Error ? error.message : String(error)
|
|
47
|
-
});
|
|
40
|
+
logger.error("Plan change callback failed", error, { userId, newProductId, previousProductId, isUpgrade });
|
|
48
41
|
}
|
|
49
42
|
}
|
|
50
43
|
|
|
@@ -56,10 +49,7 @@ async function handlePremiumStatusSync(
|
|
|
56
49
|
try {
|
|
57
50
|
await syncPremiumStatus(config, userId, customerInfo);
|
|
58
51
|
} catch (error) {
|
|
59
|
-
|
|
60
|
-
userId,
|
|
61
|
-
error: error instanceof Error ? error.message : String(error)
|
|
62
|
-
});
|
|
52
|
+
logger.error("Premium status sync failed", error, { userId });
|
|
63
53
|
}
|
|
64
54
|
}
|
|
65
55
|
|
|
@@ -69,14 +59,12 @@ export async function processCustomerInfo(
|
|
|
69
59
|
renewalState: RenewalState,
|
|
70
60
|
config: RevenueCatConfig
|
|
71
61
|
): Promise<RenewalState> {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
}
|
|
62
|
+
logger.debug("processCustomerInfo called", {
|
|
63
|
+
userId,
|
|
64
|
+
renewalState,
|
|
65
|
+
entitlementId: config.entitlementIdentifier,
|
|
66
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
67
|
+
});
|
|
80
68
|
|
|
81
69
|
const renewalResult = detectRenewal(
|
|
82
70
|
renewalState,
|
|
@@ -86,7 +74,7 @@ export async function processCustomerInfo(
|
|
|
86
74
|
|
|
87
75
|
if (renewalResult.isRenewal) {
|
|
88
76
|
if (!renewalResult.productId || !renewalResult.newExpirationDate) {
|
|
89
|
-
|
|
77
|
+
logger.error("Invalid renewal state: missing productId or expirationDate");
|
|
90
78
|
return renewalState;
|
|
91
79
|
}
|
|
92
80
|
await handleRenewal(
|
|
@@ -100,7 +88,7 @@ export async function processCustomerInfo(
|
|
|
100
88
|
|
|
101
89
|
if (renewalResult.isPlanChange) {
|
|
102
90
|
if (!renewalResult.productId || !renewalResult.previousProductId) {
|
|
103
|
-
|
|
91
|
+
logger.error("Invalid plan change state: missing productId(s)");
|
|
104
92
|
return renewalState;
|
|
105
93
|
}
|
|
106
94
|
await handlePlanChange(
|
|
@@ -114,9 +102,7 @@ export async function processCustomerInfo(
|
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
if (!renewalResult.isRenewal && !renewalResult.isPlanChange) {
|
|
117
|
-
|
|
118
|
-
console.log("[CustomerInfoHandler] Handling premium status sync (new purchase or status update)");
|
|
119
|
-
}
|
|
105
|
+
logger.debug("Handling premium status sync (new purchase or status update)");
|
|
120
106
|
await handlePremiumStatusSync(config, userId, customerInfo);
|
|
121
107
|
}
|
|
122
108
|
|
|
@@ -15,6 +15,9 @@ import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useA
|
|
|
15
15
|
import { notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
|
|
16
16
|
import { handleRestore } from "../RestoreHandler";
|
|
17
17
|
import type { PurchaseHandlerDeps } from "../PurchaseHandler";
|
|
18
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
19
|
+
|
|
20
|
+
const logger = createLogger("PurchaseErrorHandler");
|
|
18
21
|
|
|
19
22
|
export async function handleAlreadyPurchasedError(
|
|
20
23
|
deps: PurchaseHandlerDeps,
|
|
@@ -85,11 +88,10 @@ export function handlePurchaseError(
|
|
|
85
88
|
? `${errorMessage} (Code: ${errorCode})`
|
|
86
89
|
: errorMessage;
|
|
87
90
|
|
|
88
|
-
|
|
91
|
+
logger.error("Purchase failed", error, {
|
|
89
92
|
productId: pkg.product.identifier,
|
|
90
93
|
userId,
|
|
91
94
|
errorCode,
|
|
92
|
-
error,
|
|
93
95
|
});
|
|
94
96
|
|
|
95
97
|
throw new RevenueCatPurchaseError(
|
|
@@ -4,13 +4,16 @@ import type { RevenueCatConfig } from "../../../../revenuecat/core/types/Revenue
|
|
|
4
4
|
import type { PackageType } from "../../../../revenuecat/core/types/RevenueCatTypes";
|
|
5
5
|
import { notifyPurchaseCompleted, syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
6
6
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
|
|
7
|
+
import { createLogger } from "../../../../../shared/utils/logger";
|
|
8
|
+
|
|
9
|
+
const logger = createLogger("PurchaseExecutor");
|
|
7
10
|
|
|
8
11
|
async function attemptRecovery(config: RevenueCatConfig, userId: string, customerInfo: CustomerInfo): Promise<void> {
|
|
9
12
|
try {
|
|
10
|
-
|
|
13
|
+
logger.warn("Attempting recovery via syncPremiumStatus...");
|
|
11
14
|
await syncPremiumStatus(config, userId, customerInfo);
|
|
12
15
|
} catch (recoveryError) {
|
|
13
|
-
|
|
16
|
+
logger.error("Recovery also failed", recoveryError);
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
|
|
@@ -27,7 +30,7 @@ async function executeConsumablePurchase(
|
|
|
27
30
|
try {
|
|
28
31
|
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
29
32
|
} catch (syncError) {
|
|
30
|
-
|
|
33
|
+
logger.error("Post-purchase sync failed, attempting recovery", syncError);
|
|
31
34
|
await attemptRecovery(config, userId, customerInfo);
|
|
32
35
|
} finally {
|
|
33
36
|
if (savedPurchase) {
|
|
@@ -56,34 +59,29 @@ async function executeSubscriptionPurchase(
|
|
|
56
59
|
const savedPurchase = getSavedPurchase();
|
|
57
60
|
const source = savedPurchase?.source;
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
logger.debug("executeSubscriptionPurchase: START", {
|
|
63
|
+
userId,
|
|
64
|
+
productId,
|
|
65
|
+
isPremium,
|
|
66
|
+
entitlementIdentifier,
|
|
67
|
+
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
68
|
+
source,
|
|
69
|
+
packageType,
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
75
|
+
logger.debug("executeSubscriptionPurchase: SUCCESS", {
|
|
61
76
|
userId,
|
|
62
77
|
productId,
|
|
63
78
|
isPremium,
|
|
64
|
-
entitlementIdentifier,
|
|
65
|
-
activeEntitlements: Object.keys(customerInfo.entitlements.active),
|
|
66
|
-
source,
|
|
67
|
-
packageType,
|
|
68
79
|
timestamp: new Date().toISOString(),
|
|
69
80
|
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
74
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
75
|
-
console.log("[PurchaseExecutor] 🟢 executeSubscriptionPurchase: SUCCESS", {
|
|
76
|
-
userId,
|
|
77
|
-
productId,
|
|
78
|
-
isPremium,
|
|
79
|
-
timestamp: new Date().toISOString(),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
81
|
} catch (syncError) {
|
|
83
|
-
|
|
82
|
+
logger.error("Post-purchase sync failed, attempting recovery", syncError, {
|
|
84
83
|
userId,
|
|
85
84
|
productId,
|
|
86
|
-
error: syncError instanceof Error ? syncError.message : String(syncError),
|
|
87
85
|
timestamp: new Date().toISOString(),
|
|
88
86
|
});
|
|
89
87
|
await attemptRecovery(config, userId, customerInfo);
|
|
@@ -122,13 +120,11 @@ export async function executePurchase(
|
|
|
122
120
|
pkg: PurchasesPackage,
|
|
123
121
|
isConsumable: boolean
|
|
124
122
|
): Promise<PurchaseResult> {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
131
|
-
}
|
|
123
|
+
logger.debug("Starting Purchases.purchasePackage", {
|
|
124
|
+
productId: pkg.product.identifier,
|
|
125
|
+
userId,
|
|
126
|
+
isConsumable,
|
|
127
|
+
});
|
|
132
128
|
|
|
133
129
|
const { customerInfo } = await withTimeout(
|
|
134
130
|
Purchases.purchasePackage(pkg),
|
|
@@ -136,9 +132,7 @@ export async function executePurchase(
|
|
|
136
132
|
`Purchases.purchasePackage(${pkg.product.identifier})`
|
|
137
133
|
);
|
|
138
134
|
|
|
139
|
-
|
|
140
|
-
console.log("[PurchaseExecutor] Purchases.purchasePackage completed successfully");
|
|
141
|
-
}
|
|
135
|
+
logger.debug("Purchases.purchasePackage completed successfully");
|
|
142
136
|
|
|
143
137
|
const productId = pkg.product.identifier;
|
|
144
138
|
const packageType = pkg.packageType ?? null;
|
|
@@ -4,6 +4,10 @@ interface CacheEntry {
|
|
|
4
4
|
completed: boolean;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
import { createLogger } from "../../../../shared/utils/logger";
|
|
8
|
+
|
|
9
|
+
const logger = createLogger("InitializationCache");
|
|
10
|
+
|
|
7
11
|
export class InitializationCache {
|
|
8
12
|
private entries: Map<string, CacheEntry> = new Map();
|
|
9
13
|
|
|
@@ -38,7 +42,7 @@ export class InitializationCache {
|
|
|
38
42
|
if (this.entries.get(cacheKey) === entry) {
|
|
39
43
|
this.entries.delete(cacheKey);
|
|
40
44
|
}
|
|
41
|
-
|
|
45
|
+
logger.error("Initialization failed", error, { cacheKey });
|
|
42
46
|
return false;
|
|
43
47
|
});
|
|
44
48
|
|