@umituz/react-native-subscription 2.11.20 → 2.11.22
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/index.ts +14 -0
- package/src/infrastructure/repositories/CreditsRepository.ts +57 -2
- package/src/infrastructure/services/CreditsInitializer.ts +52 -0
- package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +0 -1
- package/src/presentation/hooks/useDeductCredit.ts +41 -11
- package/src/presentation/hooks/usePremiumWithConfig.ts +1 -1
- package/src/revenuecat/presentation/hooks/useSubscriptionPackages.ts +14 -7
- package/src/utils/creditMapper.ts +83 -0
- package/src/utils/packageTypeDetector.ts +62 -0
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.22",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -227,6 +227,7 @@ export {
|
|
|
227
227
|
type UseDeductCreditResult,
|
|
228
228
|
type UseInitializeCreditsParams,
|
|
229
229
|
type UseInitializeCreditsResult,
|
|
230
|
+
type InitializeCreditsOptions,
|
|
230
231
|
} from "./presentation/hooks/useDeductCredit";
|
|
231
232
|
|
|
232
233
|
export {
|
|
@@ -274,6 +275,19 @@ export {
|
|
|
274
275
|
type AICreditHelpers,
|
|
275
276
|
} from "./utils/aiCreditHelpers";
|
|
276
277
|
|
|
278
|
+
export {
|
|
279
|
+
detectPackageType,
|
|
280
|
+
type SubscriptionPackageType,
|
|
281
|
+
} from "./utils/packageTypeDetector";
|
|
282
|
+
|
|
283
|
+
export {
|
|
284
|
+
getCreditAllocation,
|
|
285
|
+
getImageCreditsForPackage,
|
|
286
|
+
getTextCreditsForPackage,
|
|
287
|
+
CREDIT_ALLOCATIONS,
|
|
288
|
+
type CreditAllocation,
|
|
289
|
+
} from "./utils/creditMapper";
|
|
290
|
+
|
|
277
291
|
// =============================================================================
|
|
278
292
|
// REVENUECAT - Errors
|
|
279
293
|
// =============================================================================
|
|
@@ -23,6 +23,8 @@ import type {
|
|
|
23
23
|
} from "../../domain/entities/Credits";
|
|
24
24
|
import type { UserCreditsDocumentRead } from "../models/UserCreditsDocument";
|
|
25
25
|
import { initializeCreditsTransaction } from "../services/CreditsInitializer";
|
|
26
|
+
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
27
|
+
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
26
28
|
|
|
27
29
|
export class CreditsRepository extends BaseRepository {
|
|
28
30
|
private config: CreditsConfig;
|
|
@@ -79,7 +81,8 @@ export class CreditsRepository extends BaseRepository {
|
|
|
79
81
|
|
|
80
82
|
async initializeCredits(
|
|
81
83
|
userId: string,
|
|
82
|
-
purchaseId?: string
|
|
84
|
+
purchaseId?: string,
|
|
85
|
+
productId?: string
|
|
83
86
|
): Promise<CreditsResult> {
|
|
84
87
|
const db = getFirestore();
|
|
85
88
|
if (!db) {
|
|
@@ -89,15 +92,63 @@ export class CreditsRepository extends BaseRepository {
|
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
if (__DEV__) {
|
|
96
|
+
console.log("[CreditsRepository] Initialize credits:", {
|
|
97
|
+
userId,
|
|
98
|
+
purchaseId,
|
|
99
|
+
productId,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
try {
|
|
93
104
|
const creditsRef = this.getCreditsDocRef(db, userId);
|
|
105
|
+
|
|
106
|
+
// Determine credit allocation based on product ID
|
|
107
|
+
let configToUse = this.config;
|
|
108
|
+
|
|
109
|
+
if (productId) {
|
|
110
|
+
const packageType = detectPackageType(productId);
|
|
111
|
+
const allocation = getCreditAllocation(packageType);
|
|
112
|
+
|
|
113
|
+
if (allocation) {
|
|
114
|
+
// Override config with tier-specific credit amounts
|
|
115
|
+
configToUse = {
|
|
116
|
+
...this.config,
|
|
117
|
+
imageCreditLimit: allocation.imageCredits,
|
|
118
|
+
textCreditLimit: allocation.textCredits,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (__DEV__) {
|
|
122
|
+
console.log("[CreditsRepository] Using tier-based allocation:", {
|
|
123
|
+
packageType,
|
|
124
|
+
imageCredits: allocation.imageCredits,
|
|
125
|
+
textCredits: allocation.textCredits,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (__DEV__) {
|
|
130
|
+
console.warn(
|
|
131
|
+
"[CreditsRepository] Could not determine package type, using default config:",
|
|
132
|
+
this.config
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
94
138
|
const result = await initializeCreditsTransaction(
|
|
95
139
|
db,
|
|
96
140
|
creditsRef,
|
|
97
|
-
|
|
141
|
+
configToUse,
|
|
98
142
|
purchaseId
|
|
99
143
|
);
|
|
100
144
|
|
|
145
|
+
if (__DEV__) {
|
|
146
|
+
console.log("[CreditsRepository] Credits initialized successfully:", {
|
|
147
|
+
imageCredits: result.imageCredits,
|
|
148
|
+
textCredits: result.textCredits,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
101
152
|
return {
|
|
102
153
|
success: true,
|
|
103
154
|
data: {
|
|
@@ -108,6 +159,10 @@ export class CreditsRepository extends BaseRepository {
|
|
|
108
159
|
},
|
|
109
160
|
};
|
|
110
161
|
} catch (error) {
|
|
162
|
+
if (__DEV__) {
|
|
163
|
+
console.error("[CreditsRepository] Failed to initialize credits:", error);
|
|
164
|
+
}
|
|
165
|
+
|
|
111
166
|
return {
|
|
112
167
|
success: false,
|
|
113
168
|
error: {
|
|
@@ -21,6 +21,14 @@ export async function initializeCreditsTransaction(
|
|
|
21
21
|
config: CreditsConfig,
|
|
22
22
|
purchaseId?: string
|
|
23
23
|
): Promise<InitializationResult> {
|
|
24
|
+
if (__DEV__) {
|
|
25
|
+
console.log("[CreditsInitializer] Starting transaction with config:", {
|
|
26
|
+
textCreditLimit: config.textCreditLimit,
|
|
27
|
+
imageCreditLimit: config.imageCreditLimit,
|
|
28
|
+
purchaseId,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
return runTransaction(db, async (transaction: Transaction) => {
|
|
25
33
|
const creditsDoc = await transaction.get(creditsRef);
|
|
26
34
|
const now = serverTimestamp();
|
|
@@ -34,7 +42,21 @@ export async function initializeCreditsTransaction(
|
|
|
34
42
|
const existing = creditsDoc.data() as UserCreditsDocumentRead;
|
|
35
43
|
processedPurchases = existing.processedPurchases || [];
|
|
36
44
|
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
console.log("[CreditsInitializer] Existing credits found:", {
|
|
47
|
+
textCredits: existing.textCredits,
|
|
48
|
+
imageCredits: existing.imageCredits,
|
|
49
|
+
processedPurchases,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
if (purchaseId && processedPurchases.includes(purchaseId)) {
|
|
54
|
+
if (__DEV__) {
|
|
55
|
+
console.warn(
|
|
56
|
+
"[CreditsInitializer] Purchase already processed:",
|
|
57
|
+
purchaseId
|
|
58
|
+
);
|
|
59
|
+
}
|
|
38
60
|
return {
|
|
39
61
|
textCredits: existing.textCredits,
|
|
40
62
|
imageCredits: existing.imageCredits,
|
|
@@ -44,9 +66,32 @@ export async function initializeCreditsTransaction(
|
|
|
44
66
|
|
|
45
67
|
newTextCredits = (existing.textCredits || 0) + config.textCreditLimit;
|
|
46
68
|
newImageCredits = (existing.imageCredits || 0) + config.imageCreditLimit;
|
|
69
|
+
|
|
70
|
+
if (__DEV__) {
|
|
71
|
+
console.log("[CreditsInitializer] Adding to existing credits:", {
|
|
72
|
+
existingText: existing.textCredits || 0,
|
|
73
|
+
existingImage: existing.imageCredits || 0,
|
|
74
|
+
adding: {
|
|
75
|
+
text: config.textCreditLimit,
|
|
76
|
+
image: config.imageCreditLimit,
|
|
77
|
+
},
|
|
78
|
+
newTotal: {
|
|
79
|
+
text: newTextCredits,
|
|
80
|
+
image: newImageCredits,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
if (existing.purchasedAt) {
|
|
48
86
|
purchasedAt = existing.purchasedAt as unknown as FieldValue;
|
|
49
87
|
}
|
|
88
|
+
} else {
|
|
89
|
+
if (__DEV__) {
|
|
90
|
+
console.log("[CreditsInitializer] Creating new credits document:", {
|
|
91
|
+
textCredits: newTextCredits,
|
|
92
|
+
imageCredits: newImageCredits,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
50
95
|
}
|
|
51
96
|
|
|
52
97
|
if (purchaseId) {
|
|
@@ -62,6 +107,13 @@ export async function initializeCreditsTransaction(
|
|
|
62
107
|
processedPurchases,
|
|
63
108
|
});
|
|
64
109
|
|
|
110
|
+
if (__DEV__) {
|
|
111
|
+
console.log("[CreditsInitializer] Transaction completed successfully:", {
|
|
112
|
+
textCredits: newTextCredits,
|
|
113
|
+
imageCredits: newImageCredits,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
65
117
|
return { textCredits: newTextCredits, imageCredits: newImageCredits };
|
|
66
118
|
});
|
|
67
119
|
}
|
|
@@ -106,8 +106,13 @@ export interface UseInitializeCreditsParams {
|
|
|
106
106
|
userId: string | undefined;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
export interface InitializeCreditsOptions {
|
|
110
|
+
purchaseId?: string;
|
|
111
|
+
productId?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
109
114
|
export interface UseInitializeCreditsResult {
|
|
110
|
-
initializeCredits: (
|
|
115
|
+
initializeCredits: (options?: InitializeCreditsOptions) => Promise<boolean>;
|
|
111
116
|
isInitializing: boolean;
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -118,14 +123,31 @@ export const useInitializeCredits = ({
|
|
|
118
123
|
const queryClient = useQueryClient();
|
|
119
124
|
|
|
120
125
|
const mutation = useMutation({
|
|
121
|
-
mutationFn: async (
|
|
126
|
+
mutationFn: async (options?: InitializeCreditsOptions) => {
|
|
122
127
|
if (!userId) {
|
|
123
128
|
throw new Error("User not authenticated");
|
|
124
129
|
}
|
|
125
|
-
|
|
130
|
+
|
|
131
|
+
if (__DEV__) {
|
|
132
|
+
console.log("[useInitializeCredits] Initializing credits:", {
|
|
133
|
+
userId,
|
|
134
|
+
purchaseId: options?.purchaseId,
|
|
135
|
+
productId: options?.productId,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return repository.initializeCredits(
|
|
140
|
+
userId,
|
|
141
|
+
options?.purchaseId,
|
|
142
|
+
options?.productId
|
|
143
|
+
);
|
|
126
144
|
},
|
|
127
145
|
onSuccess: (result) => {
|
|
128
146
|
if (userId && result.success && result.data) {
|
|
147
|
+
if (__DEV__) {
|
|
148
|
+
console.log("[useInitializeCredits] Success, updating cache:", result.data);
|
|
149
|
+
}
|
|
150
|
+
|
|
129
151
|
// Set the data immediately for optimistic UI
|
|
130
152
|
queryClient.setQueryData(creditsQueryKeys.user(userId), result.data);
|
|
131
153
|
// Also invalidate to ensure all subscribers get the update
|
|
@@ -134,16 +156,24 @@ export const useInitializeCredits = ({
|
|
|
134
156
|
});
|
|
135
157
|
}
|
|
136
158
|
},
|
|
159
|
+
onError: (error) => {
|
|
160
|
+
if (__DEV__) {
|
|
161
|
+
console.error("[useInitializeCredits] Error:", error);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
137
164
|
});
|
|
138
165
|
|
|
139
|
-
const initializeCredits = useCallback(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
const initializeCredits = useCallback(
|
|
167
|
+
async (options?: InitializeCreditsOptions): Promise<boolean> => {
|
|
168
|
+
try {
|
|
169
|
+
const result = await mutation.mutateAsync(options);
|
|
170
|
+
return result.success;
|
|
171
|
+
} catch {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
[mutation]
|
|
176
|
+
);
|
|
147
177
|
|
|
148
178
|
return {
|
|
149
179
|
initializeCredits,
|
|
@@ -67,7 +67,7 @@ export const usePremiumWithConfig = (
|
|
|
67
67
|
async (pkg: PurchasesPackage): Promise<boolean> => {
|
|
68
68
|
const success = await purchaseMutation.mutateAsync(pkg);
|
|
69
69
|
if (success && userId) {
|
|
70
|
-
await initializeCredits();
|
|
70
|
+
await initializeCredits({ productId: pkg.product.identifier });
|
|
71
71
|
}
|
|
72
72
|
return success;
|
|
73
73
|
},
|
|
@@ -14,24 +14,31 @@ import {
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Fetch available subscription packages
|
|
17
|
+
* Works for both authenticated and anonymous users
|
|
17
18
|
*/
|
|
18
19
|
export const useSubscriptionPackages = (userId: string | undefined) => {
|
|
19
20
|
return useQuery({
|
|
20
|
-
queryKey: [...SUBSCRIPTION_QUERY_KEYS.packages, userId] as const,
|
|
21
|
+
queryKey: [...SUBSCRIPTION_QUERY_KEYS.packages, userId ?? "anonymous"] as const,
|
|
21
22
|
queryFn: async () => {
|
|
22
23
|
addPackageBreadcrumb("subscription", "Fetch packages query started", {
|
|
23
|
-
userId: userId ?? "
|
|
24
|
+
userId: userId ?? "ANONYMOUS",
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
//
|
|
27
|
-
if (
|
|
28
|
-
|
|
27
|
+
// Initialize if needed (works for both authenticated and anonymous users)
|
|
28
|
+
if (userId) {
|
|
29
|
+
if (!SubscriptionManager.isInitializedForUser(userId)) {
|
|
30
|
+
await SubscriptionManager.initialize(userId);
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
if (!SubscriptionManager.isInitialized()) {
|
|
34
|
+
await SubscriptionManager.initialize(undefined);
|
|
35
|
+
}
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
const packages = await SubscriptionManager.getPackages();
|
|
32
39
|
|
|
33
40
|
addPackageBreadcrumb("subscription", "Fetch packages query success", {
|
|
34
|
-
userId: userId ?? "
|
|
41
|
+
userId: userId ?? "ANONYMOUS",
|
|
35
42
|
count: packages.length,
|
|
36
43
|
});
|
|
37
44
|
|
|
@@ -39,6 +46,6 @@ export const useSubscriptionPackages = (userId: string | undefined) => {
|
|
|
39
46
|
},
|
|
40
47
|
staleTime: STALE_TIME,
|
|
41
48
|
gcTime: GC_TIME,
|
|
42
|
-
enabled:
|
|
49
|
+
enabled: true, // Always enabled - works for both authenticated and anonymous users
|
|
43
50
|
});
|
|
44
51
|
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credit Mapper
|
|
3
|
+
* Maps subscription package types to credit amounts
|
|
4
|
+
* Based on SUBSCRIPTION_GUIDE.md pricing strategy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { SubscriptionPackageType } from "./packageTypeDetector";
|
|
8
|
+
|
|
9
|
+
export interface CreditAllocation {
|
|
10
|
+
imageCredits: number;
|
|
11
|
+
textCredits: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Standard credit allocations per package type
|
|
16
|
+
* Based on profitability analysis and value ladder strategy
|
|
17
|
+
*
|
|
18
|
+
* Weekly: 6 images - $2.99 (62% margin) - Trial users
|
|
19
|
+
* Monthly: 25 images - $9.99 (60% margin) - Regular users
|
|
20
|
+
* Yearly: 300 images - $79.99 (55% margin) - Best value (46% cheaper/image)
|
|
21
|
+
*/
|
|
22
|
+
export const CREDIT_ALLOCATIONS: Record<
|
|
23
|
+
Exclude<SubscriptionPackageType, "unknown">,
|
|
24
|
+
CreditAllocation
|
|
25
|
+
> = {
|
|
26
|
+
weekly: {
|
|
27
|
+
imageCredits: 6,
|
|
28
|
+
textCredits: 6,
|
|
29
|
+
},
|
|
30
|
+
monthly: {
|
|
31
|
+
imageCredits: 25,
|
|
32
|
+
textCredits: 25,
|
|
33
|
+
},
|
|
34
|
+
yearly: {
|
|
35
|
+
imageCredits: 300,
|
|
36
|
+
textCredits: 300,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get credit allocation for a package type
|
|
42
|
+
* Returns null for unknown package types to prevent incorrect credit assignment
|
|
43
|
+
*/
|
|
44
|
+
export function getCreditAllocation(
|
|
45
|
+
packageType: SubscriptionPackageType
|
|
46
|
+
): CreditAllocation | null {
|
|
47
|
+
if (packageType === "unknown") {
|
|
48
|
+
if (__DEV__) {
|
|
49
|
+
console.warn(
|
|
50
|
+
"[CreditMapper] Cannot allocate credits for unknown package type"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const allocation = CREDIT_ALLOCATIONS[packageType];
|
|
57
|
+
|
|
58
|
+
if (__DEV__) {
|
|
59
|
+
console.log("[CreditMapper] Credit allocation for", packageType, ":", allocation);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return allocation;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get image credits for a package type
|
|
67
|
+
*/
|
|
68
|
+
export function getImageCreditsForPackage(
|
|
69
|
+
packageType: SubscriptionPackageType
|
|
70
|
+
): number | null {
|
|
71
|
+
const allocation = getCreditAllocation(packageType);
|
|
72
|
+
return allocation?.imageCredits ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get text credits for a package type
|
|
77
|
+
*/
|
|
78
|
+
export function getTextCreditsForPackage(
|
|
79
|
+
packageType: SubscriptionPackageType
|
|
80
|
+
): number | null {
|
|
81
|
+
const allocation = getCreditAllocation(packageType);
|
|
82
|
+
return allocation?.textCredits ?? null;
|
|
83
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package Type Detector
|
|
3
|
+
* Detects subscription package type from RevenueCat package identifier
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type SubscriptionPackageType = "weekly" | "monthly" | "yearly" | "unknown";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect package type from product identifier
|
|
10
|
+
* Supports common RevenueCat naming patterns:
|
|
11
|
+
* - premium_weekly, weekly_premium, premium-weekly
|
|
12
|
+
* - premium_monthly, monthly_premium, premium-monthly
|
|
13
|
+
* - premium_yearly, yearly_premium, premium-yearly, premium_annual, annual_premium
|
|
14
|
+
*/
|
|
15
|
+
export function detectPackageType(productIdentifier: string): SubscriptionPackageType {
|
|
16
|
+
if (!productIdentifier) {
|
|
17
|
+
if (__DEV__) {
|
|
18
|
+
console.log("[PackageTypeDetector] No product identifier provided");
|
|
19
|
+
}
|
|
20
|
+
return "unknown";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const normalized = productIdentifier.toLowerCase();
|
|
24
|
+
|
|
25
|
+
if (__DEV__) {
|
|
26
|
+
console.log("[PackageTypeDetector] Detecting package type for:", normalized);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Weekly detection
|
|
30
|
+
if (normalized.includes("weekly") || normalized.includes("week")) {
|
|
31
|
+
if (__DEV__) {
|
|
32
|
+
console.log("[PackageTypeDetector] Detected: WEEKLY");
|
|
33
|
+
}
|
|
34
|
+
return "weekly";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Monthly detection
|
|
38
|
+
if (normalized.includes("monthly") || normalized.includes("month")) {
|
|
39
|
+
if (__DEV__) {
|
|
40
|
+
console.log("[PackageTypeDetector] Detected: MONTHLY");
|
|
41
|
+
}
|
|
42
|
+
return "monthly";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Yearly detection (includes annual)
|
|
46
|
+
if (
|
|
47
|
+
normalized.includes("yearly") ||
|
|
48
|
+
normalized.includes("year") ||
|
|
49
|
+
normalized.includes("annual")
|
|
50
|
+
) {
|
|
51
|
+
if (__DEV__) {
|
|
52
|
+
console.log("[PackageTypeDetector] Detected: YEARLY");
|
|
53
|
+
}
|
|
54
|
+
return "yearly";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (__DEV__) {
|
|
58
|
+
console.warn("[PackageTypeDetector] Unknown package type for:", productIdentifier);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return "unknown";
|
|
62
|
+
}
|