@umituz/react-native-subscription 2.37.40 → 2.37.42
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 +2 -4
- package/src/domains/credits/application/DeductCreditsCommand.ts +5 -3
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +6 -2
- package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +19 -1
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.37.
|
|
3
|
+
"version": "2.37.42",
|
|
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",
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"zustand": ">=5.0.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@expo/vector-icons": "^15.0.3",
|
|
49
48
|
"@gorhom/bottom-sheet": "^5.2.8",
|
|
50
49
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
51
50
|
"@react-native-community/datetimepicker": "^8.6.0",
|
|
@@ -105,6 +104,5 @@
|
|
|
105
104
|
"src",
|
|
106
105
|
"README.md",
|
|
107
106
|
"LICENSE"
|
|
108
|
-
]
|
|
109
|
-
"dependencies": {}
|
|
107
|
+
]
|
|
110
108
|
}
|
|
@@ -20,12 +20,13 @@ export async function deductCreditsOperation(
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const MAX_SINGLE_DEDUCTION = 10000;
|
|
24
|
+
if (cost <= 0 || !Number.isFinite(cost) || cost > MAX_SINGLE_DEDUCTION) {
|
|
24
25
|
return {
|
|
25
26
|
success: false,
|
|
26
27
|
remainingCredits: null,
|
|
27
28
|
error: {
|
|
28
|
-
message:
|
|
29
|
+
message: `Cost must be a positive finite number not exceeding ${MAX_SINGLE_DEDUCTION}`,
|
|
29
30
|
code: 'INVALID_AMOUNT'
|
|
30
31
|
}
|
|
31
32
|
};
|
|
@@ -39,7 +40,8 @@ export async function deductCreditsOperation(
|
|
|
39
40
|
throw new Error(CREDIT_ERROR_CODES.NO_CREDITS);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
+
const rawCredits = docSnap.data().credits;
|
|
44
|
+
const current = typeof rawCredits === "number" && Number.isFinite(rawCredits) ? rawCredits : 0;
|
|
43
45
|
if (current < cost) {
|
|
44
46
|
throw new Error(CREDIT_ERROR_CODES.CREDITS_EXHAUSTED);
|
|
45
47
|
}
|
|
@@ -15,8 +15,12 @@ export class SubscriptionSyncProcessor {
|
|
|
15
15
|
) {}
|
|
16
16
|
|
|
17
17
|
private async getCreditsUserId(revenueCatUserId: string): Promise<string> {
|
|
18
|
-
if (!revenueCatUserId || revenueCatUserId.length === 0) {
|
|
19
|
-
|
|
18
|
+
if (!revenueCatUserId || revenueCatUserId.trim().length === 0) {
|
|
19
|
+
const anonymousId = await this.getAnonymousUserId();
|
|
20
|
+
if (!anonymousId || anonymousId.trim().length === 0) {
|
|
21
|
+
throw new Error("[SubscriptionSyncProcessor] Cannot resolve credits userId: both revenueCatUserId and anonymousUserId are empty");
|
|
22
|
+
}
|
|
23
|
+
return anonymousId;
|
|
20
24
|
}
|
|
21
25
|
return revenueCatUserId;
|
|
22
26
|
}
|
|
@@ -10,9 +10,12 @@ interface SavedPurchaseState {
|
|
|
10
10
|
pkg: PurchasesPackage;
|
|
11
11
|
source: PurchaseSource;
|
|
12
12
|
timestamp: number;
|
|
13
|
+
sessionId: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const SAVED_PURCHASE_EXPIRY_MS =
|
|
16
|
+
const SAVED_PURCHASE_EXPIRY_MS = 2 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
let currentSessionId = "";
|
|
16
19
|
|
|
17
20
|
class AuthPurchaseStateManager {
|
|
18
21
|
private authProvider: PurchaseAuthProvider | null = null;
|
|
@@ -26,11 +29,16 @@ class AuthPurchaseStateManager {
|
|
|
26
29
|
return this.authProvider;
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
setSessionId(sessionId: string): void {
|
|
33
|
+
currentSessionId = sessionId;
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
savePurchase(pkg: PurchasesPackage, source: PurchaseSource): void {
|
|
30
37
|
this.savedPurchaseState = {
|
|
31
38
|
pkg,
|
|
32
39
|
source,
|
|
33
40
|
timestamp: Date.now(),
|
|
41
|
+
sessionId: currentSessionId,
|
|
34
42
|
};
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -39,6 +47,11 @@ class AuthPurchaseStateManager {
|
|
|
39
47
|
return null;
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
if (this.savedPurchaseState.sessionId !== currentSessionId) {
|
|
51
|
+
this.savedPurchaseState = null;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
const isExpired = Date.now() - this.savedPurchaseState.timestamp > SAVED_PURCHASE_EXPIRY_MS;
|
|
43
56
|
if (isExpired) {
|
|
44
57
|
this.savedPurchaseState = null;
|
|
@@ -55,9 +68,14 @@ class AuthPurchaseStateManager {
|
|
|
55
68
|
this.savedPurchaseState = null;
|
|
56
69
|
}
|
|
57
70
|
|
|
71
|
+
onUserChanged(): void {
|
|
72
|
+
this.savedPurchaseState = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
58
75
|
cleanup(): void {
|
|
59
76
|
this.authProvider = null;
|
|
60
77
|
this.savedPurchaseState = null;
|
|
78
|
+
currentSessionId = "";
|
|
61
79
|
}
|
|
62
80
|
}
|
|
63
81
|
|
|
@@ -38,11 +38,16 @@ export const useAuthAwarePurchase = (
|
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
try {
|
|
42
|
+
const result = await purchasePackage(saved.pkg);
|
|
43
|
+
if (result) {
|
|
44
|
+
authPurchaseStateManager.clearSavedPurchase();
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
} catch {
|
|
43
48
|
authPurchaseStateManager.clearSavedPurchase();
|
|
49
|
+
return false;
|
|
44
50
|
}
|
|
45
|
-
return result;
|
|
46
51
|
}, [purchasePackage]);
|
|
47
52
|
|
|
48
53
|
useEffect(() => {
|
|
@@ -58,7 +63,7 @@ export const useAuthAwarePurchase = (
|
|
|
58
63
|
isExecutingSavedRef.current = false;
|
|
59
64
|
});
|
|
60
65
|
}
|
|
61
|
-
});
|
|
66
|
+
}, [executeSavedPurchase]);
|
|
62
67
|
|
|
63
68
|
const handlePurchase = useCallback(
|
|
64
69
|
async (pkg: PurchasesPackage, source?: PurchaseSource): Promise<boolean> => {
|