@umituz/react-native-subscription 2.39.8 → 2.39.10
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 +6 -17
- package/src/domains/credits/core/UserCreditsDocument.ts +1 -1
- package/src/domains/credits/infrastructure/CreditsRepository.ts +3 -3
- package/src/domains/credits/infrastructure/operations/CreditsInitializer.ts +1 -1
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +1 -1
- package/src/domains/paywall/components/PaywallFeatures.tsx +1 -1
- package/src/domains/paywall/components/PaywallFooter.tsx +1 -1
- package/src/domains/paywall/components/PaywallScreen.styles.ts +116 -44
- package/src/domains/paywall/components/PaywallScreen.tsx +184 -138
- package/src/domains/paywall/entities/types.ts +2 -0
- package/src/domains/paywall/hooks/usePaywallActions.ts +32 -40
- package/src/domains/paywall/utils/paywallLayoutUtils.ts +55 -0
- package/src/domains/revenuecat/core/types/RevenueCatData.ts +1 -1
- package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +2 -2
- package/src/domains/revenuecat/infrastructure/services/RevenueCatInitializer.types.ts +1 -1
- package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +1 -1
- package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +1 -1
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +5 -22
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
- package/src/domains/subscription/application/featureGate/featureGateBusinessRules.ts +27 -10
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +42 -41
- package/src/domains/subscription/core/SubscriptionEvents.ts +1 -1
- package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +1 -5
- package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +4 -6
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +2 -2
- package/src/domains/subscription/infrastructure/handlers/package-operations/PackageRestorer.ts +1 -1
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +1 -1
- package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +1 -1
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +2 -2
- package/src/domains/subscription/infrastructure/managers/initializationHandler.ts +1 -1
- package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +1 -1
- package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +2 -2
- package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +4 -4
- package/src/domains/subscription/infrastructure/services/RevenueCatService.types.ts +1 -1
- package/src/domains/subscription/infrastructure/services/ServiceStateManager.ts +1 -1
- package/src/domains/subscription/infrastructure/services/listeners/CustomerInfoHandler.ts +4 -2
- package/src/domains/subscription/infrastructure/services/listeners/ListenerState.ts +1 -1
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseErrorHandler.ts +3 -3
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +2 -1
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseValidator.ts +1 -1
- package/src/domains/subscription/infrastructure/services/revenueCatServiceInstance.ts +1 -1
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +35 -42
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -3
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +1 -1
- package/src/domains/subscription/presentation/components/sections/SubscriptionSection.types.ts +1 -1
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -1
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +1 -1
- package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +11 -8
- package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +1 -1
- package/src/domains/subscription/utils/featureGateUtils.ts +37 -0
- package/src/domains/subscription/utils/packageTypeFormatter.ts +1 -1
- package/src/domains/wallet/infrastructure/repositories/transaction/CollectionBuilder.ts +1 -1
- package/src/domains/wallet/infrastructure/repositories/transaction/TransactionFetcher.ts +2 -1
- package/src/domains/wallet/infrastructure/repositories/transaction/TransactionWriter.ts +2 -1
- package/src/domains/wallet/presentation/screens/WalletScreen.tsx +1 -1
- package/src/index.ts +5 -2
- package/src/init/createSubscriptionInitModule.ts +2 -1
- package/src/shared/infrastructure/SubscriptionEventBus.ts +24 -18
- package/src/domains/revenuecat/core/errors/index.ts +0 -3
- package/src/domains/revenuecat/core/types/index.ts +0 -3
- package/src/domains/subscription/application/initializer/index.ts +0 -2
- package/src/domains/subscription/core/types/index.ts +0 -3
- package/src/domains/subscription/infrastructure/handlers/package-operations/index.ts +0 -4
- package/src/domains/subscription/infrastructure/utils/renewal/index.ts +0 -3
- package/src/shared/infrastructure/firestore/index.ts +0 -2
- package/src/shared/presentation/index.ts +0 -1
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import Purchases from "react-native-purchases";
|
|
2
1
|
import { PURCHASE_SOURCE, PURCHASE_TYPE } from "../core/SubscriptionConstants";
|
|
3
2
|
import type { PremiumStatusChangedEvent, PurchaseCompletedEvent, RenewalDetectedEvent } from "../core/SubscriptionEvents";
|
|
4
3
|
import { getCreditsRepository } from "../../credits/infrastructure/CreditsRepositoryManager";
|
|
@@ -80,14 +79,6 @@ export class SubscriptionSyncProcessor {
|
|
|
80
79
|
|
|
81
80
|
// ─── Internal Processing ──────────────────────────────────────────
|
|
82
81
|
|
|
83
|
-
private async getRevenueCatAppUserId(): Promise<string | null> {
|
|
84
|
-
try {
|
|
85
|
-
return await Purchases.getAppUserID();
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
82
|
private async getCreditsUserId(revenueCatUserId: string | null | undefined): Promise<string> {
|
|
92
83
|
const trimmed = revenueCatUserId?.trim();
|
|
93
84
|
if (trimmed && trimmed.length > 0 && trimmed !== 'undefined' && trimmed !== 'null') {
|
|
@@ -108,8 +99,8 @@ export class SubscriptionSyncProcessor {
|
|
|
108
99
|
try {
|
|
109
100
|
const revenueCatData = extractRevenueCatData(event.customerInfo, this.entitlementId);
|
|
110
101
|
revenueCatData.packageType = event.packageType ?? null;
|
|
111
|
-
|
|
112
|
-
revenueCatData.revenueCatUserId =
|
|
102
|
+
// Use the event.userId instead of polling the SDK to avoid race conditions during rapid user switching
|
|
103
|
+
revenueCatData.revenueCatUserId = event.userId;
|
|
113
104
|
const purchaseId = generatePurchaseId(revenueCatData.storeTransactionId, event.productId);
|
|
114
105
|
|
|
115
106
|
const creditsUserId = await this.getCreditsUserId(event.userId);
|
|
@@ -138,8 +129,8 @@ export class SubscriptionSyncProcessor {
|
|
|
138
129
|
try {
|
|
139
130
|
const revenueCatData = extractRevenueCatData(event.customerInfo, this.entitlementId);
|
|
140
131
|
revenueCatData.expirationDate = event.newExpirationDate ?? revenueCatData.expirationDate;
|
|
141
|
-
|
|
142
|
-
revenueCatData.revenueCatUserId =
|
|
132
|
+
// Use the event.userId instead of polling the SDK to avoid race conditions during rapid user switching
|
|
133
|
+
revenueCatData.revenueCatUserId = event.userId;
|
|
143
134
|
const purchaseId = generateRenewalId(revenueCatData.storeTransactionId, event.productId, event.newExpirationDate);
|
|
144
135
|
|
|
145
136
|
const creditsUserId = await this.getCreditsUserId(event.userId);
|
|
@@ -164,9 +155,6 @@ export class SubscriptionSyncProcessor {
|
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
private async processStatusChange(event: PremiumStatusChangedEvent): Promise<void> {
|
|
167
|
-
// If a purchase is in progress, skip metadata sync (purchase handler does it)
|
|
168
|
-
// but still allow recovery to run — the purchase handler's credit initialization
|
|
169
|
-
// might have failed, and this is the safety net.
|
|
170
158
|
if (this.purchaseInProgress) {
|
|
171
159
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
172
160
|
console.log("[SubscriptionSyncProcessor] Purchase in progress - running recovery only");
|
|
@@ -186,9 +174,6 @@ export class SubscriptionSyncProcessor {
|
|
|
186
174
|
}
|
|
187
175
|
|
|
188
176
|
if (!event.isPremium && !event.productId) {
|
|
189
|
-
// No entitlement and no productId — could be:
|
|
190
|
-
// 1. Free user who never purchased (no credits doc) → skip
|
|
191
|
-
// 2. Previously premium user whose entitlement was removed → expire
|
|
192
177
|
const hasDoc = await getCreditsRepository().creditsDocumentExists(creditsUserId);
|
|
193
178
|
if (hasDoc) {
|
|
194
179
|
await this.expireSubscription(creditsUserId);
|
|
@@ -203,7 +188,7 @@ export class SubscriptionSyncProcessor {
|
|
|
203
188
|
await this.syncPremiumStatus(creditsUserId, event);
|
|
204
189
|
}
|
|
205
190
|
|
|
206
|
-
// ─── Credit Document Operations
|
|
191
|
+
// ─── Credit Document Operations ───
|
|
207
192
|
|
|
208
193
|
private async expireSubscription(userId: string): Promise<void> {
|
|
209
194
|
await getCreditsRepository().syncExpiredStatus(userId);
|
|
@@ -213,8 +198,6 @@ export class SubscriptionSyncProcessor {
|
|
|
213
198
|
private async syncPremiumStatus(userId: string, event: PremiumStatusChangedEvent): Promise<void> {
|
|
214
199
|
const repo = getCreditsRepository();
|
|
215
200
|
|
|
216
|
-
// Recovery: if premium user has no credits document, create one.
|
|
217
|
-
// Handles edge cases like test store, reinstalls, or failed purchase initialization.
|
|
218
201
|
if (event.isPremium) {
|
|
219
202
|
const created = await repo.ensurePremiumCreditsExist(
|
|
220
203
|
userId,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
|
-
import type { RevenueCatData } from "../../revenuecat/core/types";
|
|
2
|
+
import type { RevenueCatData } from "../../revenuecat/core/types/RevenueCatData";
|
|
3
3
|
import { PERIOD_TYPE, type PeriodType } from "../core/SubscriptionConstants";
|
|
4
4
|
|
|
5
5
|
function validatePeriodType(periodType: string | undefined): PeriodType | null {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canExecuteAuthAction as canAuth,
|
|
3
|
+
canExecutePurchaseAction as canPurchase
|
|
4
|
+
} from "../../utils/featureGateUtils";
|
|
5
|
+
|
|
1
6
|
export const DEFAULT_REQUIRED_CREDITS = 1;
|
|
2
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Business rule for executing auth-related actions.
|
|
10
|
+
*/
|
|
3
11
|
export function canExecuteAuthAction(
|
|
4
12
|
isWaitingForAuthCredits: boolean,
|
|
5
13
|
isCreditsLoaded: boolean,
|
|
@@ -8,12 +16,19 @@ export function canExecuteAuthAction(
|
|
|
8
16
|
creditBalance: number,
|
|
9
17
|
requiredCredits: number
|
|
10
18
|
): boolean {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
return canAuth(
|
|
20
|
+
isWaitingForAuthCredits,
|
|
21
|
+
isCreditsLoaded,
|
|
22
|
+
hasPendingAction,
|
|
23
|
+
hasSubscription,
|
|
24
|
+
creditBalance,
|
|
25
|
+
requiredCredits
|
|
26
|
+
);
|
|
15
27
|
}
|
|
16
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Business rule for executing purchase-related actions.
|
|
31
|
+
*/
|
|
17
32
|
export function canExecutePurchaseAction(
|
|
18
33
|
isWaitingForPurchase: boolean,
|
|
19
34
|
creditBalance: number,
|
|
@@ -22,10 +37,12 @@ export function canExecutePurchaseAction(
|
|
|
22
37
|
prevHasSubscription: boolean,
|
|
23
38
|
hasPendingAction: boolean
|
|
24
39
|
): boolean {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
return canPurchase(
|
|
41
|
+
isWaitingForPurchase,
|
|
42
|
+
creditBalance,
|
|
43
|
+
prevBalance,
|
|
44
|
+
hasSubscription,
|
|
45
|
+
prevHasSubscription,
|
|
46
|
+
hasPendingAction
|
|
47
|
+
);
|
|
31
48
|
}
|
|
@@ -2,16 +2,18 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
|
|
|
2
2
|
import { getCurrentUserId, setupAuthStateListener } from "../SubscriptionAuthListener";
|
|
3
3
|
import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
|
|
4
4
|
|
|
5
|
-
const AUTH_STATE_DEBOUNCE_MS = 500;
|
|
5
|
+
const AUTH_STATE_DEBOUNCE_MS = 500;
|
|
6
6
|
const MAX_RETRY_ATTEMPTS = 3;
|
|
7
7
|
const RETRY_DELAY_MS = 2000;
|
|
8
8
|
|
|
9
9
|
export async function startBackgroundInitialization(config: SubscriptionInitConfig): Promise<() => void> {
|
|
10
10
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
11
11
|
let retryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
// Track the ID of the current initialization sequence to abort stale retries/state updates
|
|
14
|
+
let currentSequenceId = 0;
|
|
13
15
|
let lastInitSucceeded = false;
|
|
14
|
-
let
|
|
16
|
+
let lastUserId: string | undefined = undefined;
|
|
15
17
|
|
|
16
18
|
const initializeInBackground = async (revenueCatUserId?: string): Promise<void> => {
|
|
17
19
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
@@ -20,20 +22,23 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
20
22
|
await SubscriptionManager.initialize(revenueCatUserId);
|
|
21
23
|
};
|
|
22
24
|
|
|
23
|
-
const attemptInitWithRetry = async (revenueCatUserId
|
|
24
|
-
// Abort if
|
|
25
|
-
if (
|
|
25
|
+
const attemptInitWithRetry = async (revenueCatUserId: string | undefined, attempt: number, sequenceId: number): Promise<void> => {
|
|
26
|
+
// Abort if this is no longer the active sequence (e.g., user changed)
|
|
27
|
+
if (sequenceId !== currentSequenceId) {
|
|
26
28
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
27
|
-
console.log('[BackgroundInitializer] Aborting retry -
|
|
29
|
+
console.log('[BackgroundInitializer] Aborting retry - sequence changed');
|
|
28
30
|
}
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
try {
|
|
33
35
|
await initializeInBackground(revenueCatUserId);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
if (sequenceId === currentSequenceId) {
|
|
37
|
+
lastInitSucceeded = true;
|
|
38
|
+
}
|
|
36
39
|
} catch (error) {
|
|
40
|
+
if (sequenceId !== currentSequenceId) return;
|
|
41
|
+
|
|
37
42
|
lastInitSucceeded = false;
|
|
38
43
|
console.error('[BackgroundInitializer] Initialization failed:', {
|
|
39
44
|
userId: revenueCatUserId,
|
|
@@ -47,12 +52,11 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
47
52
|
console.log('[BackgroundInitializer] Scheduling retry', { attempt: attempt + 2 });
|
|
48
53
|
}
|
|
49
54
|
retryTimer = setTimeout(() => {
|
|
50
|
-
|
|
55
|
+
// Fire and forget promise, but safe because of sequenceId check
|
|
56
|
+
attemptInitWithRetry(revenueCatUserId, attempt + 1, sequenceId).catch(err => {
|
|
57
|
+
console.error('[BackgroundInitializer] Retry failed unhandled:', err);
|
|
58
|
+
});
|
|
51
59
|
}, RETRY_DELAY_MS * (attempt + 1));
|
|
52
|
-
} else {
|
|
53
|
-
// After all retries failed, set lastUserId so we don't block
|
|
54
|
-
// but mark as failed so next auth change can retry
|
|
55
|
-
lastUserId = revenueCatUserId;
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
};
|
|
@@ -66,7 +70,7 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
66
70
|
retryTimer = null;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
if (lastUserId === revenueCatUserId &&
|
|
73
|
+
if (lastUserId === revenueCatUserId && lastInitSucceeded) {
|
|
70
74
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
71
75
|
console.log('[BackgroundInitializer] UserId unchanged and init succeeded, skipping');
|
|
72
76
|
}
|
|
@@ -74,8 +78,10 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
debounceTimer = setTimeout(async () => {
|
|
77
|
-
//
|
|
78
|
-
|
|
81
|
+
// Start a new sequence
|
|
82
|
+
currentSequenceId++;
|
|
83
|
+
const sequenceId = currentSequenceId;
|
|
84
|
+
|
|
79
85
|
if (!revenueCatUserId && !lastUserId) {
|
|
80
86
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
81
87
|
console.log('[BackgroundInitializer] No user and no previous user, waiting for auth');
|
|
@@ -87,18 +93,19 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
87
93
|
console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', revenueCatUserId || '(undefined - anonymous)');
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
// Important: Always reset on user change, not just on logout.
|
|
97
|
+
// This ensures previous user's cached state is cleared before init.
|
|
98
|
+
if (lastUserId !== revenueCatUserId) {
|
|
99
|
+
await SubscriptionManager.reset();
|
|
100
|
+
lastInitSucceeded = false;
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
103
|
+
lastUserId = revenueCatUserId;
|
|
104
|
+
|
|
105
|
+
// Start the retry chain
|
|
106
|
+
attemptInitWithRetry(revenueCatUserId, 0, sequenceId).catch(err => {
|
|
107
|
+
console.error('[BackgroundInitializer] Init sequence failed unhandled:', err);
|
|
108
|
+
});
|
|
102
109
|
}, AUTH_STATE_DEBOUNCE_MS);
|
|
103
110
|
};
|
|
104
111
|
|
|
@@ -114,12 +121,11 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
114
121
|
console.log('[BackgroundInitializer] Initial RevenueCat userId:', initialRevenueCatUserId || '(undefined - anonymous)');
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
// Initialize RevenueCat for all users (including anonymous).
|
|
118
|
-
// Anonymous users get their Firebase UID passed to RevenueCat so they can make purchases.
|
|
119
|
-
// Credits are stored at users/{uid}/credits/balance regardless of auth status.
|
|
120
124
|
if (initialRevenueCatUserId) {
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
currentSequenceId++;
|
|
126
|
+
attemptInitWithRetry(initialRevenueCatUserId, 0, currentSequenceId).catch(err => {
|
|
127
|
+
console.error('[BackgroundInitializer] Initial sequence failed unhandled:', err);
|
|
128
|
+
});
|
|
123
129
|
} else if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
124
130
|
console.log('[BackgroundInitializer] No user available yet, waiting for auth state');
|
|
125
131
|
}
|
|
@@ -127,14 +133,9 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
127
133
|
const unsubscribe = setupAuthStateListener(() => auth, debouncedInitialize);
|
|
128
134
|
|
|
129
135
|
return () => {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
clearTimeout(retryTimer);
|
|
135
|
-
}
|
|
136
|
-
if (unsubscribe) {
|
|
137
|
-
unsubscribe();
|
|
138
|
-
}
|
|
136
|
+
currentSequenceId++; // Invalidate any running sequences
|
|
137
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
138
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
139
|
+
if (unsubscribe) unsubscribe();
|
|
139
140
|
};
|
|
140
141
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { PurchaseSource } from "./SubscriptionConstants";
|
|
3
|
-
import type { SubscriptionMetadata } from "./types";
|
|
3
|
+
import type { SubscriptionMetadata } from "./types/SubscriptionMetadata";
|
|
4
4
|
import type { PackageType } from "../../revenuecat/core/types/RevenueCatTypes";
|
|
5
5
|
|
|
6
6
|
export interface PurchaseCompletedEvent {
|
|
@@ -13,10 +13,6 @@ abstract class BaseStatusHandler {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
abstract handle(input: StatusResolverInput): SubscriptionStatusType;
|
|
16
|
-
|
|
17
|
-
protected nextOrFallback(input: StatusResolverInput, fallback: SubscriptionStatusType): SubscriptionStatusType {
|
|
18
|
-
return this.next ? this.next.handle(input) : fallback;
|
|
19
|
-
}
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
export class InactiveStatusHandler extends BaseStatusHandler {
|
|
@@ -26,7 +22,7 @@ export class InactiveStatusHandler extends BaseStatusHandler {
|
|
|
26
22
|
if (!input.isPremium || isExpired) {
|
|
27
23
|
return isExpired ? SUBSCRIPTION_STATUS.EXPIRED : SUBSCRIPTION_STATUS.NONE;
|
|
28
24
|
}
|
|
29
|
-
return this.
|
|
25
|
+
return this.next ? this.next.handle(input) : SUBSCRIPTION_STATUS.NONE;
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { PurchasesPackage, CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { IRevenueCatService } from "../../../../shared/application/ports/IRevenueCatService";
|
|
3
3
|
import { PurchaseStatusResolver, type PremiumStatus } from "./PurchaseStatusResolver";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type RestoreResultInfo,
|
|
9
|
-
} from "./package-operations";
|
|
4
|
+
import { fetchPackages } from "./package-operations/PackageFetcher";
|
|
5
|
+
import { executePurchase } from "./package-operations/PackagePurchaser";
|
|
6
|
+
import { restorePurchases } from "./package-operations/PackageRestorer";
|
|
7
|
+
import type { RestoreResultInfo } from "./package-operations/types";
|
|
10
8
|
|
|
11
9
|
export class PackageHandler {
|
|
12
10
|
constructor(
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
|
-
import { getPremiumEntitlement } from "../../../revenuecat/core/types";
|
|
2
|
+
import { getPremiumEntitlement } from "../../../revenuecat/core/types/RevenueCatTypes";
|
|
3
3
|
import { toDate } from "../../../../shared/utils/dateConverter";
|
|
4
4
|
import { detectPackageType } from "../../../../utils/packageTypeDetector";
|
|
5
|
-
import type { PremiumStatus } from "../../core/types";
|
|
5
|
+
import type { PremiumStatus } from "../../core/types/PremiumStatus";
|
|
6
6
|
|
|
7
7
|
export type { PremiumStatus };
|
|
8
8
|
|
package/src/domains/subscription/infrastructure/handlers/package-operations/PackageRestorer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IRevenueCatService } from "../../../../../shared/application/ports/IRevenueCatService";
|
|
2
|
-
import { getPremiumEntitlement } from "../../../../revenuecat/core/types";
|
|
2
|
+
import { getPremiumEntitlement } from "../../../../revenuecat/core/types/RevenueCatTypes";
|
|
3
3
|
import type { RestoreResultInfo } from "./types";
|
|
4
4
|
|
|
5
5
|
export async function restorePurchases(
|
|
@@ -9,7 +9,7 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
|
|
|
9
9
|
import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
|
|
10
10
|
import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionStatus";
|
|
11
11
|
import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
|
|
12
|
-
import { getErrorMessage } from "../../../revenuecat/core/errors";
|
|
12
|
+
import { getErrorMessage } from "../../../revenuecat/core/errors/RevenueCatErrorHandler";
|
|
13
13
|
|
|
14
14
|
interface PurchaseMutationResult {
|
|
15
15
|
success: boolean;
|
|
@@ -8,7 +8,7 @@ import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionM
|
|
|
8
8
|
import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
|
|
9
9
|
import { subscriptionStatusQueryKeys } from "../../presentation/useSubscriptionStatus";
|
|
10
10
|
import { creditsQueryKeys } from "../../../credits/presentation/creditsQueryKeys";
|
|
11
|
-
import { getErrorMessage } from "../../../revenuecat/core/errors";
|
|
11
|
+
import { getErrorMessage } from "../../../revenuecat/core/errors/RevenueCatErrorHandler";
|
|
12
12
|
|
|
13
13
|
interface RestoreResult {
|
|
14
14
|
success: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
2
|
-
import type { PremiumStatus } from "../../core/types";
|
|
1
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
2
|
+
import type { PremiumStatus } from "../../core/types/PremiumStatus";
|
|
3
3
|
import type { RestoreResultInfo } from "../handlers/package-operations/types";
|
|
4
4
|
|
|
5
5
|
export interface SubscriptionManagerConfig {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IRevenueCatService } from "../../../../shared/application/ports/IRevenueCatService";
|
|
2
2
|
import { initializeRevenueCatService, getRevenueCatService } from "../services/revenueCatServiceInstance";
|
|
3
3
|
import { ensureServiceAvailable } from "./subscriptionManagerUtils";
|
|
4
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
4
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
5
5
|
|
|
6
6
|
export const performServiceInitialization = async (config: RevenueCatConfig, userId: string): Promise<{ service: IRevenueCatService; success: boolean }> => {
|
|
7
7
|
await initializeRevenueCatService(config);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Purchases, { type CustomerInfo } from "react-native-purchases";
|
|
2
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
2
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
3
3
|
import { ListenerState } from "./listeners/ListenerState";
|
|
4
4
|
import { processCustomerInfo } from "./listeners/CustomerInfoHandler";
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
2
2
|
import type { PurchaseResult } from "../../../../shared/application/ports/IRevenueCatService";
|
|
3
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
4
|
-
import { isUserCancelledError, isAlreadyPurchasedError } from "../../../revenuecat/core/types";
|
|
3
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
4
|
+
import { isUserCancelledError, isAlreadyPurchasedError } from "../../../revenuecat/core/types/RevenueCatTypes";
|
|
5
5
|
import { validatePurchaseReady, isConsumableProduct } from "./purchase/PurchaseValidator";
|
|
6
6
|
import { executePurchase } from "./purchase/PurchaseExecutor";
|
|
7
7
|
import { handleAlreadyPurchasedError, handlePurchaseError } from "./purchase/PurchaseErrorHandler";
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
RevenueCatRestoreError,
|
|
5
5
|
RevenueCatInitializationError,
|
|
6
6
|
RevenueCatNetworkError,
|
|
7
|
-
} from "../../../revenuecat/core/errors";
|
|
8
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
7
|
+
} from "../../../revenuecat/core/errors/RevenueCatError";
|
|
8
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
9
9
|
import {
|
|
10
10
|
getRawErrorMessage,
|
|
11
11
|
getErrorCode,
|
|
12
12
|
isNetworkError,
|
|
13
13
|
isInvalidCredentialsError,
|
|
14
|
-
} from "../../../revenuecat/core/types";
|
|
14
|
+
} from "../../../revenuecat/core/types/RevenueCatTypes";
|
|
15
15
|
import { notifyRestoreCompleted } from "../utils/PremiumStatusSyncer";
|
|
16
16
|
|
|
17
17
|
interface RestoreHandlerDeps {
|
|
@@ -50,7 +50,7 @@ export async function handleRestore(deps: RestoreHandlerDeps, userId: string): P
|
|
|
50
50
|
|
|
51
51
|
// Generic error with code
|
|
52
52
|
const errorCode = getErrorCode(error);
|
|
53
|
-
const errorMessage = getRawErrorMessage(error
|
|
53
|
+
const errorMessage = getRawErrorMessage(error);
|
|
54
54
|
const enhancedMessage = errorCode
|
|
55
55
|
? `${errorMessage} (Code: ${errorCode})`
|
|
56
56
|
: errorMessage;
|
|
@@ -1,7 +1,7 @@
|
|
|
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 "../../../revenuecat/core/types";
|
|
4
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
5
5
|
import { initializeSDK } from "../../../revenuecat/infrastructure/services/RevenueCatInitializer";
|
|
6
6
|
import { fetchOfferings } from "./OfferingsFetcher";
|
|
7
7
|
import { handlePurchase } from "./PurchaseHandler";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
|
-
import type { RevenueCatConfig } from "../../../../revenuecat/core/types";
|
|
2
|
+
import type { RevenueCatConfig } from "../../../../revenuecat/core/types/RevenueCatConfig";
|
|
3
3
|
import { syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
4
|
-
import { detectRenewal
|
|
4
|
+
import { detectRenewal } from "../../utils/renewal/RenewalDetector";
|
|
5
|
+
import { updateRenewalState } from "../../utils/renewal/RenewalStateUpdater";
|
|
6
|
+
import type { RenewalState } from "../../utils/renewal/types";
|
|
5
7
|
|
|
6
8
|
async function handleRenewal(
|
|
7
9
|
userId: string,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CustomerInfoUpdateListener } from "react-native-purchases";
|
|
2
|
-
import type { RenewalState } from "../../utils/renewal";
|
|
2
|
+
import type { RenewalState } from "../../utils/renewal/types";
|
|
3
3
|
|
|
4
4
|
export class ListenerState {
|
|
5
5
|
listener: CustomerInfoUpdateListener | null = null;
|
|
@@ -3,14 +3,14 @@ import type { PurchaseResult } from "../../../../../shared/application/ports/IRe
|
|
|
3
3
|
import {
|
|
4
4
|
RevenueCatPurchaseError,
|
|
5
5
|
RevenueCatNetworkError,
|
|
6
|
-
} from "../../../../revenuecat/core/errors";
|
|
6
|
+
} from "../../../../revenuecat/core/errors/RevenueCatError";
|
|
7
7
|
import {
|
|
8
8
|
isUserCancelledError,
|
|
9
9
|
isNetworkError,
|
|
10
10
|
isInvalidCredentialsError,
|
|
11
11
|
getRawErrorMessage,
|
|
12
12
|
getErrorCode,
|
|
13
|
-
} from "../../../../revenuecat/core/types";
|
|
13
|
+
} from "../../../../revenuecat/core/types/RevenueCatTypes";
|
|
14
14
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
|
|
15
15
|
import { notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
|
|
16
16
|
import { handleRestore } from "../RestoreHandler";
|
|
@@ -80,7 +80,7 @@ export function handlePurchaseError(
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const errorCode = getErrorCode(error);
|
|
83
|
-
const errorMessage = getRawErrorMessage(error
|
|
83
|
+
const errorMessage = getRawErrorMessage(error);
|
|
84
84
|
const enhancedMessage = errorCode
|
|
85
85
|
? `${errorMessage} (Code: ${errorCode})`
|
|
86
86
|
: errorMessage;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { PurchaseResult } from "../../../../../shared/application/ports/IRevenueCatService";
|
|
3
|
-
import type { RevenueCatConfig
|
|
3
|
+
import type { RevenueCatConfig } from "../../../../revenuecat/core/types/RevenueCatConfig";
|
|
4
|
+
import type { PackageType } from "../../../../revenuecat/core/types/RevenueCatTypes";
|
|
4
5
|
import { notifyPurchaseCompleted, syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
5
6
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
|
|
6
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
2
|
-
import { RevenueCatInitializationError } from "../../../../revenuecat/core/errors";
|
|
2
|
+
import { RevenueCatInitializationError } from "../../../../revenuecat/core/errors/RevenueCatError";
|
|
3
3
|
|
|
4
4
|
export function validatePurchaseReady(isInitialized: boolean): void {
|
|
5
5
|
if (!isInitialized) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RevenueCatService } from "./RevenueCatService.types";
|
|
2
|
-
import type { RevenueCatConfig } from "../../../revenuecat/core/types";
|
|
2
|
+
import type { RevenueCatConfig } from "../../../revenuecat/core/types/RevenueCatConfig";
|
|
3
3
|
|
|
4
4
|
let revenueCatServiceInstance: RevenueCatService | null = null;
|
|
5
5
|
|