@umituz/react-native-subscription 2.13.16 → 2.13.18
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/components/PaywallModal.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/presentation/hooks/usePremium.ts +6 -6
- package/src/presentation/screens/components/SubscriptionHeader.tsx +3 -0
- package/src/revenuecat/infrastructure/handlers/PackageHandler.ts +24 -6
- package/src/revenuecat/infrastructure/managers/SubscriptionManager.ts +9 -9
- package/src/revenuecat/presentation/hooks/usePurchasePackage.ts +50 -22
- package/src/revenuecat/presentation/hooks/useRestorePurchase.ts +57 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.18",
|
|
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",
|
|
@@ -227,7 +227,7 @@ const styles = StyleSheet.create({
|
|
|
227
227
|
modalContent: { padding: 0, borderWidth: 0, overflow: "hidden" },
|
|
228
228
|
container: { flex: 1 },
|
|
229
229
|
closeBtn: { position: "absolute", top: 12, right: 12, width: 32, height: 32, borderRadius: 16, justifyContent: "center", alignItems: "center", zIndex: 10 },
|
|
230
|
-
scroll: { flexGrow: 1, padding: 16, paddingTop: 48 },
|
|
230
|
+
scroll: { flexGrow: 1, padding: 16, paddingTop: 48, paddingBottom: 32 },
|
|
231
231
|
header: { alignItems: "center", marginBottom: 12 },
|
|
232
232
|
title: { fontWeight: "700", textAlign: "center", marginBottom: 4 },
|
|
233
233
|
subtitle: { textAlign: "center" },
|
package/src/index.ts
CHANGED
|
@@ -391,6 +391,8 @@ export {
|
|
|
391
391
|
type PremiumStatus,
|
|
392
392
|
} from "./revenuecat/infrastructure/managers/SubscriptionManager";
|
|
393
393
|
|
|
394
|
+
export type { RestoreResultInfo } from "./revenuecat/infrastructure/handlers/PackageHandler";
|
|
395
|
+
|
|
394
396
|
// =============================================================================
|
|
395
397
|
// REVENUECAT - Hooks
|
|
396
398
|
// =============================================================================
|
|
@@ -89,11 +89,11 @@ export const usePremium = (userId?: string): UsePremiumResult => {
|
|
|
89
89
|
const handlePurchase = useCallback(
|
|
90
90
|
async (pkg: PurchasesPackage): Promise<boolean> => {
|
|
91
91
|
try {
|
|
92
|
-
const
|
|
93
|
-
return success;
|
|
92
|
+
const result = await purchaseMutation.mutateAsync(pkg);
|
|
93
|
+
return result.success;
|
|
94
94
|
} catch (error) {
|
|
95
95
|
if (__DEV__) {
|
|
96
|
-
console.error(
|
|
96
|
+
console.error("[usePremium] Purchase failed:", error);
|
|
97
97
|
}
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
@@ -104,11 +104,11 @@ export const usePremium = (userId?: string): UsePremiumResult => {
|
|
|
104
104
|
// Restore handler with proper error handling
|
|
105
105
|
const handleRestore = useCallback(async (): Promise<boolean> => {
|
|
106
106
|
try {
|
|
107
|
-
const
|
|
108
|
-
return success;
|
|
107
|
+
const result = await restoreMutation.mutateAsync();
|
|
108
|
+
return result.success;
|
|
109
109
|
} catch (error) {
|
|
110
110
|
if (__DEV__) {
|
|
111
|
-
console.error(
|
|
111
|
+
console.error("[usePremium] Restore failed:", error);
|
|
112
112
|
}
|
|
113
113
|
return false;
|
|
114
114
|
}
|
|
@@ -153,10 +153,13 @@ const styles = StyleSheet.create({
|
|
|
153
153
|
flexDirection: "row",
|
|
154
154
|
justifyContent: "space-between",
|
|
155
155
|
alignItems: "center",
|
|
156
|
+
gap: 16,
|
|
156
157
|
},
|
|
157
158
|
label: {
|
|
159
|
+
flex: 1,
|
|
158
160
|
},
|
|
159
161
|
value: {
|
|
160
162
|
fontWeight: "600",
|
|
163
|
+
textAlign: "right",
|
|
161
164
|
},
|
|
162
165
|
});
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
7
|
-
import type { IRevenueCatService } from
|
|
8
|
-
import { getPremiumEntitlement } from
|
|
7
|
+
import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
|
|
8
|
+
import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
|
|
9
9
|
import {
|
|
10
10
|
trackPackageError,
|
|
11
11
|
addPackageBreadcrumb,
|
|
@@ -17,6 +17,11 @@ export interface PremiumStatus {
|
|
|
17
17
|
expirationDate: Date | null;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export interface RestoreResultInfo {
|
|
21
|
+
success: boolean;
|
|
22
|
+
productId: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export class PackageHandler {
|
|
21
26
|
constructor(
|
|
22
27
|
private service: IRevenueCatService | null,
|
|
@@ -89,22 +94,35 @@ export class PackageHandler {
|
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
async restore(userId: string): Promise<
|
|
97
|
+
async restore(userId: string): Promise<RestoreResultInfo> {
|
|
93
98
|
if (!this.service?.isInitialized()) {
|
|
94
99
|
trackPackageWarning("subscription", "Restore attempted but not initialized", {});
|
|
95
|
-
return false;
|
|
100
|
+
return { success: false, productId: null };
|
|
96
101
|
}
|
|
97
102
|
|
|
98
103
|
try {
|
|
99
104
|
const result = await this.service.restorePurchases(userId);
|
|
100
|
-
|
|
105
|
+
|
|
106
|
+
// Extract product ID from active entitlement
|
|
107
|
+
let productId: string | null = null;
|
|
108
|
+
if (result.success && result.customerInfo) {
|
|
109
|
+
const entitlement = getPremiumEntitlement(
|
|
110
|
+
result.customerInfo,
|
|
111
|
+
this.entitlementId
|
|
112
|
+
);
|
|
113
|
+
if (entitlement) {
|
|
114
|
+
productId = entitlement.productIdentifier;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { success: result.success, productId };
|
|
101
119
|
} catch (error) {
|
|
102
120
|
trackPackageError(error instanceof Error ? error : new Error(String(error)), {
|
|
103
121
|
packageName: "subscription",
|
|
104
122
|
operation: "restore",
|
|
105
123
|
userId,
|
|
106
124
|
});
|
|
107
|
-
return false;
|
|
125
|
+
return { success: false, productId: null };
|
|
108
126
|
}
|
|
109
127
|
}
|
|
110
128
|
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
8
|
-
import type { RevenueCatConfig } from
|
|
9
|
-
import type { IRevenueCatService } from
|
|
10
|
-
import { initializeRevenueCatService, getRevenueCatService } from
|
|
11
|
-
import { UserIdProvider } from
|
|
12
|
-
import { InitializationCache } from
|
|
13
|
-
import { PackageHandler } from
|
|
14
|
-
import type { PremiumStatus } from
|
|
8
|
+
import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
|
|
9
|
+
import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
|
|
10
|
+
import { initializeRevenueCatService, getRevenueCatService } from "../services/RevenueCatService";
|
|
11
|
+
import { UserIdProvider } from "../utils/UserIdProvider";
|
|
12
|
+
import { InitializationCache } from "../utils/InitializationCache";
|
|
13
|
+
import { PackageHandler } from "../handlers/PackageHandler";
|
|
14
|
+
import type { PremiumStatus, RestoreResultInfo } from "../handlers/PackageHandler";
|
|
15
15
|
import {
|
|
16
16
|
trackPackageError,
|
|
17
17
|
addPackageBreadcrumb,
|
|
@@ -138,10 +138,10 @@ class SubscriptionManagerImpl {
|
|
|
138
138
|
return this.packageHandler!.purchase(pkg, userId);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
async restore(): Promise<
|
|
141
|
+
async restore(): Promise<RestoreResultInfo> {
|
|
142
142
|
this.ensureConfigured();
|
|
143
143
|
const userId = this.initCache.getCurrentUserId();
|
|
144
|
-
if (!userId) return false;
|
|
144
|
+
if (!userId) return { success: false, productId: null };
|
|
145
145
|
return this.packageHandler!.restore(userId);
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -1,37 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purchase Package Hook
|
|
3
3
|
* TanStack mutation for purchasing subscription packages
|
|
4
|
+
* Automatically initializes credits after successful purchase
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
8
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
8
|
-
import { SubscriptionManager } from
|
|
9
|
+
import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
|
|
9
10
|
import {
|
|
10
11
|
trackPackageError,
|
|
11
12
|
addPackageBreadcrumb,
|
|
12
13
|
} from "@umituz/react-native-sentry";
|
|
13
14
|
import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
|
|
14
15
|
import { creditsQueryKeys } from "../../../presentation/hooks/useCredits";
|
|
16
|
+
import { getCreditsRepository } from "../../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
17
|
+
|
|
18
|
+
interface PurchaseResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
productId: string;
|
|
21
|
+
}
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Purchase a subscription package
|
|
25
|
+
* After successful purchase, automatically initializes credits
|
|
18
26
|
*/
|
|
19
27
|
export const usePurchasePackage = (userId: string | undefined) => {
|
|
20
28
|
const queryClient = useQueryClient();
|
|
21
29
|
|
|
22
30
|
return useMutation({
|
|
23
|
-
mutationFn: async (pkg: PurchasesPackage) => {
|
|
31
|
+
mutationFn: async (pkg: PurchasesPackage): Promise<PurchaseResult> => {
|
|
24
32
|
if (!userId) {
|
|
25
33
|
throw new Error("User not authenticated");
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
packageId: pkg.identifier,
|
|
30
|
-
userId,
|
|
31
|
-
});
|
|
36
|
+
const productId = pkg.product.identifier;
|
|
32
37
|
|
|
33
|
-
addPackageBreadcrumb("subscription", "Purchase
|
|
38
|
+
addPackageBreadcrumb("subscription", "Purchase started", {
|
|
34
39
|
packageId: pkg.identifier,
|
|
40
|
+
productId,
|
|
35
41
|
userId,
|
|
36
42
|
});
|
|
37
43
|
|
|
@@ -40,35 +46,57 @@ export const usePurchasePackage = (userId: string | undefined) => {
|
|
|
40
46
|
if (success) {
|
|
41
47
|
addPackageBreadcrumb("subscription", "Purchase success", {
|
|
42
48
|
packageId: pkg.identifier,
|
|
49
|
+
productId,
|
|
43
50
|
userId,
|
|
44
51
|
});
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// Initialize credits immediately after purchase
|
|
54
|
+
const repository = getCreditsRepository();
|
|
55
|
+
const creditResult = await repository.initializeCredits(
|
|
48
56
|
userId,
|
|
49
|
-
|
|
57
|
+
pkg.identifier,
|
|
58
|
+
productId
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (creditResult.success) {
|
|
62
|
+
addPackageBreadcrumb("subscription", "Credits initialized", {
|
|
63
|
+
productId,
|
|
64
|
+
userId,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Update cache immediately for instant UI update
|
|
68
|
+
if (creditResult.data) {
|
|
69
|
+
queryClient.setQueryData(
|
|
70
|
+
creditsQueryKeys.user(userId),
|
|
71
|
+
creditResult.data
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
addPackageBreadcrumb("subscription", "Credits initialization failed", {
|
|
76
|
+
productId,
|
|
77
|
+
userId,
|
|
78
|
+
error: creditResult.error?.message,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
50
81
|
} else {
|
|
51
82
|
addPackageBreadcrumb("subscription", "Purchase cancelled", {
|
|
52
83
|
packageId: pkg.identifier,
|
|
53
84
|
userId,
|
|
54
85
|
});
|
|
55
|
-
|
|
56
|
-
addPackageBreadcrumb("subscription", "Purchase mutation failed", {
|
|
57
|
-
packageId: pkg.identifier,
|
|
58
|
-
userId,
|
|
59
|
-
});
|
|
60
86
|
}
|
|
61
87
|
|
|
62
|
-
return success;
|
|
88
|
+
return { success, productId };
|
|
63
89
|
},
|
|
64
|
-
onSuccess: () => {
|
|
65
|
-
|
|
66
|
-
queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
|
|
67
|
-
});
|
|
68
|
-
if (userId) {
|
|
90
|
+
onSuccess: (result) => {
|
|
91
|
+
if (result.success) {
|
|
69
92
|
queryClient.invalidateQueries({
|
|
70
|
-
queryKey:
|
|
93
|
+
queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
|
|
71
94
|
});
|
|
95
|
+
if (userId) {
|
|
96
|
+
queryClient.invalidateQueries({
|
|
97
|
+
queryKey: creditsQueryKeys.user(userId),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
72
100
|
}
|
|
73
101
|
},
|
|
74
102
|
onError: (error) => {
|
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Restore Purchase Hook
|
|
3
3
|
* TanStack mutation for restoring previous purchases
|
|
4
|
+
* Automatically initializes credits after successful restore
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
|
-
import { SubscriptionManager } from
|
|
8
|
+
import { SubscriptionManager } from "../../infrastructure/managers/SubscriptionManager";
|
|
8
9
|
import {
|
|
9
10
|
trackPackageError,
|
|
10
11
|
addPackageBreadcrumb,
|
|
11
12
|
} from "@umituz/react-native-sentry";
|
|
12
13
|
import { SUBSCRIPTION_QUERY_KEYS } from "./subscriptionQueryKeys";
|
|
14
|
+
import { creditsQueryKeys } from "../../../presentation/hooks/useCredits";
|
|
15
|
+
import { getCreditsRepository } from "../../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
16
|
+
|
|
17
|
+
interface RestoreResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
productId: string | null;
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
/**
|
|
15
23
|
* Restore previous purchases
|
|
24
|
+
* After successful restore, automatically initializes credits
|
|
16
25
|
*/
|
|
17
26
|
export const useRestorePurchase = (userId: string | undefined) => {
|
|
18
27
|
const queryClient = useQueryClient();
|
|
19
28
|
|
|
20
29
|
return useMutation({
|
|
21
|
-
mutationFn: async () => {
|
|
30
|
+
mutationFn: async (): Promise<RestoreResult> => {
|
|
22
31
|
if (!userId) {
|
|
23
32
|
throw new Error("User not authenticated");
|
|
24
33
|
}
|
|
@@ -27,32 +36,63 @@ export const useRestorePurchase = (userId: string | undefined) => {
|
|
|
27
36
|
userId,
|
|
28
37
|
});
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
userId,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const success = await SubscriptionManager.restore();
|
|
39
|
+
const result = await SubscriptionManager.restore();
|
|
35
40
|
|
|
36
|
-
if (success) {
|
|
41
|
+
if (result.success) {
|
|
37
42
|
addPackageBreadcrumb("subscription", "Restore success", {
|
|
38
43
|
userId,
|
|
44
|
+
productId: result.productId,
|
|
39
45
|
});
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
// Initialize credits if we have a product ID
|
|
48
|
+
if (result.productId) {
|
|
49
|
+
const repository = getCreditsRepository();
|
|
50
|
+
const creditResult = await repository.initializeCredits(
|
|
51
|
+
userId,
|
|
52
|
+
undefined,
|
|
53
|
+
result.productId
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (creditResult.success) {
|
|
57
|
+
addPackageBreadcrumb("subscription", "Credits initialized after restore", {
|
|
58
|
+
productId: result.productId,
|
|
59
|
+
userId,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Update cache immediately for instant UI update
|
|
63
|
+
if (creditResult.data) {
|
|
64
|
+
queryClient.setQueryData(
|
|
65
|
+
creditsQueryKeys.user(userId),
|
|
66
|
+
creditResult.data
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
addPackageBreadcrumb("subscription", "Credits initialization failed after restore", {
|
|
71
|
+
productId: result.productId,
|
|
72
|
+
userId,
|
|
73
|
+
error: creditResult.error?.message,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
44
77
|
} else {
|
|
45
|
-
addPackageBreadcrumb("subscription", "Restore
|
|
78
|
+
addPackageBreadcrumb("subscription", "Restore failed - no premium found", {
|
|
46
79
|
userId,
|
|
47
80
|
});
|
|
48
81
|
}
|
|
49
82
|
|
|
50
|
-
return
|
|
83
|
+
return result;
|
|
51
84
|
},
|
|
52
|
-
onSuccess: () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
onSuccess: (result) => {
|
|
86
|
+
if (result.success) {
|
|
87
|
+
queryClient.invalidateQueries({
|
|
88
|
+
queryKey: SUBSCRIPTION_QUERY_KEYS.packages,
|
|
89
|
+
});
|
|
90
|
+
if (userId) {
|
|
91
|
+
queryClient.invalidateQueries({
|
|
92
|
+
queryKey: creditsQueryKeys.user(userId),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
56
96
|
},
|
|
57
97
|
onError: (error) => {
|
|
58
98
|
trackPackageError(
|