@umituz/react-native-subscription 2.44.1 → 2.45.1
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/paywall/hooks/usePaywallActions.ts +65 -61
- package/src/domains/paywall/hooks/usePaywallActions.utils.ts +39 -0
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +84 -296
- package/src/domains/subscription/application/sync/CreditDocumentOperations.ts +64 -0
- package/src/domains/subscription/application/sync/PurchaseSyncHandler.ts +83 -0
- package/src/domains/subscription/application/sync/RenewalSyncHandler.ts +69 -0
- package/src/domains/subscription/application/sync/StatusChangeSyncHandler.ts +57 -0
- package/src/domains/subscription/application/sync/SyncProcessorLogger.ts +120 -0
- package/src/domains/subscription/application/sync/UserIdResolver.ts +31 -0
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.parts.tsx +201 -0
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackScreen.tsx +89 -185
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.1",
|
|
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",
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paywall Actions Hook
|
|
3
|
+
*
|
|
4
|
+
* Handles purchase and restore operations with premium verification.
|
|
5
|
+
* Ref management and success checking extracted to utilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
1
8
|
import { useState, useCallback, useRef, useMemo } from "react";
|
|
2
9
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
3
|
-
import
|
|
4
|
-
import { useSubscriptionStatus } from "../../subscription/presentation/useSubscriptionStatus";
|
|
5
|
-
import { useCredits } from "../../credits/presentation/useCredits";
|
|
10
|
+
import { usePremiumVerification } from "./usePaywallActions.utils";
|
|
6
11
|
|
|
7
12
|
interface UsePaywallActionsParams {
|
|
8
13
|
packages?: PurchasesPackage[];
|
|
9
14
|
purchasePackage: (pkg: PurchasesPackage) => Promise<boolean>;
|
|
10
15
|
restorePurchase: () => Promise<boolean>;
|
|
11
|
-
source?: PurchaseSource
|
|
16
|
+
source?: string; // PurchaseSource
|
|
12
17
|
onPurchaseSuccess?: () => void;
|
|
13
18
|
onPurchaseError?: (error: Error | string) => void;
|
|
14
19
|
onAuthRequired?: () => void;
|
|
@@ -30,34 +35,42 @@ export function usePaywallActions({
|
|
|
30
35
|
const isProcessingRef = useRef(isProcessing);
|
|
31
36
|
isProcessingRef.current = isProcessing;
|
|
32
37
|
|
|
33
|
-
const {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
const { verifyPremiumStatus } = usePremiumVerification();
|
|
39
|
+
|
|
40
|
+
// Ref management
|
|
41
|
+
const callbacksRef = useRef({
|
|
42
|
+
purchasePackage,
|
|
43
|
+
restorePurchase,
|
|
44
|
+
onPurchaseSuccess,
|
|
45
|
+
onPurchaseError,
|
|
46
|
+
onAuthRequired,
|
|
47
|
+
onClose,
|
|
48
|
+
packages,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Update refs
|
|
52
|
+
callbacksRef.current = {
|
|
53
|
+
purchasePackage,
|
|
54
|
+
restorePurchase,
|
|
55
|
+
onPurchaseSuccess,
|
|
56
|
+
onPurchaseError,
|
|
57
|
+
onAuthRequired,
|
|
58
|
+
onClose,
|
|
59
|
+
packages,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ─────────────────────────────────────────────────────────────
|
|
63
|
+
// PURCHASE HANDLER
|
|
64
|
+
// ─────────────────────────────────────────────────────────────
|
|
51
65
|
|
|
52
66
|
const handlePurchase = useCallback(async () => {
|
|
53
67
|
const currentSelectedId = selectedPlanId;
|
|
54
68
|
if (!currentSelectedId) return;
|
|
55
|
-
|
|
56
69
|
if (isProcessingRef.current) return;
|
|
57
70
|
|
|
58
|
-
const pkg =
|
|
71
|
+
const pkg = callbacksRef.current.packages.find((p) => p.product.identifier === currentSelectedId);
|
|
59
72
|
if (!pkg) {
|
|
60
|
-
|
|
73
|
+
callbacksRef.current.onPurchaseError?.(new Error(`Package not found: ${currentSelectedId}`));
|
|
61
74
|
return;
|
|
62
75
|
}
|
|
63
76
|
|
|
@@ -70,7 +83,7 @@ export function usePaywallActions({
|
|
|
70
83
|
setIsProcessing(true);
|
|
71
84
|
|
|
72
85
|
try {
|
|
73
|
-
const success = await
|
|
86
|
+
const success = await callbacksRef.current.purchasePackage(pkg);
|
|
74
87
|
|
|
75
88
|
if (__DEV__) {
|
|
76
89
|
console.log('[usePaywallActions] ✅ Purchase completed', { success });
|
|
@@ -78,36 +91,17 @@ export function usePaywallActions({
|
|
|
78
91
|
|
|
79
92
|
let isActuallySuccessful = success === true;
|
|
80
93
|
|
|
94
|
+
// Fallback verification if success is undefined
|
|
81
95
|
if (success === undefined) {
|
|
82
|
-
|
|
83
|
-
console.log('[usePaywallActions] 🔍 Checking premium status as fallback...');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const [statusResult, creditsResult] = await Promise.all([
|
|
87
|
-
refetchStatus(),
|
|
88
|
-
refetchCredits()
|
|
89
|
-
]);
|
|
90
|
-
|
|
91
|
-
const isSubscriptionPremium = statusResult.data?.isPremium ?? false;
|
|
92
|
-
const isCreditsPremium = creditsResult.data?.isPremium ?? false;
|
|
93
|
-
|
|
94
|
-
isActuallySuccessful = isSubscriptionPremium || isCreditsPremium;
|
|
95
|
-
|
|
96
|
-
if (__DEV__) {
|
|
97
|
-
console.log('[usePaywallActions] 📊 Fallback check result:', {
|
|
98
|
-
isSubscriptionPremium,
|
|
99
|
-
isCreditsPremium,
|
|
100
|
-
isActuallySuccessful
|
|
101
|
-
});
|
|
102
|
-
}
|
|
96
|
+
isActuallySuccessful = await verifyPremiumStatus();
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
if (isActuallySuccessful) {
|
|
106
100
|
if (__DEV__) {
|
|
107
101
|
console.log('[usePaywallActions] 🎉 Purchase successful, closing paywall');
|
|
108
102
|
}
|
|
109
|
-
|
|
110
|
-
|
|
103
|
+
callbacksRef.current.onPurchaseSuccess?.();
|
|
104
|
+
callbacksRef.current.onClose?.();
|
|
111
105
|
} else {
|
|
112
106
|
if (__DEV__) {
|
|
113
107
|
console.warn('[usePaywallActions] ⚠️ Purchase did not indicate success');
|
|
@@ -117,11 +111,15 @@ export function usePaywallActions({
|
|
|
117
111
|
if (__DEV__) {
|
|
118
112
|
console.error('[usePaywallActions] ❌ Purchase error:', error);
|
|
119
113
|
}
|
|
120
|
-
|
|
114
|
+
callbacksRef.current.onPurchaseError?.(error instanceof Error ? error : new Error(String(error)));
|
|
121
115
|
} finally {
|
|
122
116
|
setIsProcessing(false);
|
|
123
117
|
}
|
|
124
|
-
}, [selectedPlanId,
|
|
118
|
+
}, [selectedPlanId, verifyPremiumStatus]);
|
|
119
|
+
|
|
120
|
+
// ─────────────────────────────────────────────────────────────
|
|
121
|
+
// RESTORE HANDLER
|
|
122
|
+
// ─────────────────────────────────────────────────────────────
|
|
125
123
|
|
|
126
124
|
const handleRestore = useCallback(async () => {
|
|
127
125
|
if (isProcessingRef.current) return;
|
|
@@ -131,8 +129,9 @@ export function usePaywallActions({
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
setIsProcessing(true);
|
|
132
|
+
|
|
134
133
|
try {
|
|
135
|
-
const success = await
|
|
134
|
+
const success = await callbacksRef.current.restorePurchase();
|
|
136
135
|
|
|
137
136
|
if (__DEV__) {
|
|
138
137
|
console.log('[usePaywallActions] ✅ Restore completed', { success });
|
|
@@ -140,20 +139,17 @@ export function usePaywallActions({
|
|
|
140
139
|
|
|
141
140
|
let isActuallySuccessful = success === true;
|
|
142
141
|
|
|
142
|
+
// Fallback verification if success is undefined
|
|
143
143
|
if (success === undefined) {
|
|
144
|
-
|
|
145
|
-
refetchStatus(),
|
|
146
|
-
refetchCredits()
|
|
147
|
-
]);
|
|
148
|
-
isActuallySuccessful = (statusResult.data?.isPremium ?? false) || (creditsResult.data?.isPremium ?? false);
|
|
144
|
+
isActuallySuccessful = await verifyPremiumStatus();
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
if (isActuallySuccessful) {
|
|
152
148
|
if (__DEV__) {
|
|
153
149
|
console.log('[usePaywallActions] 🎉 Restore successful, closing paywall');
|
|
154
150
|
}
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
callbacksRef.current.onPurchaseSuccess?.();
|
|
152
|
+
callbacksRef.current.onClose?.();
|
|
157
153
|
} else {
|
|
158
154
|
if (__DEV__) {
|
|
159
155
|
console.warn('[usePaywallActions] ⚠️ Restore did not indicate success');
|
|
@@ -163,11 +159,15 @@ export function usePaywallActions({
|
|
|
163
159
|
if (__DEV__) {
|
|
164
160
|
console.error('[usePaywallActions] ❌ Restore error:', error);
|
|
165
161
|
}
|
|
166
|
-
|
|
162
|
+
callbacksRef.current.onPurchaseError?.(error instanceof Error ? error : new Error(String(error)));
|
|
167
163
|
} finally {
|
|
168
164
|
setIsProcessing(false);
|
|
169
165
|
}
|
|
170
|
-
}, [
|
|
166
|
+
}, [verifyPremiumStatus]);
|
|
167
|
+
|
|
168
|
+
// ─────────────────────────────────────────────────────────────
|
|
169
|
+
// RESET
|
|
170
|
+
// ─────────────────────────────────────────────────────────────
|
|
171
171
|
|
|
172
172
|
const resetState = useCallback(() => {
|
|
173
173
|
if (__DEV__) {
|
|
@@ -177,6 +177,10 @@ export function usePaywallActions({
|
|
|
177
177
|
setIsProcessing(false);
|
|
178
178
|
}, []);
|
|
179
179
|
|
|
180
|
+
// ─────────────────────────────────────────────────────────────
|
|
181
|
+
// RETURN
|
|
182
|
+
// ─────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
180
184
|
return useMemo(() => ({
|
|
181
185
|
selectedPlanId,
|
|
182
186
|
setSelectedPlanId,
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paywall Actions Utilities
|
|
3
|
+
* Helper functions for paywall purchase/restore operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useSubscriptionStatus } from "../../subscription/presentation/useSubscriptionStatus";
|
|
7
|
+
import { useCredits } from "../../credits/presentation/useCredits";
|
|
8
|
+
|
|
9
|
+
export function usePremiumVerification() {
|
|
10
|
+
const { refetch: refetchStatus } = useSubscriptionStatus();
|
|
11
|
+
const { refetch: refetchCredits } = useCredits();
|
|
12
|
+
|
|
13
|
+
const verifyPremiumStatus = async (): Promise<boolean> => {
|
|
14
|
+
if (__DEV__) {
|
|
15
|
+
console.log('[PremiumVerification] 🔍 Checking premium status as fallback...');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const [statusResult, creditsResult] = await Promise.all([
|
|
19
|
+
refetchStatus(),
|
|
20
|
+
refetchCredits()
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const isSubscriptionPremium = statusResult.data?.isPremium ?? false;
|
|
24
|
+
const isCreditsPremium = creditsResult.data?.isPremium ?? false;
|
|
25
|
+
const isActuallySuccessful = isSubscriptionPremium || isCreditsPremium;
|
|
26
|
+
|
|
27
|
+
if (__DEV__) {
|
|
28
|
+
console.log('[PremiumVerification] 📊 Fallback check result:', {
|
|
29
|
+
isSubscriptionPremium,
|
|
30
|
+
isCreditsPremium,
|
|
31
|
+
isActuallySuccessful
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return isActuallySuccessful;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return { verifyPremiumStatus };
|
|
39
|
+
}
|