@umituz/react-native-subscription 2.11.5 → 2.11.7
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/infrastructure/services/ActivationHandler.ts +0 -8
- package/src/infrastructure/services/SubscriptionService.ts +0 -16
- package/src/presentation/components/paywall/BestValueBadge.tsx +0 -1
- package/src/presentation/components/paywall/PaywallFeaturesList.tsx +0 -3
- package/src/presentation/components/paywall/PaywallLegalFooter.tsx +2 -2
- package/src/presentation/components/paywall/PaywallModal.tsx +0 -11
- package/src/presentation/components/paywall/SubscriptionFooter.tsx +2 -2
- package/src/presentation/components/paywall/SubscriptionModal.tsx +0 -10
- package/src/presentation/components/paywall/SubscriptionPackageList.tsx +14 -21
- package/src/presentation/components/paywall/SubscriptionPlanCard.tsx +9 -10
- package/src/presentation/hooks/useDeductCredit.ts +5 -4
- package/src/presentation/hooks/usePremiumWithConfig.ts +2 -1
- package/src/presentation/hooks/useSubscriptionModal.ts +2 -2
- package/src/revenuecat/infrastructure/services/CustomerInfoListenerManager.ts +0 -8
- package/src/revenuecat/infrastructure/services/OfferingsFetcher.ts +0 -22
- package/src/revenuecat/infrastructure/services/PurchaseHandler.ts +0 -8
- package/src/revenuecat/infrastructure/services/RestoreHandler.ts +0 -5
- package/src/revenuecat/infrastructure/services/RevenueCatInitializer.ts +0 -37
- package/src/revenuecat/infrastructure/services/RevenueCatService.ts +0 -9
- package/src/revenuecat/infrastructure/services/ServiceStateManager.ts +0 -15
- package/src/revenuecat/infrastructure/utils/ApiKeyResolver.ts +0 -16
- package/src/revenuecat/infrastructure/utils/PremiumStatusSyncer.ts +0 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.7",
|
|
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",
|
|
@@ -26,10 +26,6 @@ export async function activateSubscription(
|
|
|
26
26
|
expiresAt: string | null
|
|
27
27
|
): Promise<SubscriptionStatus> {
|
|
28
28
|
try {
|
|
29
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
30
|
-
console.log("[Subscription] Activating subscription in handler", { userId, productId, expiresAt });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
const updatedStatus = await config.repository.updateSubscriptionStatus(
|
|
34
30
|
userId,
|
|
35
31
|
{
|
|
@@ -57,10 +53,6 @@ export async function deactivateSubscription(
|
|
|
57
53
|
userId: string
|
|
58
54
|
): Promise<SubscriptionStatus> {
|
|
59
55
|
try {
|
|
60
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
61
|
-
console.log("[Subscription] Deactivating subscription in handler", { userId });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
56
|
const updatedStatus = await config.repository.updateSubscriptionStatus(
|
|
65
57
|
userId,
|
|
66
58
|
{
|
|
@@ -39,17 +39,11 @@ export class SubscriptionService implements ISubscriptionService {
|
|
|
39
39
|
try {
|
|
40
40
|
const status = await this.repository.getSubscriptionStatus(userId);
|
|
41
41
|
if (!status) {
|
|
42
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
43
|
-
console.log("[Subscription] No status found for user, returning default");
|
|
44
|
-
}
|
|
45
42
|
return createDefaultSubscriptionStatus();
|
|
46
43
|
}
|
|
47
44
|
|
|
48
45
|
const isValid = this.repository.isSubscriptionValid(status);
|
|
49
46
|
if (!isValid && status.isPremium) {
|
|
50
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
51
|
-
console.log("[Subscription] Expired subscription found, deactivating");
|
|
52
|
-
}
|
|
53
47
|
return await this.deactivateSubscription(userId);
|
|
54
48
|
}
|
|
55
49
|
|
|
@@ -76,9 +70,6 @@ export class SubscriptionService implements ISubscriptionService {
|
|
|
76
70
|
productId: string,
|
|
77
71
|
expiresAt: string | null
|
|
78
72
|
): Promise<SubscriptionStatus> {
|
|
79
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
80
|
-
console.log("[Subscription] Activating subscription", { userId, productId, expiresAt });
|
|
81
|
-
}
|
|
82
73
|
return activateSubscription(
|
|
83
74
|
this.handlerConfig,
|
|
84
75
|
userId,
|
|
@@ -88,9 +79,6 @@ export class SubscriptionService implements ISubscriptionService {
|
|
|
88
79
|
}
|
|
89
80
|
|
|
90
81
|
async deactivateSubscription(userId: string): Promise<SubscriptionStatus> {
|
|
91
|
-
if (typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
92
|
-
console.log("[Subscription] Deactivating subscription", { userId });
|
|
93
|
-
}
|
|
94
82
|
return deactivateSubscription(this.handlerConfig, userId);
|
|
95
83
|
}
|
|
96
84
|
|
|
@@ -148,10 +136,6 @@ export function initializeSubscriptionService(
|
|
|
148
136
|
}
|
|
149
137
|
|
|
150
138
|
export function getSubscriptionService(): SubscriptionService | null {
|
|
151
|
-
if (!subscriptionServiceInstance && typeof globalThis !== 'undefined' && (globalThis as any).__DEV__) {
|
|
152
|
-
// eslint-disable-next-line no-console
|
|
153
|
-
console.warn("[Subscription] Service not initialized");
|
|
154
|
-
}
|
|
155
139
|
return subscriptionServiceInstance;
|
|
156
140
|
}
|
|
157
141
|
|
|
@@ -18,9 +18,6 @@ interface PaywallFeaturesListProps {
|
|
|
18
18
|
|
|
19
19
|
export const PaywallFeaturesList: React.FC<PaywallFeaturesListProps> = React.memo(
|
|
20
20
|
({ features, containerStyle, gap = 12 }) => {
|
|
21
|
-
if (__DEV__) {
|
|
22
|
-
console.log("[PaywallFeaturesList] Rendering features count:", features.length);
|
|
23
|
-
}
|
|
24
21
|
return (
|
|
25
22
|
<View style={[styles.container, containerStyle]}>
|
|
26
23
|
{features.map((feature, index) => (
|
|
@@ -39,13 +39,13 @@ export const PaywallLegalFooter: React.FC<PaywallLegalFooterProps> = React.memo(
|
|
|
39
39
|
|
|
40
40
|
const handlePrivacyPress = () => {
|
|
41
41
|
if (privacyUrl) {
|
|
42
|
-
Linking.openURL(privacyUrl).catch(
|
|
42
|
+
Linking.openURL(privacyUrl).catch(() => {});
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
const handleTermsPress = () => {
|
|
47
47
|
if (termsUrl) {
|
|
48
|
-
Linking.openURL(termsUrl).catch(
|
|
48
|
+
Linking.openURL(termsUrl).catch(() => {});
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
|
|
@@ -95,17 +95,6 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
95
95
|
onSubscriptionPurchase,
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (__DEV__) {
|
|
100
|
-
console.log("[PaywallModal] State:", {
|
|
101
|
-
visible,
|
|
102
|
-
activeTab,
|
|
103
|
-
creditsPackagesCount: creditsPackages?.length ?? 0,
|
|
104
|
-
subscriptionPackagesCount: subscriptionPackages?.length ?? 0,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}, [visible, activeTab, creditsPackages?.length, subscriptionPackages?.length]);
|
|
108
|
-
|
|
109
98
|
return (
|
|
110
99
|
<BaseModal visible={visible} onClose={onClose}>
|
|
111
100
|
<View style={styles.container}>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
3
|
+
import type { PurchasesPackage } from "react-native-purchases";
|
|
3
4
|
import { AtomicText } from "@umituz/react-native-design-system";
|
|
4
5
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
5
6
|
import { PaywallLegalFooter } from "./PaywallLegalFooter";
|
|
@@ -10,7 +11,7 @@ interface SubscriptionFooterProps {
|
|
|
10
11
|
processingText: string;
|
|
11
12
|
purchaseButtonText: string;
|
|
12
13
|
hasPackages: boolean;
|
|
13
|
-
selectedPkg:
|
|
14
|
+
selectedPkg: PurchasesPackage | null;
|
|
14
15
|
restoreButtonText: string;
|
|
15
16
|
showRestoreButton: boolean;
|
|
16
17
|
privacyUrl?: string;
|
|
@@ -21,7 +22,6 @@ interface SubscriptionFooterProps {
|
|
|
21
22
|
onRestore: () => void;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
// @ts-ignore
|
|
25
25
|
import { LinearGradient } from "expo-linear-gradient";
|
|
26
26
|
|
|
27
27
|
export const SubscriptionFooter: React.FC<SubscriptionFooterProps> = React.memo(
|
|
@@ -79,16 +79,6 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
|
|
|
79
79
|
onClose,
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
if (__DEV__) {
|
|
83
|
-
console.log("[SubscriptionModal] State:", {
|
|
84
|
-
visible,
|
|
85
|
-
isLoading,
|
|
86
|
-
packagesCount: packages?.length ?? 0,
|
|
87
|
-
selectedPkg: selectedPkg?.identifier ?? null,
|
|
88
|
-
isProcessing,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
82
|
return (
|
|
93
83
|
<BaseModal visible={visible} onClose={onClose}>
|
|
94
84
|
<View style={styles.container}>
|
|
@@ -4,7 +4,7 @@ import { AtomicText } from "@umituz/react-native-design-system";
|
|
|
4
4
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
5
5
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
6
6
|
import { SubscriptionPlanCard } from "./SubscriptionPlanCard";
|
|
7
|
-
import { isYearlyPackage } from "../../../utils/packagePeriodUtils";
|
|
7
|
+
import { isYearlyPackage, isMonthlyPackage, isWeeklyPackage } from "../../../utils/packagePeriodUtils";
|
|
8
8
|
|
|
9
9
|
interface SubscriptionPackageListProps {
|
|
10
10
|
isLoading: boolean;
|
|
@@ -34,19 +34,6 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
|
|
|
34
34
|
const hasPackages = packages.length > 0;
|
|
35
35
|
const showLoading = isLoading && !hasPackages;
|
|
36
36
|
|
|
37
|
-
// Debug logging in development
|
|
38
|
-
if (__DEV__) {
|
|
39
|
-
console.log("[SubscriptionPackageList] State:", {
|
|
40
|
-
isLoading,
|
|
41
|
-
hasPackages,
|
|
42
|
-
showLoading,
|
|
43
|
-
packagesCount: packages?.length ?? 0,
|
|
44
|
-
loadingText,
|
|
45
|
-
emptyText,
|
|
46
|
-
selectedPkgId: selectedPkg?.identifier ?? null,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
37
|
if (showLoading) {
|
|
51
38
|
return (
|
|
52
39
|
<View style={styles.centerContent}>
|
|
@@ -94,28 +81,34 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
|
|
|
94
81
|
|
|
95
82
|
const productId = pkg.product.identifier;
|
|
96
83
|
const packageId = pkg.identifier;
|
|
84
|
+
const productTitle = pkg.product.title || "";
|
|
97
85
|
|
|
98
|
-
// 1. Exact match
|
|
86
|
+
// 1. Exact match
|
|
99
87
|
if (creditAmounts[productId] !== undefined) return creditAmounts[productId];
|
|
100
88
|
if (creditAmounts[packageId] !== undefined) return creditAmounts[packageId];
|
|
101
89
|
|
|
102
|
-
// 2. Case-insensitive
|
|
90
|
+
// 2. Case-insensitive and Title matching
|
|
103
91
|
const lowerProductId = productId.toLowerCase();
|
|
104
92
|
const lowerPackageId = packageId.toLowerCase();
|
|
93
|
+
const lowerTitle = productTitle.toLowerCase();
|
|
105
94
|
|
|
106
95
|
for (const [key, value] of Object.entries(creditAmounts)) {
|
|
107
96
|
const lowerKey = key.toLowerCase();
|
|
108
|
-
if (lowerProductId === lowerKey || lowerPackageId === lowerKey) {
|
|
97
|
+
if (lowerProductId === lowerKey || lowerPackageId === lowerKey || lowerTitle.includes(lowerKey)) {
|
|
109
98
|
return value;
|
|
110
99
|
}
|
|
111
100
|
}
|
|
112
101
|
|
|
113
|
-
// 3.
|
|
102
|
+
// 3. Period-based fallback
|
|
103
|
+
const isYearly = isYearlyPackage(pkg);
|
|
104
|
+
const isMonthly = isMonthlyPackage(pkg);
|
|
105
|
+
const isWeekly = isWeeklyPackage(pkg);
|
|
106
|
+
|
|
114
107
|
for (const [key, value] of Object.entries(creditAmounts)) {
|
|
115
108
|
const lowerKey = key.toLowerCase();
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
if (isYearly && (lowerKey.includes("year") || lowerKey.includes("annual") || lowerKey === "yearly")) return value;
|
|
110
|
+
if (isMonthly && (lowerKey.includes("month") || lowerKey === "monthly")) return value;
|
|
111
|
+
if (isWeekly && (lowerKey.includes("week") || lowerKey === "weekly")) return value;
|
|
119
112
|
}
|
|
120
113
|
|
|
121
114
|
return undefined;
|
|
@@ -13,7 +13,6 @@ import { useLocalization } from "@umituz/react-native-localization";
|
|
|
13
13
|
import { BestValueBadge } from "./BestValueBadge";
|
|
14
14
|
|
|
15
15
|
import { getPeriodLabel, isYearlyPackage } from "../../../utils/packagePeriodUtils";
|
|
16
|
-
// @ts-ignore
|
|
17
16
|
import { LinearGradient } from "expo-linear-gradient";
|
|
18
17
|
|
|
19
18
|
interface SubscriptionPlanCardProps {
|
|
@@ -40,12 +39,6 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
|
|
|
40
39
|
|
|
41
40
|
const title = pkg.product.title || t(`paywall.period.${periodLabel}`);
|
|
42
41
|
|
|
43
|
-
// Debug: Log product identifier and credit amount
|
|
44
|
-
if (__DEV__ && creditAmount) {
|
|
45
|
-
console.log("[SubscriptionPlanCard] Product:", pkg.product.identifier, "Credits:", creditAmount);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
42
|
const CardComponent = isSelected ? LinearGradient : View;
|
|
50
43
|
const cardProps = isSelected
|
|
51
44
|
? {
|
|
@@ -117,18 +110,24 @@ export const SubscriptionPlanCard: React.FC<SubscriptionPlanCardProps> =
|
|
|
117
110
|
<View
|
|
118
111
|
style={[
|
|
119
112
|
styles.creditBadge,
|
|
120
|
-
{
|
|
113
|
+
{
|
|
114
|
+
backgroundColor: withAlpha(tokens.colors.primary, 0.25), // Increased alpha
|
|
115
|
+
borderColor: withAlpha(tokens.colors.primary, 0.4),
|
|
116
|
+
borderWidth: 1,
|
|
117
|
+
flexDirection: "row",
|
|
118
|
+
alignItems: "center"
|
|
119
|
+
},
|
|
121
120
|
]}
|
|
122
121
|
>
|
|
123
122
|
<AtomicText
|
|
124
123
|
type="labelSmall"
|
|
125
124
|
style={{
|
|
126
125
|
color: tokens.colors.primary,
|
|
127
|
-
fontWeight: "
|
|
126
|
+
fontWeight: "800",
|
|
128
127
|
fontSize: 11,
|
|
129
128
|
}}
|
|
130
129
|
>
|
|
131
|
-
{creditAmount} {t("paywall.credits")}
|
|
130
|
+
{creditAmount} {t("paywall.credits") || "Credits"}
|
|
132
131
|
</AtomicText>
|
|
133
132
|
</View>
|
|
134
133
|
)}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Generic and reusable - uses module-level repository.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { useCallback } from "react";
|
|
8
9
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
9
10
|
import type { CreditType, UserCredits } from "../../domain/entities/Credits";
|
|
10
11
|
import { getCreditsRepository } from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
@@ -78,7 +79,7 @@ export const useDeductCredit = ({
|
|
|
78
79
|
},
|
|
79
80
|
});
|
|
80
81
|
|
|
81
|
-
const deductCredit = async (creditType: CreditType): Promise<boolean> => {
|
|
82
|
+
const deductCredit = useCallback(async (creditType: CreditType): Promise<boolean> => {
|
|
82
83
|
try {
|
|
83
84
|
const result = await mutation.mutateAsync(creditType);
|
|
84
85
|
|
|
@@ -93,7 +94,7 @@ export const useDeductCredit = ({
|
|
|
93
94
|
} catch {
|
|
94
95
|
return false;
|
|
95
96
|
}
|
|
96
|
-
};
|
|
97
|
+
}, [mutation, onCreditsExhausted]);
|
|
97
98
|
|
|
98
99
|
return {
|
|
99
100
|
deductCredit,
|
|
@@ -135,14 +136,14 @@ export const useInitializeCredits = ({
|
|
|
135
136
|
},
|
|
136
137
|
});
|
|
137
138
|
|
|
138
|
-
const initializeCredits = async (purchaseId?: string): Promise<boolean> => {
|
|
139
|
+
const initializeCredits = useCallback(async (purchaseId?: string): Promise<boolean> => {
|
|
139
140
|
try {
|
|
140
141
|
const result = await mutation.mutateAsync(purchaseId);
|
|
141
142
|
return result.success;
|
|
142
143
|
} catch {
|
|
143
144
|
return false;
|
|
144
145
|
}
|
|
145
|
-
};
|
|
146
|
+
}, [mutation]);
|
|
146
147
|
|
|
147
148
|
return {
|
|
148
149
|
initializeCredits,
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { useCallback } from "react";
|
|
9
9
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
10
|
+
import type { UserCredits } from "../../domain/entities/Credits";
|
|
10
11
|
import { useCredits } from "./useCredits";
|
|
11
12
|
import { useInitializeCredits } from "./useDeductCredit";
|
|
12
13
|
import {
|
|
@@ -27,7 +28,7 @@ export interface UsePremiumWithConfigResult {
|
|
|
27
28
|
isPremium: boolean;
|
|
28
29
|
isLoading: boolean;
|
|
29
30
|
packages: PurchasesPackage[];
|
|
30
|
-
credits:
|
|
31
|
+
credits: UserCredits | null;
|
|
31
32
|
showPaywall: boolean;
|
|
32
33
|
purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
33
34
|
restorePurchase: () => Promise<boolean>;
|
|
@@ -23,7 +23,7 @@ export const useSubscriptionModal = ({
|
|
|
23
23
|
} finally {
|
|
24
24
|
setIsProcessing(false);
|
|
25
25
|
}
|
|
26
|
-
}, [selectedPkg,
|
|
26
|
+
}, [selectedPkg, onPurchase, onClose]);
|
|
27
27
|
|
|
28
28
|
const handleRestore = useCallback(async () => {
|
|
29
29
|
if (isProcessing) return;
|
|
@@ -33,7 +33,7 @@ export const useSubscriptionModal = ({
|
|
|
33
33
|
} finally {
|
|
34
34
|
setIsProcessing(false);
|
|
35
35
|
}
|
|
36
|
-
}, [
|
|
36
|
+
}, [onRestore, onClose]);
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
39
|
selectedPkg,
|
|
@@ -47,14 +47,6 @@ export class CustomerInfoListenerManager {
|
|
|
47
47
|
entitlementIdentifier: this.entitlementIdentifier,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
if (__DEV__) {
|
|
51
|
-
console.log("[RevenueCat] CustomerInfo updated", {
|
|
52
|
-
userId: this.currentUserId,
|
|
53
|
-
hasPremium,
|
|
54
|
-
entitlementIdentifier: this.entitlementIdentifier,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
50
|
syncPremiumStatus(config, this.currentUserId, customerInfo);
|
|
59
51
|
};
|
|
60
52
|
|
|
@@ -22,19 +22,8 @@ export async function fetchOfferings(
|
|
|
22
22
|
isInitialized: deps.isInitialized(),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
if (__DEV__) {
|
|
26
|
-
console.log(
|
|
27
|
-
"[RevenueCat] fetchOfferings() called, isInitialized:",
|
|
28
|
-
deps.isInitialized()
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
if (!deps.isInitialized()) {
|
|
33
26
|
trackPackageWarning("subscription", "Fetch offerings called before initialization", {});
|
|
34
|
-
|
|
35
|
-
if (__DEV__) {
|
|
36
|
-
console.log("[RevenueCat] fetchOfferings() - NOT initialized");
|
|
37
|
-
}
|
|
38
27
|
return null;
|
|
39
28
|
}
|
|
40
29
|
|
|
@@ -48,13 +37,6 @@ export async function fetchOfferings(
|
|
|
48
37
|
packagesCount,
|
|
49
38
|
});
|
|
50
39
|
|
|
51
|
-
if (__DEV__) {
|
|
52
|
-
console.log("[RevenueCat] fetchOfferings() result:", {
|
|
53
|
-
hasCurrent: !!offerings.current,
|
|
54
|
-
packagesCount,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
40
|
return offerings.current;
|
|
59
41
|
} catch (error) {
|
|
60
42
|
trackPackageError(
|
|
@@ -64,10 +46,6 @@ export async function fetchOfferings(
|
|
|
64
46
|
operation: "fetch_offerings",
|
|
65
47
|
}
|
|
66
48
|
);
|
|
67
|
-
|
|
68
|
-
if (__DEV__) {
|
|
69
|
-
console.log("[RevenueCat] fetchOfferings() error:", error);
|
|
70
|
-
}
|
|
71
49
|
return null;
|
|
72
50
|
}
|
|
73
51
|
}
|
|
@@ -46,14 +46,12 @@ export async function handlePurchase(
|
|
|
46
46
|
pkg: PurchasesPackage,
|
|
47
47
|
userId: string
|
|
48
48
|
): Promise<PurchaseResult> {
|
|
49
|
-
if (__DEV__) console.log("[RevenueCat] Purchase started. Product:", pkg.product.identifier, "User:", userId);
|
|
50
49
|
addPackageBreadcrumb("subscription", "Purchase started", {
|
|
51
50
|
productId: pkg.product.identifier,
|
|
52
51
|
userId,
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
if (!deps.isInitialized()) {
|
|
56
|
-
if (__DEV__) console.error("[RevenueCat] Purchase failed - Not initialized");
|
|
57
55
|
const error = new RevenueCatInitializationError();
|
|
58
56
|
trackPackageError(error, {
|
|
59
57
|
packageName: "subscription",
|
|
@@ -66,14 +64,12 @@ export async function handlePurchase(
|
|
|
66
64
|
|
|
67
65
|
const consumableIds = deps.config.consumableProductIdentifiers || [];
|
|
68
66
|
const isConsumable = isConsumableProduct(pkg, consumableIds);
|
|
69
|
-
if (__DEV__) console.log("[RevenueCat] Product type:", isConsumable ? "consumable" : "subscription");
|
|
70
67
|
|
|
71
68
|
try {
|
|
72
69
|
const purchaseResult = await Purchases.purchasePackage(pkg);
|
|
73
70
|
const customerInfo = purchaseResult.customerInfo;
|
|
74
71
|
|
|
75
72
|
if (isConsumable) {
|
|
76
|
-
if (__DEV__) console.log("[RevenueCat] Consumable purchase successful:", pkg.product.identifier);
|
|
77
73
|
return {
|
|
78
74
|
success: true,
|
|
79
75
|
isPremium: false,
|
|
@@ -87,7 +83,6 @@ export async function handlePurchase(
|
|
|
87
83
|
const isPremium = !!customerInfo.entitlements.active[entitlementIdentifier];
|
|
88
84
|
|
|
89
85
|
if (isPremium) {
|
|
90
|
-
if (__DEV__) console.log("[RevenueCat] Subscription purchase successful. Premium active:", isPremium);
|
|
91
86
|
await syncPremiumStatus(deps.config, userId, customerInfo);
|
|
92
87
|
await notifyPurchaseCompleted(
|
|
93
88
|
deps.config,
|
|
@@ -98,7 +93,6 @@ export async function handlePurchase(
|
|
|
98
93
|
return { success: true, isPremium: true, customerInfo };
|
|
99
94
|
}
|
|
100
95
|
|
|
101
|
-
if (__DEV__) console.warn("[RevenueCat] Purchase completed but entitlement not active");
|
|
102
96
|
const entitlementError = new RevenueCatPurchaseError(
|
|
103
97
|
"Purchase completed but premium entitlement not active",
|
|
104
98
|
pkg.product.identifier
|
|
@@ -113,14 +107,12 @@ export async function handlePurchase(
|
|
|
113
107
|
throw entitlementError;
|
|
114
108
|
} catch (error) {
|
|
115
109
|
if (isUserCancelledError(error)) {
|
|
116
|
-
if (__DEV__) console.log("[RevenueCat] Purchase cancelled by user");
|
|
117
110
|
addPackageBreadcrumb("subscription", "Purchase cancelled by user", {
|
|
118
111
|
productId: pkg.product.identifier,
|
|
119
112
|
userId,
|
|
120
113
|
});
|
|
121
114
|
return { success: false, isPremium: false };
|
|
122
115
|
}
|
|
123
|
-
if (__DEV__) console.error("[RevenueCat] Purchase error:", error);
|
|
124
116
|
const errorMessage = getErrorMessage(error, "Purchase failed");
|
|
125
117
|
const purchaseError = new RevenueCatPurchaseError(errorMessage, pkg.product.identifier);
|
|
126
118
|
trackPackageError(purchaseError, {
|
|
@@ -33,11 +33,9 @@ export async function handleRestore(
|
|
|
33
33
|
deps: RestoreHandlerDeps,
|
|
34
34
|
userId: string
|
|
35
35
|
): Promise<RestoreResult> {
|
|
36
|
-
if (__DEV__) console.log("[RevenueCat] Restore started for user:", userId);
|
|
37
36
|
addPackageBreadcrumb("subscription", "Restore started", { userId });
|
|
38
37
|
|
|
39
38
|
if (!deps.isInitialized()) {
|
|
40
|
-
if (__DEV__) console.error("[RevenueCat] Restore failed - Not initialized");
|
|
41
39
|
const error = new RevenueCatInitializationError();
|
|
42
40
|
trackPackageError(error, {
|
|
43
41
|
packageName: "subscription",
|
|
@@ -53,14 +51,12 @@ export async function handleRestore(
|
|
|
53
51
|
const isPremium = !!customerInfo.entitlements.active[entitlementIdentifier];
|
|
54
52
|
|
|
55
53
|
if (isPremium) {
|
|
56
|
-
if (__DEV__) console.log("[RevenueCat] Restore successful - Premium active");
|
|
57
54
|
await syncPremiumStatus(deps.config, userId, customerInfo);
|
|
58
55
|
addPackageBreadcrumb("subscription", "Restore successful - premium active", {
|
|
59
56
|
userId,
|
|
60
57
|
entitlementId: entitlementIdentifier,
|
|
61
58
|
});
|
|
62
59
|
} else {
|
|
63
|
-
if (__DEV__) console.log("[RevenueCat] Restore completed - No premium found");
|
|
64
60
|
addPackageBreadcrumb("subscription", "Restore completed - no premium found", {
|
|
65
61
|
userId,
|
|
66
62
|
});
|
|
@@ -70,7 +66,6 @@ export async function handleRestore(
|
|
|
70
66
|
|
|
71
67
|
return { success: isPremium, isPremium, customerInfo };
|
|
72
68
|
} catch (error) {
|
|
73
|
-
if (__DEV__) console.error("[RevenueCat] Restore error:", error);
|
|
74
69
|
const errorMessage = getErrorMessage(error, "Restore failed");
|
|
75
70
|
const restoreError = new RevenueCatRestoreError(errorMessage);
|
|
76
71
|
trackPackageError(restoreError, {
|
|
@@ -36,15 +36,8 @@ export async function initializeSDK(
|
|
|
36
36
|
isAlreadyConfigured: isPurchasesConfigured,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
if (__DEV__) {
|
|
40
|
-
console.log("[RevenueCat] initializeSDK() called with userId:", userId, "isPurchasesConfigured:", isPurchasesConfigured);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
39
|
// Case 1: Already initialized with the same user ID
|
|
44
40
|
if (deps.isInitialized() && deps.getCurrentUserId() === userId) {
|
|
45
|
-
if (__DEV__) {
|
|
46
|
-
console.log("[RevenueCat] Already initialized with same userId, skipping configure");
|
|
47
|
-
}
|
|
48
41
|
|
|
49
42
|
try {
|
|
50
43
|
const [customerInfo, offerings] = await Promise.all([
|
|
@@ -69,10 +62,6 @@ export async function initializeSDK(
|
|
|
69
62
|
|
|
70
63
|
// Case 2: Already configured but different user or re-initializing
|
|
71
64
|
if (isPurchasesConfigured) {
|
|
72
|
-
if (__DEV__) {
|
|
73
|
-
console.log("[RevenueCat] SDK already configured, using logIn for userId:", userId);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
65
|
try {
|
|
77
66
|
const { customerInfo } = await Purchases.logIn(userId);
|
|
78
67
|
|
|
@@ -85,7 +74,6 @@ export async function initializeSDK(
|
|
|
85
74
|
|
|
86
75
|
return { success: true, offering: offerings.current, hasPremium };
|
|
87
76
|
} catch (error) {
|
|
88
|
-
if (__DEV__) console.warn("[RevenueCat] logIn failed:", error);
|
|
89
77
|
// If logIn fails, we don't necessarily want to re-configure if it's already configured
|
|
90
78
|
// But we can return failure
|
|
91
79
|
return { success: false, offering: null, hasPremium: false };
|
|
@@ -105,25 +93,11 @@ export async function initializeSDK(
|
|
|
105
93
|
}
|
|
106
94
|
|
|
107
95
|
try {
|
|
108
|
-
if (deps.isUsingTestStore()) {
|
|
109
|
-
if (__DEV__) {
|
|
110
|
-
console.log("[RevenueCat] Using Test Store key");
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (__DEV__) {
|
|
115
|
-
console.log("[RevenueCat] Calling Purchases.configure()...");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
96
|
await Purchases.configure({ apiKey: key, appUserID: userId });
|
|
119
97
|
isPurchasesConfigured = true;
|
|
120
98
|
deps.setInitialized(true);
|
|
121
99
|
deps.setCurrentUserId(userId);
|
|
122
100
|
|
|
123
|
-
if (__DEV__) {
|
|
124
|
-
console.log("[RevenueCat] SDK configured successfully");
|
|
125
|
-
}
|
|
126
|
-
|
|
127
101
|
const [customerInfo, offerings] = await Promise.all([
|
|
128
102
|
Purchases.getCustomerInfo(),
|
|
129
103
|
Purchases.getOfferings(),
|
|
@@ -138,14 +112,6 @@ export async function initializeSDK(
|
|
|
138
112
|
allOfferingsCount: Object.keys(offerings.all).length,
|
|
139
113
|
});
|
|
140
114
|
|
|
141
|
-
if (__DEV__) {
|
|
142
|
-
console.log("[RevenueCat] Fetched offerings:", {
|
|
143
|
-
hasCurrent: !!offerings.current,
|
|
144
|
-
packagesCount,
|
|
145
|
-
allOfferingsCount: Object.keys(offerings.all).length,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
115
|
const entitlementId = deps.config.entitlementIdentifier;
|
|
150
116
|
const hasPremium = !!customerInfo.entitlements.active[entitlementId];
|
|
151
117
|
|
|
@@ -163,9 +129,6 @@ export async function initializeSDK(
|
|
|
163
129
|
}
|
|
164
130
|
);
|
|
165
131
|
|
|
166
|
-
if (__DEV__) {
|
|
167
|
-
console.log("[RevenueCat] Init failed:", errorMessage);
|
|
168
|
-
}
|
|
169
132
|
return { success: false, offering: null, hasPremium: false };
|
|
170
133
|
}
|
|
171
134
|
}
|
|
@@ -54,12 +54,10 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
54
54
|
|
|
55
55
|
async initialize(userId: string, apiKey?: string): Promise<InitializeResult> {
|
|
56
56
|
if (this.isInitialized() && this.getCurrentUserId() === userId) {
|
|
57
|
-
if (__DEV__) console.log("[RevenueCat] Already initialized for user:", userId);
|
|
58
57
|
addPackageBreadcrumb("subscription", "Already initialized", { userId });
|
|
59
58
|
return { success: true, offering: (await this.fetchOfferings()), hasPremium: false };
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
if (__DEV__) console.log("[RevenueCat] Initializing for user:", userId, "API Key:", apiKey ? "provided" : "from config");
|
|
63
61
|
addPackageBreadcrumb("subscription", "Initialization started", { userId });
|
|
64
62
|
|
|
65
63
|
try {
|
|
@@ -77,12 +75,10 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
77
75
|
);
|
|
78
76
|
|
|
79
77
|
if (result.success) {
|
|
80
|
-
if (__DEV__) console.log("[RevenueCat] Initialization successful. Offering:", result.offering?.identifier, "Packages:", result.offering?.availablePackages.length);
|
|
81
78
|
this.listenerManager.setUserId(userId);
|
|
82
79
|
this.listenerManager.setupListener(this.stateManager.getConfig());
|
|
83
80
|
addPackageBreadcrumb("subscription", "Initialization successful", { userId });
|
|
84
81
|
} else {
|
|
85
|
-
if (__DEV__) console.warn("[RevenueCat] Initialization failed for user:", userId);
|
|
86
82
|
trackPackageWarning("subscription", "Initialization failed", {
|
|
87
83
|
userId,
|
|
88
84
|
hasOffering: !!result.offering,
|
|
@@ -91,7 +87,6 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
91
87
|
|
|
92
88
|
return result;
|
|
93
89
|
} catch (error) {
|
|
94
|
-
if (__DEV__) console.error("[RevenueCat] Initialization error:", error);
|
|
95
90
|
trackPackageError(error instanceof Error ? error : new Error(String(error)), {
|
|
96
91
|
packageName: "subscription",
|
|
97
92
|
operation: "initialize",
|
|
@@ -136,11 +131,9 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
136
131
|
|
|
137
132
|
async reset(): Promise<void> {
|
|
138
133
|
if (!this.isInitialized()) {
|
|
139
|
-
if (__DEV__) console.log("[RevenueCat] Reset called but not initialized");
|
|
140
134
|
return;
|
|
141
135
|
}
|
|
142
136
|
|
|
143
|
-
if (__DEV__) console.log("[RevenueCat] Resetting for user:", this.getCurrentUserId());
|
|
144
137
|
addPackageBreadcrumb("subscription", "Reset started", {
|
|
145
138
|
userId: this.getCurrentUserId(),
|
|
146
139
|
});
|
|
@@ -150,10 +143,8 @@ export class RevenueCatService implements IRevenueCatService {
|
|
|
150
143
|
try {
|
|
151
144
|
await Purchases.logOut();
|
|
152
145
|
this.stateManager.setInitialized(false);
|
|
153
|
-
if (__DEV__) console.log("[RevenueCat] Reset successful");
|
|
154
146
|
addPackageBreadcrumb("subscription", "Reset successful", {});
|
|
155
147
|
} catch (error) {
|
|
156
|
-
if (__DEV__) console.warn("[RevenueCat] Reset failed (non-critical):", error);
|
|
157
148
|
trackPackageWarning("subscription", "Reset failed (non-critical)", {
|
|
158
149
|
error: error instanceof Error ? error.message : String(error),
|
|
159
150
|
});
|
|
@@ -15,14 +15,6 @@ export class ServiceStateManager {
|
|
|
15
15
|
constructor(config: RevenueCatConfig) {
|
|
16
16
|
this.config = config;
|
|
17
17
|
this.usingTestStore = this.shouldUseTestStore();
|
|
18
|
-
|
|
19
|
-
if (__DEV__) {
|
|
20
|
-
console.log("[RevenueCat] Config", {
|
|
21
|
-
hasTestKey: !!this.config.testStoreKey,
|
|
22
|
-
usingTestStore: this.usingTestStore,
|
|
23
|
-
entitlementIdentifier: this.config.entitlementIdentifier,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
18
|
}
|
|
27
19
|
|
|
28
20
|
private shouldUseTestStore(): boolean {
|
|
@@ -60,12 +52,5 @@ export class ServiceStateManager {
|
|
|
60
52
|
updateConfig(config: RevenueCatConfig): void {
|
|
61
53
|
this.config = config;
|
|
62
54
|
this.usingTestStore = this.shouldUseTestStore();
|
|
63
|
-
|
|
64
|
-
if (__DEV__) {
|
|
65
|
-
console.log("[RevenueCat] Config updated", {
|
|
66
|
-
hasTestKey: !!this.config.testStoreKey,
|
|
67
|
-
usingTestStore: this.usingTestStore,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
55
|
}
|
|
71
56
|
}
|
|
@@ -21,9 +21,6 @@ export function shouldUseTestStore(config: RevenueCatConfig): boolean {
|
|
|
21
21
|
|
|
22
22
|
// CRITICAL: Production builds should NEVER use test store
|
|
23
23
|
if (isProductionBuild() && !isExpoGo()) {
|
|
24
|
-
if (__DEV__) {
|
|
25
|
-
console.log("[RevenueCat] Production build detected - using production API keys");
|
|
26
|
-
}
|
|
27
24
|
return false;
|
|
28
25
|
}
|
|
29
26
|
|
|
@@ -38,17 +35,6 @@ export function shouldUseTestStore(config: RevenueCatConfig): boolean {
|
|
|
38
35
|
export function resolveApiKey(config: RevenueCatConfig): string | null {
|
|
39
36
|
const useTestStore = shouldUseTestStore(config);
|
|
40
37
|
|
|
41
|
-
// Always log in development, log warnings in production for debugging
|
|
42
|
-
/* eslint-disable-next-line no-console */
|
|
43
|
-
console.log("[RevenueCat] resolveApiKey:", {
|
|
44
|
-
platform: Platform.OS,
|
|
45
|
-
useTestStore,
|
|
46
|
-
hasTestKey: !!config.testStoreKey,
|
|
47
|
-
hasIosKey: !!config.iosApiKey,
|
|
48
|
-
hasAndroidKey: !!config.androidApiKey,
|
|
49
|
-
isProduction: isProductionBuild(),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
38
|
if (useTestStore) {
|
|
53
39
|
return config.testStoreKey ?? null;
|
|
54
40
|
}
|
|
@@ -60,8 +46,6 @@ export function resolveApiKey(config: RevenueCatConfig): string | null {
|
|
|
60
46
|
: config.iosApiKey;
|
|
61
47
|
|
|
62
48
|
if (!key || key === "" || key.includes("YOUR_")) {
|
|
63
|
-
/* eslint-disable-next-line no-console */
|
|
64
|
-
console.warn("[RevenueCat] ⚠️ NO API KEY - packages will not load. Check env vars.");
|
|
65
49
|
return null;
|
|
66
50
|
}
|
|
67
51
|
|
|
@@ -63,12 +63,6 @@ export async function syncPremiumStatus(
|
|
|
63
63
|
isPremium,
|
|
64
64
|
}
|
|
65
65
|
);
|
|
66
|
-
|
|
67
|
-
if (__DEV__) {
|
|
68
|
-
const message =
|
|
69
|
-
error instanceof Error ? error.message : "Premium sync failed";
|
|
70
|
-
console.log("[RevenueCat] Premium status sync failed:", message);
|
|
71
|
-
}
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
|
|
@@ -104,12 +98,6 @@ export async function notifyPurchaseCompleted(
|
|
|
104
98
|
productId,
|
|
105
99
|
}
|
|
106
100
|
);
|
|
107
|
-
|
|
108
|
-
if (__DEV__) {
|
|
109
|
-
const message =
|
|
110
|
-
error instanceof Error ? error.message : "Purchase callback failed";
|
|
111
|
-
console.log("[RevenueCat] Purchase completion callback failed:", message);
|
|
112
|
-
}
|
|
113
101
|
}
|
|
114
102
|
}
|
|
115
103
|
|
|
@@ -145,11 +133,5 @@ export async function notifyRestoreCompleted(
|
|
|
145
133
|
isPremium,
|
|
146
134
|
}
|
|
147
135
|
);
|
|
148
|
-
|
|
149
|
-
if (__DEV__) {
|
|
150
|
-
const message =
|
|
151
|
-
error instanceof Error ? error.message : "Restore callback failed";
|
|
152
|
-
console.log("[RevenueCat] Restore completion callback failed:", message);
|
|
153
|
-
}
|
|
154
136
|
}
|
|
155
137
|
}
|