@umituz/react-native-subscription 2.14.82 → 2.14.83
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/PaywallContainer.tsx +26 -159
- package/src/domains/paywall/components/PaywallFeatures.tsx +25 -0
- package/src/domains/paywall/components/PaywallFooter.tsx +46 -93
- package/src/domains/paywall/components/PaywallModal.tsx +14 -99
- package/src/domains/paywall/hooks/usePaywallActions.ts +63 -0
- package/src/index.ts +23 -461
- package/src/infrastructure/repositories/CreditsRepository.ts +43 -177
- package/src/infrastructure/services/SubscriptionInitializer.ts +32 -186
- package/src/presentation/hooks/index.ts +23 -0
- package/src/presentation/hooks/useDeductCredit.ts +22 -148
- package/src/presentation/hooks/useInitializeCredits.ts +57 -0
- package/src/presentation/hooks/usePremiumWithCredits.ts +1 -1
- package/src/revenuecat/index.ts +12 -0
- package/src/utils/index.ts +15 -0
|
@@ -1,212 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credits Repository
|
|
3
|
-
*
|
|
4
|
-
* Firestore operations for user credits management.
|
|
5
|
-
* Extends BaseRepository from @umituz/react-native-firebase.
|
|
6
3
|
*/
|
|
7
4
|
|
|
8
|
-
import {
|
|
9
|
-
doc,
|
|
10
|
-
getDoc,
|
|
11
|
-
runTransaction,
|
|
12
|
-
serverTimestamp,
|
|
13
|
-
type Firestore,
|
|
14
|
-
type Transaction,
|
|
15
|
-
} from "firebase/firestore";
|
|
5
|
+
import { doc, getDoc, runTransaction, serverTimestamp, type Firestore, type Transaction } from "firebase/firestore";
|
|
16
6
|
import { BaseRepository, getFirestore } from "@umituz/react-native-firebase";
|
|
17
|
-
import type {
|
|
18
|
-
CreditType,
|
|
19
|
-
CreditsConfig,
|
|
20
|
-
CreditsResult,
|
|
21
|
-
DeductCreditsResult,
|
|
22
|
-
} from "../../domain/entities/Credits";
|
|
7
|
+
import type { CreditType, CreditsConfig, CreditsResult, DeductCreditsResult } from "../../domain/entities/Credits";
|
|
23
8
|
import type { UserCreditsDocumentRead } from "../models/UserCreditsDocument";
|
|
24
9
|
import { initializeCreditsTransaction } from "../services/CreditsInitializer";
|
|
25
10
|
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
26
11
|
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
27
12
|
|
|
28
|
-
declare const __DEV__: boolean;
|
|
29
|
-
|
|
30
13
|
export class CreditsRepository extends BaseRepository {
|
|
31
|
-
private config: CreditsConfig;
|
|
32
|
-
|
|
33
|
-
constructor(config: CreditsConfig) {
|
|
34
|
-
super();
|
|
35
|
-
this.config = config;
|
|
36
|
-
}
|
|
14
|
+
constructor(private config: CreditsConfig) { super(); }
|
|
37
15
|
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
return doc(db, this.config.collectionName, userId);
|
|
16
|
+
private getRef(db: Firestore, userId: string) {
|
|
17
|
+
return this.config.useUserSubcollection
|
|
18
|
+
? doc(db, "users", userId, "credits", "balance")
|
|
19
|
+
: doc(db, this.config.collectionName, userId);
|
|
44
20
|
}
|
|
45
21
|
|
|
46
22
|
async getCredits(userId: string): Promise<CreditsResult> {
|
|
47
23
|
const db = getFirestore();
|
|
48
|
-
if (!db) {
|
|
49
|
-
return {
|
|
50
|
-
success: false,
|
|
51
|
-
error: { message: "Database not available", code: "DB_NOT_AVAILABLE" },
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
24
|
+
if (!db) return { success: false, error: { message: "No DB", code: "DB_ERR" } };
|
|
55
25
|
try {
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const data = snapshot.data() as UserCreditsDocumentRead;
|
|
64
|
-
return {
|
|
65
|
-
success: true,
|
|
66
|
-
data: {
|
|
67
|
-
textCredits: data.textCredits,
|
|
68
|
-
imageCredits: data.imageCredits,
|
|
69
|
-
purchasedAt: data.purchasedAt?.toDate?.() || new Date(),
|
|
70
|
-
lastUpdatedAt: data.lastUpdatedAt?.toDate?.() || new Date(),
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return {
|
|
75
|
-
success: false,
|
|
76
|
-
error: {
|
|
77
|
-
message: error instanceof Error ? error.message : "Failed to get credits",
|
|
78
|
-
code: "FETCH_FAILED",
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
26
|
+
const snap = await getDoc(this.getRef(db, userId));
|
|
27
|
+
if (!snap.exists()) return { success: true, data: undefined };
|
|
28
|
+
const d = snap.data() as UserCreditsDocumentRead;
|
|
29
|
+
return { success: true, data: { textCredits: d.textCredits, imageCredits: d.imageCredits, purchasedAt: d.purchasedAt?.toDate?.() || new Date(), lastUpdatedAt: d.lastUpdatedAt?.toDate?.() || new Date() } };
|
|
30
|
+
} catch (e: any) { return { success: false, error: { message: e.message, code: "FETCH_ERR" } }; }
|
|
82
31
|
}
|
|
83
32
|
|
|
84
|
-
async initializeCredits(
|
|
85
|
-
userId: string,
|
|
86
|
-
purchaseId?: string,
|
|
87
|
-
productId?: string
|
|
88
|
-
): Promise<CreditsResult> {
|
|
33
|
+
async initializeCredits(userId: string, purchaseId?: string, productId?: string): Promise<CreditsResult> {
|
|
89
34
|
const db = getFirestore();
|
|
90
|
-
if (!db) {
|
|
91
|
-
return {
|
|
92
|
-
success: false,
|
|
93
|
-
error: { message: "Database not available", code: "INIT_FAILED" },
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
35
|
+
if (!db) return { success: false, error: { message: "No DB", code: "INIT_ERR" } };
|
|
97
36
|
try {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Determine credit allocation based on product ID
|
|
101
|
-
let configToUse = this.config;
|
|
102
|
-
|
|
37
|
+
let cfg = { ...this.config };
|
|
103
38
|
if (productId) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (__DEV__) {
|
|
110
|
-
console.log("[CreditsRepository] Credit package detected:", { productId, amount: creditPackageAmount });
|
|
111
|
-
}
|
|
112
|
-
configToUse = {
|
|
113
|
-
...this.config,
|
|
114
|
-
imageCreditLimit: creditPackageAmount,
|
|
115
|
-
textCreditLimit: creditPackageAmount,
|
|
116
|
-
};
|
|
117
|
-
} else {
|
|
118
|
-
// Subscription package: use package type detection
|
|
119
|
-
const packageType = detectPackageType(productId);
|
|
120
|
-
const allocation = getCreditAllocation(packageType);
|
|
121
|
-
|
|
122
|
-
if (allocation) {
|
|
123
|
-
configToUse = {
|
|
124
|
-
...this.config,
|
|
125
|
-
imageCreditLimit: allocation.imageCredits,
|
|
126
|
-
textCreditLimit: allocation.textCredits,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
39
|
+
const amt = this.config.creditPackageAmounts?.[productId];
|
|
40
|
+
if (amt) cfg = { ...cfg, imageCreditLimit: amt, textCreditLimit: amt };
|
|
41
|
+
else {
|
|
42
|
+
const alloc = getCreditAllocation(detectPackageType(productId));
|
|
43
|
+
if (alloc) cfg = { ...cfg, imageCreditLimit: alloc.imageCredits, textCreditLimit: alloc.textCredits };
|
|
129
44
|
}
|
|
130
45
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
creditsRef,
|
|
135
|
-
configToUse,
|
|
136
|
-
purchaseId
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
success: true,
|
|
141
|
-
data: {
|
|
142
|
-
textCredits: result.textCredits,
|
|
143
|
-
imageCredits: result.imageCredits,
|
|
144
|
-
purchasedAt: new Date(),
|
|
145
|
-
lastUpdatedAt: new Date(),
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
} catch (error) {
|
|
149
|
-
return {
|
|
150
|
-
success: false,
|
|
151
|
-
error: {
|
|
152
|
-
message: error instanceof Error ? error.message : "Failed to initialize credits",
|
|
153
|
-
code: "INIT_FAILED",
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}
|
|
46
|
+
const res = await initializeCreditsTransaction(db, this.getRef(db, userId), cfg, purchaseId);
|
|
47
|
+
return { success: true, data: { textCredits: res.textCredits, imageCredits: res.imageCredits, purchasedAt: new Date(), lastUpdatedAt: new Date() } };
|
|
48
|
+
} catch (e: any) { return { success: false, error: { message: e.message, code: "INIT_ERR" } }; }
|
|
157
49
|
}
|
|
158
50
|
|
|
159
|
-
async deductCredit(
|
|
160
|
-
userId: string,
|
|
161
|
-
creditType: CreditType
|
|
162
|
-
): Promise<DeductCreditsResult> {
|
|
51
|
+
async deductCredit(userId: string, type: CreditType): Promise<DeductCreditsResult> {
|
|
163
52
|
const db = getFirestore();
|
|
164
|
-
if (!db) {
|
|
165
|
-
|
|
166
|
-
success: false,
|
|
167
|
-
error: { message: "Database not available", code: "DEDUCT_FAILED" },
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
53
|
+
if (!db) return { success: false, error: { message: "No DB", code: "ERR" } };
|
|
54
|
+
const field = type === "text" ? "textCredits" : "imageCredits";
|
|
171
55
|
try {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (currentCredits <= 0) throw new Error("CREDITS_EXHAUSTED");
|
|
181
|
-
|
|
182
|
-
const updatedCredits = currentCredits - 1;
|
|
183
|
-
transaction.update(creditsRef, {
|
|
184
|
-
[fieldName]: updatedCredits,
|
|
185
|
-
lastUpdatedAt: serverTimestamp(),
|
|
186
|
-
});
|
|
187
|
-
return updatedCredits;
|
|
56
|
+
const remaining = await runTransaction(db, async (tx: Transaction) => {
|
|
57
|
+
const docSnap = await tx.get(this.getRef(db, userId));
|
|
58
|
+
if (!docSnap.exists()) throw new Error("NO_CREDITS");
|
|
59
|
+
const current = docSnap.data()[field] as number;
|
|
60
|
+
if (current <= 0) throw new Error("CREDITS_EXHAUSTED");
|
|
61
|
+
const updated = current - 1;
|
|
62
|
+
tx.update(this.getRef(db, userId), { [field]: updated, lastUpdatedAt: serverTimestamp() });
|
|
63
|
+
return updated;
|
|
188
64
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const code = msg === "NO_CREDITS" || msg === "CREDITS_EXHAUSTED" ? msg : "DEDUCT_FAILED";
|
|
194
|
-
const message = msg === "NO_CREDITS" ? "No credits found" : msg === "CREDITS_EXHAUSTED" ? "Credits exhausted" : msg;
|
|
195
|
-
|
|
196
|
-
return { success: false, error: { message, code } };
|
|
65
|
+
return { success: true, remainingCredits: remaining };
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
const code = e.message === "NO_CREDITS" || e.message === "CREDITS_EXHAUSTED" ? e.message : "DEDUCT_ERR";
|
|
68
|
+
return { success: false, error: { message: e.message, code } };
|
|
197
69
|
}
|
|
198
70
|
}
|
|
199
71
|
|
|
200
|
-
async hasCredits(userId: string,
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
const credits = creditType === "text" ? result.data.textCredits : result.data.imageCredits;
|
|
204
|
-
return credits > 0;
|
|
72
|
+
async hasCredits(userId: string, type: CreditType): Promise<boolean> {
|
|
73
|
+
const res = await this.getCredits(userId);
|
|
74
|
+
return !!(res.success && res.data && (type === "text" ? res.data.textCredits : res.data.imageCredits) > 0);
|
|
205
75
|
}
|
|
206
76
|
}
|
|
207
77
|
|
|
208
|
-
export const createCreditsRepository = (
|
|
209
|
-
config: CreditsConfig
|
|
210
|
-
): CreditsRepository => {
|
|
211
|
-
return new CreditsRepository(config);
|
|
212
|
-
};
|
|
78
|
+
export const createCreditsRepository = (c: CreditsConfig) => new CreditsRepository(c);
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subscription Initializer
|
|
3
|
-
* Single entry point for subscription system initialization
|
|
4
|
-
* Apps just call initializeSubscription with config
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
import { Platform } from "react-native";
|
|
8
|
-
import type { CustomerInfo } from "react-native-purchases";
|
|
9
6
|
import type { CreditsConfig } from "../../domain/entities/Credits";
|
|
10
7
|
import { configureCreditsRepository, getCreditsRepository } from "../repositories/CreditsRepositoryProvider";
|
|
11
8
|
import { SubscriptionManager } from "../../revenuecat/infrastructure/managers/SubscriptionManager";
|
|
@@ -16,213 +13,62 @@ export interface FirebaseAuthLike {
|
|
|
16
13
|
onAuthStateChanged: (callback: (user: { uid: string; isAnonymous: boolean } | null) => void) => () => void;
|
|
17
14
|
}
|
|
18
15
|
|
|
19
|
-
export interface CreditPackageConfig {
|
|
20
|
-
/** Identifier pattern to match credit packages (e.g., "credit") */
|
|
21
|
-
identifierPattern?: string;
|
|
22
|
-
/** Map of productId to credit amounts */
|
|
23
|
-
amounts?: Record<string, number>;
|
|
24
|
-
}
|
|
16
|
+
export interface CreditPackageConfig { identifierPattern?: string; amounts?: Record<string, number>; }
|
|
25
17
|
|
|
26
18
|
export interface SubscriptionInitConfig {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
apiKeyIos?: string;
|
|
31
|
-
/** Android-specific API key (overrides apiKey if provided on Android) */
|
|
32
|
-
apiKeyAndroid?: string;
|
|
33
|
-
testStoreKey?: string;
|
|
34
|
-
entitlementId: string;
|
|
35
|
-
credits: CreditsConfig;
|
|
36
|
-
getAnonymousUserId: () => Promise<string>;
|
|
37
|
-
getFirebaseAuth: () => FirebaseAuthLike | null;
|
|
38
|
-
showAuthModal: () => void;
|
|
39
|
-
/** Callback after credits are updated (for cache invalidation) */
|
|
40
|
-
onCreditsUpdated?: (userId: string) => void;
|
|
41
|
-
/** Credit package configuration for consumable purchases */
|
|
42
|
-
creditPackages?: CreditPackageConfig;
|
|
43
|
-
timeoutMs?: number;
|
|
44
|
-
authStateTimeoutMs?: number;
|
|
19
|
+
apiKey?: string; apiKeyIos?: string; apiKeyAndroid?: string; testStoreKey?: string; entitlementId: string; credits: CreditsConfig;
|
|
20
|
+
getAnonymousUserId: () => Promise<string>; getFirebaseAuth: () => FirebaseAuthLike | null; showAuthModal: () => void;
|
|
21
|
+
onCreditsUpdated?: (userId: string) => void; creditPackages?: CreditPackageConfig; timeoutMs?: number; authStateTimeoutMs?: number;
|
|
45
22
|
}
|
|
46
23
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* This prevents unnecessary logIn calls that trigger Apple Sign In dialog
|
|
50
|
-
*/
|
|
51
|
-
const waitForAuthState = async (
|
|
52
|
-
getFirebaseAuth: () => FirebaseAuthLike | null,
|
|
53
|
-
timeoutMs: number
|
|
54
|
-
): Promise<string | undefined> => {
|
|
55
|
-
const auth = getFirebaseAuth();
|
|
24
|
+
const waitForAuthState = async (getAuth: () => FirebaseAuthLike | null, timeoutMs: number): Promise<string | undefined> => {
|
|
25
|
+
const auth = getAuth();
|
|
56
26
|
if (!auth) return undefined;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Wait for auth state to settle
|
|
64
|
-
return new Promise<string | undefined>((resolve) => {
|
|
65
|
-
const unsubscribe = auth.onAuthStateChanged((user) => {
|
|
66
|
-
unsubscribe();
|
|
67
|
-
resolve(user?.uid || undefined);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// Timeout fallback - don't wait forever
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
unsubscribe();
|
|
73
|
-
resolve(undefined);
|
|
74
|
-
}, timeoutMs);
|
|
27
|
+
if (auth.currentUser) return auth.currentUser.uid;
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const unsub = auth.onAuthStateChanged((u) => { unsub(); resolve(u?.uid); });
|
|
30
|
+
setTimeout(() => { unsub(); resolve(undefined); }, timeoutMs);
|
|
75
31
|
});
|
|
76
32
|
};
|
|
77
33
|
|
|
78
|
-
|
|
79
|
-
* Check if a product is a credit package
|
|
80
|
-
*/
|
|
81
|
-
const isCreditPackage = (productId: string, pattern?: string): boolean => {
|
|
82
|
-
const patternToUse = pattern || "credit";
|
|
83
|
-
return productId.toLowerCase().includes(patternToUse.toLowerCase());
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
export const initializeSubscription = async (
|
|
88
|
-
config: SubscriptionInitConfig,
|
|
89
|
-
): Promise<void> => {
|
|
90
|
-
const {
|
|
91
|
-
apiKey,
|
|
92
|
-
apiKeyIos,
|
|
93
|
-
apiKeyAndroid,
|
|
94
|
-
testStoreKey,
|
|
95
|
-
entitlementId,
|
|
96
|
-
credits,
|
|
97
|
-
getAnonymousUserId,
|
|
98
|
-
getFirebaseAuth,
|
|
99
|
-
showAuthModal,
|
|
100
|
-
onCreditsUpdated,
|
|
101
|
-
creditPackages,
|
|
102
|
-
timeoutMs = 10000,
|
|
103
|
-
authStateTimeoutMs = 2000,
|
|
104
|
-
} = config;
|
|
105
|
-
|
|
106
|
-
// Resolve API key based on platform
|
|
107
|
-
const resolvedApiKey = Platform.OS === "ios"
|
|
108
|
-
? (apiKeyIos || apiKey || "")
|
|
109
|
-
: (apiKeyAndroid || apiKey || "");
|
|
110
|
-
|
|
111
|
-
if (!resolvedApiKey) {
|
|
112
|
-
throw new Error("RevenueCat API key is required");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Merge credit package amounts into credits config
|
|
116
|
-
const creditsConfigWithPackages = {
|
|
117
|
-
...credits,
|
|
118
|
-
creditPackageAmounts: creditPackages?.amounts,
|
|
119
|
-
};
|
|
120
|
-
configureCreditsRepository(creditsConfigWithPackages);
|
|
34
|
+
const isCreditPkg = (id: string, pat?: string) => id.toLowerCase().includes((pat || "credit").toLowerCase());
|
|
121
35
|
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
if (creditPackages?.identifierPattern) {
|
|
125
|
-
consumableIdentifiers.push(creditPackages.identifierPattern);
|
|
126
|
-
} else {
|
|
127
|
-
consumableIdentifiers.push("credit");
|
|
128
|
-
}
|
|
36
|
+
export const initializeSubscription = async (config: SubscriptionInitConfig): Promise<void> => {
|
|
37
|
+
const { apiKey, apiKeyIos, apiKeyAndroid, testStoreKey, entitlementId, credits, getAnonymousUserId, getFirebaseAuth, showAuthModal, onCreditsUpdated, creditPackages, timeoutMs = 10000, authStateTimeoutMs = 2000 } = config;
|
|
129
38
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
userId: string,
|
|
133
|
-
productId: string,
|
|
134
|
-
_customerInfo: CustomerInfo
|
|
135
|
-
): Promise<void> => {
|
|
136
|
-
const isCredit = isCreditPackage(productId, creditPackages?.identifierPattern);
|
|
39
|
+
const key = Platform.OS === "ios" ? (apiKeyIos || apiKey || "") : (apiKeyAndroid || apiKey || "");
|
|
40
|
+
if (!key) throw new Error("API key required");
|
|
137
41
|
|
|
138
|
-
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
42
|
+
configureCreditsRepository({ ...credits, creditPackageAmounts: creditPackages?.amounts });
|
|
141
43
|
|
|
44
|
+
const onPurchase = async (userId: string, productId: string) => {
|
|
45
|
+
if (!isCreditPkg(productId, creditPackages?.identifierPattern)) return;
|
|
142
46
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
await repository.initializeCredits(userId, purchaseId, productId);
|
|
147
|
-
|
|
148
|
-
if (__DEV__) {
|
|
149
|
-
console.log("[SubscriptionInitializer] Credits added for purchase:", {
|
|
150
|
-
userId,
|
|
151
|
-
productId,
|
|
152
|
-
purchaseId,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (onCreditsUpdated) {
|
|
157
|
-
onCreditsUpdated(userId);
|
|
158
|
-
}
|
|
159
|
-
} catch (error) {
|
|
160
|
-
if (__DEV__) {
|
|
161
|
-
console.error("[SubscriptionInitializer] Failed to add credits:", error);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
47
|
+
await getCreditsRepository().initializeCredits(userId, `purchase_${productId}_${Date.now()}`, productId);
|
|
48
|
+
onCreditsUpdated?.(userId);
|
|
49
|
+
} catch { /* Silent */ }
|
|
164
50
|
};
|
|
165
51
|
|
|
166
|
-
|
|
167
|
-
const handleCreditRenewal = async (
|
|
168
|
-
userId: string,
|
|
169
|
-
productId: string,
|
|
170
|
-
renewalId: string
|
|
171
|
-
): Promise<void> => {
|
|
52
|
+
const onRenewal = async (userId: string, productId: string, renewalId: string) => {
|
|
172
53
|
try {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (__DEV__) {
|
|
177
|
-
console.log("[SubscriptionInitializer] Credits renewed:", {
|
|
178
|
-
userId,
|
|
179
|
-
productId,
|
|
180
|
-
renewalId,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (onCreditsUpdated) {
|
|
185
|
-
onCreditsUpdated(userId);
|
|
186
|
-
}
|
|
187
|
-
} catch (error) {
|
|
188
|
-
if (__DEV__) {
|
|
189
|
-
console.error("[SubscriptionInitializer] Failed to renew credits:", error);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
54
|
+
await getCreditsRepository().initializeCredits(userId, renewalId, productId);
|
|
55
|
+
onCreditsUpdated?.(userId);
|
|
56
|
+
} catch { /* Silent */ }
|
|
192
57
|
};
|
|
193
58
|
|
|
194
59
|
SubscriptionManager.configure({
|
|
195
|
-
config: {
|
|
196
|
-
|
|
197
|
-
testStoreKey,
|
|
198
|
-
entitlementIdentifier: entitlementId,
|
|
199
|
-
consumableProductIdentifiers: consumableIdentifiers,
|
|
200
|
-
onCreditRenewal: handleCreditRenewal,
|
|
201
|
-
onCreditsUpdated,
|
|
202
|
-
onPurchaseCompleted: handlePurchaseCompleted,
|
|
203
|
-
},
|
|
204
|
-
apiKey: resolvedApiKey,
|
|
205
|
-
getAnonymousUserId,
|
|
60
|
+
config: { apiKey: key, testStoreKey, entitlementIdentifier: entitlementId, consumableProductIdentifiers: [creditPackages?.identifierPattern || "credit"], onCreditRenewal: onRenewal, onPurchaseCompleted: onPurchase, onCreditsUpdated },
|
|
61
|
+
apiKey: key, getAnonymousUserId
|
|
206
62
|
});
|
|
207
63
|
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
const initPromise = SubscriptionManager.initialize(initialUserId);
|
|
212
|
-
const timeoutPromise = new Promise<boolean>((_, reject) =>
|
|
213
|
-
setTimeout(
|
|
214
|
-
() => reject(new Error("Subscription initialization timeout")),
|
|
215
|
-
timeoutMs,
|
|
216
|
-
),
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
await Promise.race([initPromise, timeoutPromise]);
|
|
64
|
+
const userId = await waitForAuthState(getFirebaseAuth, authStateTimeoutMs);
|
|
65
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), timeoutMs));
|
|
66
|
+
await Promise.race([SubscriptionManager.initialize(userId), timeout]);
|
|
220
67
|
|
|
221
68
|
configureAuthProvider({
|
|
222
69
|
isAuthenticated: () => {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
return !!(user && !user.isAnonymous);
|
|
70
|
+
const u = getFirebaseAuth()?.currentUser;
|
|
71
|
+
return !!(u && !u.isAnonymous);
|
|
226
72
|
},
|
|
227
73
|
showAuthModal,
|
|
228
74
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export * from "./useAuthAwarePurchase";
|
|
2
|
+
export * from "./useAuthGate";
|
|
3
|
+
export * from "./useAuthSubscriptionSync";
|
|
4
|
+
export * from "./useCreditChecker";
|
|
5
|
+
export * from "./useCredits";
|
|
6
|
+
export * from "./useCreditsGate";
|
|
7
|
+
export * from "./useDeductCredit";
|
|
8
|
+
export * from "./useInitializeCredits";
|
|
9
|
+
export * from "./useDevTestCallbacks";
|
|
10
|
+
export * from "./useFeatureGate";
|
|
11
|
+
export * from "./usePaywallOperations";
|
|
12
|
+
export * from "./usePaywallVisibility";
|
|
13
|
+
export * from "./usePremium";
|
|
14
|
+
export * from "./usePremiumGate";
|
|
15
|
+
export * from "./usePremiumWithCredits";
|
|
16
|
+
export * from "./useSubscription";
|
|
17
|
+
export * from "./useSubscriptionDetails";
|
|
18
|
+
export * from "./useSubscriptionGate";
|
|
19
|
+
export * from "./useSubscriptionSettingsConfig";
|
|
20
|
+
export * from "./useSubscriptionStatus";
|
|
21
|
+
export * from "./useUserTier";
|
|
22
|
+
export * from "./useUserTierWithRepository";
|
|
23
|
+
export * from "./feedback/usePaywallFeedback";
|