@umituz/react-native-subscription 2.27.28 → 2.27.29
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/domain/entities/Credits.ts +2 -0
- package/src/index.ts +2 -0
- package/src/infrastructure/services/CreditsInitializer.ts +2 -1
- package/src/presentation/hooks/useAuthAwarePurchase.ts +25 -9
- package/src/presentation/hooks/useCredits.ts +3 -3
- package/src/presentation/hooks/useFreeCreditsInit.ts +17 -1
- package/src/presentation/hooks/useSubscriptionStatus.ts +3 -3
- package/src/revenuecat/infrastructure/handlers/PackageHandler.ts +2 -0
- package/src/revenuecat/infrastructure/services/CustomerInfoListenerManager.ts +7 -1
- package/src/revenuecat/infrastructure/utils/PremiumStatusSyncer.ts +10 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.29",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -87,6 +87,8 @@ export type {
|
|
|
87
87
|
DeductCreditsResult,
|
|
88
88
|
PurchaseSource,
|
|
89
89
|
PurchaseType,
|
|
90
|
+
CreditAllocation,
|
|
91
|
+
PackageAllocationMap,
|
|
90
92
|
} from "./domain/entities/Credits";
|
|
91
93
|
export { DEFAULT_CREDITS_CONFIG } from "./domain/entities/Credits";
|
|
92
94
|
export { InsufficientCreditsError } from "./domain/errors/InsufficientCreditsError";
|
|
@@ -29,6 +29,7 @@ import { getCreditAllocation } from "../../utils/creditMapper";
|
|
|
29
29
|
|
|
30
30
|
interface InitializationResult {
|
|
31
31
|
credits: number;
|
|
32
|
+
alreadyProcessed?: boolean;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface InitializeCreditsMetadata {
|
|
@@ -58,7 +59,7 @@ export async function initializeCreditsTransaction(
|
|
|
58
59
|
let processedPurchases: string[] = existingData?.processedPurchases || [];
|
|
59
60
|
|
|
60
61
|
if (existingData && purchaseId && processedPurchases.includes(purchaseId)) {
|
|
61
|
-
return { credits: existingData.credits, alreadyProcessed: true }
|
|
62
|
+
return { credits: existingData.credits, alreadyProcessed: true };
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
if (existingData?.purchasedAt) {
|
|
@@ -16,23 +16,40 @@ export interface PurchaseAuthProvider {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
let globalAuthProvider: PurchaseAuthProvider | null = null;
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
interface SavedPurchaseState {
|
|
21
|
+
pkg: PurchasesPackage;
|
|
22
|
+
source: PurchaseSource;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SAVED_PURCHASE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
|
|
27
|
+
let savedPurchaseState: SavedPurchaseState | null = null;
|
|
21
28
|
|
|
22
29
|
export const configureAuthProvider = (provider: PurchaseAuthProvider): void => {
|
|
23
30
|
globalAuthProvider = provider;
|
|
24
31
|
};
|
|
25
32
|
|
|
33
|
+
const savePurchase = (pkg: PurchasesPackage, source: PurchaseSource): void => {
|
|
34
|
+
savedPurchaseState = { pkg, source, timestamp: Date.now() };
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
export const getSavedPurchase = (): { pkg: PurchasesPackage; source: PurchaseSource } | null => {
|
|
27
|
-
if (
|
|
28
|
-
return
|
|
38
|
+
if (!savedPurchaseState) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isExpired = Date.now() - savedPurchaseState.timestamp > SAVED_PURCHASE_EXPIRY_MS;
|
|
43
|
+
if (isExpired) {
|
|
44
|
+
savedPurchaseState = null;
|
|
45
|
+
return null;
|
|
29
46
|
}
|
|
30
|
-
|
|
47
|
+
|
|
48
|
+
return { pkg: savedPurchaseState.pkg, source: savedPurchaseState.source };
|
|
31
49
|
};
|
|
32
50
|
|
|
33
51
|
export const clearSavedPurchase = (): void => {
|
|
34
|
-
|
|
35
|
-
savedSource = null;
|
|
52
|
+
savedPurchaseState = null;
|
|
36
53
|
};
|
|
37
54
|
|
|
38
55
|
export interface UseAuthAwarePurchaseParams {
|
|
@@ -69,8 +86,7 @@ export const useAuthAwarePurchase = (
|
|
|
69
86
|
const isAuth = globalAuthProvider.isAuthenticated();
|
|
70
87
|
|
|
71
88
|
if (!isAuth) {
|
|
72
|
-
|
|
73
|
-
savedSource = source || params?.source || "settings";
|
|
89
|
+
savePurchase(pkg, source || params?.source || "settings");
|
|
74
90
|
globalAuthProvider.showAuthModal();
|
|
75
91
|
return false;
|
|
76
92
|
}
|
|
@@ -86,9 +86,9 @@ export const useCredits = (): UseCreditsResult => {
|
|
|
86
86
|
return result.data || null;
|
|
87
87
|
},
|
|
88
88
|
enabled: queryEnabled,
|
|
89
|
-
staleTime:
|
|
90
|
-
gcTime:
|
|
91
|
-
refetchOnMount:
|
|
89
|
+
staleTime: 30 * 1000, // 30 seconds - data considered fresh
|
|
90
|
+
gcTime: 5 * 60 * 1000, // 5 minutes - keep in cache after unmount
|
|
91
|
+
refetchOnMount: "always",
|
|
92
92
|
refetchOnWindowFocus: true,
|
|
93
93
|
refetchOnReconnect: true,
|
|
94
94
|
});
|
|
@@ -63,6 +63,13 @@ async function initializeFreeCreditsForUser(
|
|
|
63
63
|
|
|
64
64
|
const promise = (async () => {
|
|
65
65
|
try {
|
|
66
|
+
if (!isCreditsRepositoryConfigured()) {
|
|
67
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
68
|
+
console.warn("[useFreeCreditsInit] Credits repository not configured");
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
const repository = getCreditsRepository();
|
|
67
74
|
const result = await repository.initializeFreeCredits(userId);
|
|
68
75
|
|
|
@@ -78,6 +85,11 @@ async function initializeFreeCreditsForUser(
|
|
|
78
85
|
}
|
|
79
86
|
return false;
|
|
80
87
|
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
90
|
+
console.error("[useFreeCreditsInit] Unexpected error:", error);
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
81
93
|
} finally {
|
|
82
94
|
freeCreditsInitInProgress.delete(userId);
|
|
83
95
|
initPromises.delete(userId);
|
|
@@ -142,7 +154,11 @@ export function useFreeCreditsInit(params: UseFreeCreditsInitParams): UseFreeCre
|
|
|
142
154
|
if (needsInit) {
|
|
143
155
|
// Double-check inside effect to handle race conditions
|
|
144
156
|
if (!freeCreditsInitAttempted.has(userId)) {
|
|
145
|
-
initializeFreeCreditsForUser(userId, stableOnComplete)
|
|
157
|
+
initializeFreeCreditsForUser(userId, stableOnComplete).catch((error) => {
|
|
158
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
159
|
+
console.error("[useFreeCreditsInit] Init failed:", error);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
146
162
|
}
|
|
147
163
|
} else if (querySuccess && isAnonymous && !hasCredits && isFreeCreditsEnabled) {
|
|
148
164
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -37,9 +37,9 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
|
|
|
37
37
|
return SubscriptionManager.checkPremiumStatus();
|
|
38
38
|
},
|
|
39
39
|
enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
|
|
40
|
-
staleTime:
|
|
41
|
-
gcTime:
|
|
42
|
-
refetchOnMount:
|
|
40
|
+
staleTime: 30 * 1000, // 30 seconds
|
|
41
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
42
|
+
refetchOnMount: "always",
|
|
43
43
|
refetchOnWindowFocus: true,
|
|
44
44
|
refetchOnReconnect: true,
|
|
45
45
|
});
|
|
@@ -7,6 +7,8 @@ import type { PurchasesPackage, CustomerInfo } from "react-native-purchases";
|
|
|
7
7
|
import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
|
|
8
8
|
import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
|
|
9
9
|
|
|
10
|
+
declare const __DEV__: boolean;
|
|
11
|
+
|
|
10
12
|
export interface PremiumStatus {
|
|
11
13
|
isPremium: boolean;
|
|
12
14
|
expirationDate: Date | null;
|
|
@@ -76,7 +76,13 @@ export class CustomerInfoListenerManager {
|
|
|
76
76
|
|
|
77
77
|
this.renewalState = updateRenewalState(this.renewalState, renewalResult);
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
try {
|
|
80
|
+
await syncPremiumStatus(config, this.currentUserId, customerInfo);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (__DEV__) {
|
|
83
|
+
console.error("[CustomerInfoListener] syncPremiumStatus failed:", error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
80
86
|
};
|
|
81
87
|
|
|
82
88
|
Purchases.addCustomerInfoUpdateListener(this.listener);
|
|
@@ -7,6 +7,8 @@ import type { CustomerInfo } from "react-native-purchases";
|
|
|
7
7
|
import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
|
|
8
8
|
import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
|
|
9
9
|
|
|
10
|
+
declare const __DEV__: boolean;
|
|
11
|
+
|
|
10
12
|
export async function syncPremiumStatus(
|
|
11
13
|
config: RevenueCatConfig,
|
|
12
14
|
userId: string,
|
|
@@ -34,13 +36,13 @@ export async function syncPremiumStatus(
|
|
|
34
36
|
} else {
|
|
35
37
|
await config.onPremiumStatusChanged(userId, false, undefined, undefined, undefined, undefined);
|
|
36
38
|
}
|
|
37
|
-
} catch {
|
|
38
|
-
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (__DEV__) {
|
|
41
|
+
console.error('[PremiumStatusSyncer] syncPremiumStatus failed:', error);
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
declare const __DEV__: boolean;
|
|
43
|
-
|
|
44
46
|
export async function notifyPurchaseCompleted(
|
|
45
47
|
config: RevenueCatConfig,
|
|
46
48
|
userId: string,
|
|
@@ -88,7 +90,9 @@ export async function notifyRestoreCompleted(
|
|
|
88
90
|
|
|
89
91
|
try {
|
|
90
92
|
await config.onRestoreCompleted(userId, isPremium, customerInfo);
|
|
91
|
-
} catch {
|
|
92
|
-
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (__DEV__) {
|
|
95
|
+
console.error('[PremiumStatusSyncer] notifyRestoreCompleted failed:', error);
|
|
96
|
+
}
|
|
93
97
|
}
|
|
94
98
|
}
|