@umituz/react-native-subscription 2.22.8 → 2.22.10
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/infrastructure/mappers/CreditsMapper.ts +35 -14
- package/src/infrastructure/repositories/CreditsRepository.ts +19 -0
- package/src/infrastructure/services/SubscriptionInitializer.ts +23 -4
- package/src/presentation/hooks/useCredits.ts +11 -4
- package/src/presentation/hooks/useSubscriptionSettingsConfig.ts +62 -135
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.10",
|
|
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,20 +1,22 @@
|
|
|
1
1
|
import type { UserCredits, SubscriptionStatus } from "../../domain/entities/Credits";
|
|
2
2
|
import type { UserCreditsDocumentRead } from "../models/UserCreditsDocument";
|
|
3
3
|
|
|
4
|
-
/** Maps Firestore document to domain entity */
|
|
4
|
+
/** Maps Firestore document to domain entity with expiration validation */
|
|
5
5
|
export class CreditsMapper {
|
|
6
6
|
static toEntity(doc: UserCreditsDocumentRead): UserCredits {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const expirationDate = doc.expirationDate?.toDate?.() ?? null;
|
|
8
|
+
|
|
9
|
+
// Validate isPremium against expirationDate (real-time check)
|
|
10
|
+
const { isPremium, status } = CreditsMapper.validateSubscription(doc, expirationDate);
|
|
9
11
|
|
|
10
12
|
return {
|
|
11
|
-
// Core subscription
|
|
12
|
-
isPremium
|
|
13
|
+
// Core subscription (validated)
|
|
14
|
+
isPremium,
|
|
13
15
|
status,
|
|
14
16
|
|
|
15
17
|
// Dates
|
|
16
18
|
purchasedAt: doc.purchasedAt?.toDate?.() ?? null,
|
|
17
|
-
expirationDate
|
|
19
|
+
expirationDate,
|
|
18
20
|
lastUpdatedAt: doc.lastUpdatedAt?.toDate?.() ?? null,
|
|
19
21
|
|
|
20
22
|
// RevenueCat details
|
|
@@ -35,14 +37,33 @@ export class CreditsMapper {
|
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
/**
|
|
39
|
-
private static
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
/** Validate subscription status against expirationDate */
|
|
41
|
+
private static validateSubscription(
|
|
42
|
+
doc: UserCreditsDocumentRead,
|
|
43
|
+
expirationDate: Date | null
|
|
44
|
+
): { isPremium: boolean; status: SubscriptionStatus } {
|
|
45
|
+
const docIsPremium = doc.isPremium ?? false;
|
|
46
|
+
|
|
47
|
+
// No expiration date = lifetime or free
|
|
48
|
+
if (!expirationDate) {
|
|
49
|
+
return {
|
|
50
|
+
isPremium: docIsPremium,
|
|
51
|
+
status: docIsPremium ? "active" : "free",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check if subscription has expired
|
|
56
|
+
const isExpired = expirationDate < new Date();
|
|
57
|
+
|
|
58
|
+
if (isExpired) {
|
|
59
|
+
// Subscription expired - override document's isPremium
|
|
60
|
+
return { isPremium: false, status: "expired" };
|
|
45
61
|
}
|
|
46
|
-
|
|
62
|
+
|
|
63
|
+
// Subscription still active
|
|
64
|
+
return {
|
|
65
|
+
isPremium: docIsPremium,
|
|
66
|
+
status: docIsPremium ? "active" : "free",
|
|
67
|
+
};
|
|
47
68
|
}
|
|
48
69
|
}
|
|
@@ -129,6 +129,25 @@ export class CreditsRepository extends BaseRepository {
|
|
|
129
129
|
const res = await this.getCredits(userId);
|
|
130
130
|
return !!(res.success && res.data && res.data.credits >= cost);
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/** Sync expired subscription status to Firestore (background) */
|
|
134
|
+
async syncExpiredStatus(userId: string): Promise<void> {
|
|
135
|
+
const db = getFirestore();
|
|
136
|
+
if (!db) return;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const ref = this.getRef(db, userId);
|
|
140
|
+
const { updateDoc } = await import("firebase/firestore");
|
|
141
|
+
await updateDoc(ref, {
|
|
142
|
+
isPremium: false,
|
|
143
|
+
status: "expired",
|
|
144
|
+
lastUpdatedAt: serverTimestamp(),
|
|
145
|
+
});
|
|
146
|
+
if (__DEV__) console.log("[CreditsRepository] Synced expired status for:", userId.slice(0, 8));
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (__DEV__) console.error("[CreditsRepository] Sync expired failed:", e);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
132
151
|
}
|
|
133
152
|
|
|
134
153
|
export const createCreditsRepository = (c: CreditsConfig) => new CreditsRepository(c);
|
|
@@ -41,16 +41,31 @@ export const initializeSubscription = async (config: SubscriptionInitConfig): Pr
|
|
|
41
41
|
|
|
42
42
|
configureCreditsRepository({ ...credits, creditPackageAmounts: creditPackages?.amounts });
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
/** Extract RevenueCat data from CustomerInfo (Single Source of Truth) */
|
|
45
|
+
const extractRevenueCatData = (customerInfo: CustomerInfo, _productId: string): RevenueCatData => {
|
|
46
|
+
const entitlement = customerInfo.entitlements.active[entitlementId]
|
|
47
|
+
?? customerInfo.entitlements.all[entitlementId];
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
expirationDate: entitlement?.expirationDate ?? customerInfo.latestExpirationDate ?? null,
|
|
51
|
+
willRenew: entitlement?.willRenew ?? false,
|
|
52
|
+
originalTransactionId: entitlement?.originalPurchaseDate ?? undefined,
|
|
53
|
+
isPremium: Object.keys(customerInfo.entitlements.active).length > 0,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const onPurchase = async (userId: string, productId: string, customerInfo: CustomerInfo, source?: string) => {
|
|
45
58
|
if (__DEV__) {
|
|
46
59
|
console.log('[SubscriptionInitializer] onPurchase called:', { userId, productId, source });
|
|
47
60
|
}
|
|
48
61
|
try {
|
|
62
|
+
const revenueCatData = extractRevenueCatData(customerInfo, productId);
|
|
49
63
|
const result = await getCreditsRepository().initializeCredits(
|
|
50
64
|
userId,
|
|
51
65
|
`purchase_${productId}_${Date.now()}`,
|
|
52
66
|
productId,
|
|
53
|
-
source as any
|
|
67
|
+
source as any,
|
|
68
|
+
revenueCatData
|
|
54
69
|
);
|
|
55
70
|
if (__DEV__) {
|
|
56
71
|
console.log('[SubscriptionInitializer] Credits initialized:', result);
|
|
@@ -63,16 +78,20 @@ export const initializeSubscription = async (config: SubscriptionInitConfig): Pr
|
|
|
63
78
|
}
|
|
64
79
|
};
|
|
65
80
|
|
|
66
|
-
const onRenewal = async (userId: string, productId: string,
|
|
81
|
+
const onRenewal = async (userId: string, productId: string, newExpirationDate: string, customerInfo: CustomerInfo) => {
|
|
67
82
|
if (__DEV__) {
|
|
68
83
|
console.log('[SubscriptionInitializer] onRenewal called:', { userId, productId });
|
|
69
84
|
}
|
|
70
85
|
try {
|
|
86
|
+
const revenueCatData = extractRevenueCatData(customerInfo, productId);
|
|
87
|
+
// Update expiration date from renewal
|
|
88
|
+
revenueCatData.expirationDate = newExpirationDate || revenueCatData.expirationDate;
|
|
71
89
|
const result = await getCreditsRepository().initializeCredits(
|
|
72
90
|
userId,
|
|
73
91
|
`renewal_${productId}_${Date.now()}`,
|
|
74
92
|
productId,
|
|
75
|
-
"renewal" as any
|
|
93
|
+
"renewal" as any,
|
|
94
|
+
revenueCatData
|
|
76
95
|
);
|
|
77
96
|
if (__DEV__) {
|
|
78
97
|
console.log('[SubscriptionInitializer] Credits reset on renewal:', result);
|
|
@@ -88,15 +88,22 @@ export const useCredits = ({
|
|
|
88
88
|
if (__DEV__) console.error("[useCredits] Query failed:", result.error?.message);
|
|
89
89
|
throw new Error(result.error?.message || "Failed to fetch credits");
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
|
|
92
|
+
// Background sync: If mapper detected expired status, sync to Firestore
|
|
93
|
+
if (result.data?.status === "expired") {
|
|
94
|
+
if (__DEV__) console.log("[useCredits] Detected expired subscription, syncing...");
|
|
95
|
+
repository.syncExpiredStatus(userId).catch(() => {});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (__DEV__) console.log("[useCredits] Query success:", { hasData: !!result.data, credits: result.data?.credits, status: result.data?.status });
|
|
92
99
|
return result.data || null;
|
|
93
100
|
},
|
|
94
101
|
enabled: queryEnabled,
|
|
95
102
|
staleTime,
|
|
96
103
|
gcTime,
|
|
97
|
-
refetchOnMount: true,
|
|
98
|
-
refetchOnWindowFocus: true,
|
|
99
|
-
refetchOnReconnect: true,
|
|
104
|
+
refetchOnMount: true,
|
|
105
|
+
refetchOnWindowFocus: true,
|
|
106
|
+
refetchOnReconnect: true,
|
|
100
107
|
});
|
|
101
108
|
|
|
102
109
|
const credits = data ?? null;
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useSubscriptionSettingsConfig Hook
|
|
3
3
|
* Returns ready-to-use config for settings screens
|
|
4
|
-
*
|
|
4
|
+
* Single Source of Truth: Firestore (credits document)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useMemo, useCallback } from "react";
|
|
8
8
|
import { useCredits } from "./useCredits";
|
|
9
|
-
import { useSubscriptionStatus } from "./useSubscriptionStatus";
|
|
10
|
-
import { useCustomerInfo } from "../../revenuecat/presentation/hooks/useCustomerInfo";
|
|
11
9
|
import { usePaywallVisibility } from "./usePaywallVisibility";
|
|
12
10
|
import { calculateDaysRemaining } from "../../domain/entities/SubscriptionStatus";
|
|
13
|
-
import {
|
|
14
|
-
import { formatDate, convertPurchasedAt } from "../utils/subscriptionDateUtils";
|
|
11
|
+
import { formatDate } from "../utils/subscriptionDateUtils";
|
|
15
12
|
import { useCreditsArray, getSubscriptionStatusType } from "./useSubscriptionSettingsConfig.utils";
|
|
16
13
|
import { getCreditsConfig } from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
17
|
-
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
18
|
-
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
19
14
|
import type {
|
|
20
15
|
SubscriptionSettingsConfig,
|
|
21
16
|
SubscriptionStatusType,
|
|
@@ -31,105 +26,53 @@ export type {
|
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
28
|
* Hook that returns ready-to-use subscription config for settings
|
|
34
|
-
*
|
|
29
|
+
* Single Source of Truth: Firestore credits document
|
|
35
30
|
*/
|
|
36
31
|
export const useSubscriptionSettingsConfig = (
|
|
37
32
|
params: UseSubscriptionSettingsConfigParams
|
|
38
33
|
): SubscriptionSettingsConfig => {
|
|
39
|
-
const {
|
|
40
|
-
userId,
|
|
41
|
-
translations,
|
|
42
|
-
creditLimit,
|
|
43
|
-
upgradePrompt,
|
|
44
|
-
} = params;
|
|
34
|
+
const { userId, translations, creditLimit, upgradePrompt } = params;
|
|
45
35
|
|
|
46
|
-
//
|
|
36
|
+
// Single Source of Truth: Firestore credits document
|
|
47
37
|
const { credits } = useCredits({ userId, enabled: !!userId });
|
|
48
|
-
const {
|
|
49
|
-
isPremium: subscriptionActive,
|
|
50
|
-
expirationDate: statusExpirationDate,
|
|
51
|
-
} = useSubscriptionStatus({
|
|
52
|
-
userId,
|
|
53
|
-
enabled: !!userId,
|
|
54
|
-
});
|
|
55
|
-
const { customerInfo } = useCustomerInfo();
|
|
56
38
|
const { openPaywall } = usePaywallVisibility();
|
|
57
39
|
|
|
58
40
|
const handleOpenPaywall = useCallback(() => {
|
|
59
41
|
openPaywall("settings");
|
|
60
42
|
}, [openPaywall]);
|
|
61
43
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const allEntitlement = customerInfo?.entitlements.all[entitlementId];
|
|
44
|
+
// All data from Firestore (Single Source of Truth)
|
|
45
|
+
const isPremium = credits?.isPremium ?? false;
|
|
46
|
+
const willRenew = credits?.willRenew ?? false;
|
|
66
47
|
|
|
67
|
-
//
|
|
68
|
-
const
|
|
48
|
+
// Expiration date from Firestore
|
|
49
|
+
const expiresAtIso = credits?.expirationDate?.toISOString() ?? null;
|
|
69
50
|
|
|
51
|
+
// Purchase date from Firestore
|
|
52
|
+
const purchasedAtIso = credits?.purchasedAt?.toISOString() ?? null;
|
|
53
|
+
|
|
54
|
+
// Credit limit from Firestore or config fallback
|
|
70
55
|
const dynamicCreditLimit = useMemo(() => {
|
|
56
|
+
if (credits?.creditLimit) return credits.creditLimit;
|
|
71
57
|
const config = getCreditsConfig();
|
|
72
|
-
|
|
73
|
-
// 1. ÖNCE FIRESTORE'DAN OKU (Single Source of Truth)
|
|
74
|
-
if (credits?.creditLimit) {
|
|
75
|
-
return credits.creditLimit;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 2. FALLBACK: RevenueCat'ten detect et
|
|
79
|
-
if (activeEntitlement?.productIdentifier) {
|
|
80
|
-
const packageType = detectPackageType(activeEntitlement.productIdentifier);
|
|
81
|
-
const allocation = getCreditAllocation(packageType, config.packageAllocations);
|
|
82
|
-
if (allocation !== null) return allocation;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 3. LAST RESORT: Credit miktarına bakarak tahmin et
|
|
86
|
-
if (credits?.credits && config.packageAllocations) {
|
|
87
|
-
const currentCredits = credits.credits;
|
|
88
|
-
const allocations = Object.values(config.packageAllocations).map(a => a.credits);
|
|
89
|
-
const closest = allocations.find(a => a >= currentCredits) || Math.max(...allocations);
|
|
90
|
-
return closest;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 4. FINAL FALLBACK: Config'den al
|
|
94
58
|
return creditLimit ?? config.creditLimit;
|
|
95
|
-
}, [credits?.creditLimit,
|
|
96
|
-
|
|
97
|
-
// Get expiration date with fallback chain (supports expired subscriptions)
|
|
98
|
-
// 1. Active entitlement (current subscription)
|
|
99
|
-
// 2. All entitlements (includes expired subscriptions)
|
|
100
|
-
// 3. latestExpirationDate from CustomerInfo
|
|
101
|
-
// 4. Status from Firestore
|
|
102
|
-
const expiresAtIso = activeEntitlement?.expirationDate
|
|
103
|
-
?? allEntitlement?.expirationDate
|
|
104
|
-
?? customerInfo?.latestExpirationDate
|
|
105
|
-
?? (statusExpirationDate ? statusExpirationDate.toISOString() : null);
|
|
106
|
-
|
|
107
|
-
const willRenew = activeEntitlement?.willRenew || false;
|
|
108
|
-
const purchasedAtIso = convertPurchasedAt(credits?.purchasedAt);
|
|
59
|
+
}, [credits?.creditLimit, creditLimit]);
|
|
109
60
|
|
|
110
61
|
// Formatted dates
|
|
111
|
-
const formattedExpirationDate = useMemo(
|
|
112
|
-
|
|
113
|
-
[expiresAtIso]
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
const formattedPurchaseDate = useMemo(
|
|
117
|
-
() => formatDate(purchasedAtIso),
|
|
118
|
-
[purchasedAtIso]
|
|
119
|
-
);
|
|
62
|
+
const formattedExpirationDate = useMemo(() => formatDate(expiresAtIso), [expiresAtIso]);
|
|
63
|
+
const formattedPurchaseDate = useMemo(() => formatDate(purchasedAtIso), [purchasedAtIso]);
|
|
120
64
|
|
|
121
|
-
// Days remaining
|
|
122
|
-
const daysRemaining = useMemo(
|
|
123
|
-
() => calculateDaysRemaining(expiresAtIso),
|
|
124
|
-
[expiresAtIso]
|
|
125
|
-
);
|
|
65
|
+
// Days remaining
|
|
66
|
+
const daysRemaining = useMemo(() => calculateDaysRemaining(expiresAtIso), [expiresAtIso]);
|
|
126
67
|
|
|
127
|
-
// Status type
|
|
128
|
-
const statusType: SubscriptionStatusType =
|
|
68
|
+
// Status type from Firestore or derived
|
|
69
|
+
const statusType: SubscriptionStatusType = credits?.status
|
|
70
|
+
? (credits.status as SubscriptionStatusType)
|
|
71
|
+
: getSubscriptionStatusType(isPremium);
|
|
129
72
|
|
|
130
73
|
const creditsArray = useCreditsArray(credits, dynamicCreditLimit, translations);
|
|
131
74
|
|
|
132
|
-
// Centralized display flags
|
|
75
|
+
// Centralized display flags
|
|
133
76
|
const hasCredits = creditsArray.length > 0;
|
|
134
77
|
const display = useMemo(() => ({
|
|
135
78
|
showHeader: isPremium || hasCredits,
|
|
@@ -139,63 +82,47 @@ export const useSubscriptionSettingsConfig = (
|
|
|
139
82
|
}), [isPremium, hasCredits, upgradePrompt, expiresAtIso]);
|
|
140
83
|
|
|
141
84
|
// Build config
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
description: translations.description,
|
|
148
|
-
isPremium,
|
|
149
|
-
statusLabel: isPremium
|
|
150
|
-
? translations.statusActive
|
|
151
|
-
: translations.statusFree,
|
|
152
|
-
icon: "diamond",
|
|
153
|
-
onPress: handleOpenPaywall,
|
|
154
|
-
},
|
|
155
|
-
sectionConfig: {
|
|
156
|
-
statusType,
|
|
157
|
-
isPremium,
|
|
158
|
-
display,
|
|
159
|
-
expirationDate: formattedExpirationDate,
|
|
160
|
-
purchaseDate: formattedPurchaseDate,
|
|
161
|
-
isLifetime: isPremium && !expiresAtIso,
|
|
162
|
-
daysRemaining,
|
|
163
|
-
willRenew,
|
|
164
|
-
credits: creditsArray,
|
|
165
|
-
translations: {
|
|
166
|
-
title: translations.title,
|
|
167
|
-
statusLabel: translations.statusLabel,
|
|
168
|
-
statusActive: translations.statusActive,
|
|
169
|
-
statusExpired: translations.statusExpired,
|
|
170
|
-
statusFree: translations.statusFree,
|
|
171
|
-
statusCanceled: translations.statusCanceled,
|
|
172
|
-
expiresLabel: translations.expiresLabel,
|
|
173
|
-
purchasedLabel: translations.purchasedLabel,
|
|
174
|
-
lifetimeLabel: translations.lifetimeLabel,
|
|
175
|
-
creditsTitle: translations.creditsTitle,
|
|
176
|
-
remainingLabel: translations.remainingLabel,
|
|
177
|
-
manageButton: translations.manageButton,
|
|
178
|
-
upgradeButton: translations.upgradeButton,
|
|
179
|
-
},
|
|
180
|
-
onUpgrade: handleOpenPaywall,
|
|
181
|
-
upgradePrompt,
|
|
182
|
-
},
|
|
183
|
-
}),
|
|
184
|
-
[
|
|
185
|
-
translations,
|
|
85
|
+
return useMemo((): SubscriptionSettingsConfig => ({
|
|
86
|
+
enabled: true,
|
|
87
|
+
settingsItem: {
|
|
88
|
+
title: translations.title,
|
|
89
|
+
description: translations.description,
|
|
186
90
|
isPremium,
|
|
91
|
+
statusLabel: isPremium ? translations.statusActive : translations.statusFree,
|
|
92
|
+
icon: "diamond",
|
|
93
|
+
onPress: handleOpenPaywall,
|
|
94
|
+
},
|
|
95
|
+
sectionConfig: {
|
|
187
96
|
statusType,
|
|
97
|
+
isPremium,
|
|
188
98
|
display,
|
|
189
|
-
formattedExpirationDate,
|
|
190
|
-
formattedPurchaseDate,
|
|
191
|
-
expiresAtIso,
|
|
99
|
+
expirationDate: formattedExpirationDate,
|
|
100
|
+
purchaseDate: formattedPurchaseDate,
|
|
101
|
+
isLifetime: isPremium && !expiresAtIso,
|
|
192
102
|
daysRemaining,
|
|
193
103
|
willRenew,
|
|
194
|
-
creditsArray,
|
|
195
|
-
|
|
104
|
+
credits: creditsArray,
|
|
105
|
+
translations: {
|
|
106
|
+
title: translations.title,
|
|
107
|
+
statusLabel: translations.statusLabel,
|
|
108
|
+
statusActive: translations.statusActive,
|
|
109
|
+
statusExpired: translations.statusExpired,
|
|
110
|
+
statusFree: translations.statusFree,
|
|
111
|
+
statusCanceled: translations.statusCanceled,
|
|
112
|
+
expiresLabel: translations.expiresLabel,
|
|
113
|
+
purchasedLabel: translations.purchasedLabel,
|
|
114
|
+
lifetimeLabel: translations.lifetimeLabel,
|
|
115
|
+
creditsTitle: translations.creditsTitle,
|
|
116
|
+
remainingLabel: translations.remainingLabel,
|
|
117
|
+
manageButton: translations.manageButton,
|
|
118
|
+
upgradeButton: translations.upgradeButton,
|
|
119
|
+
},
|
|
120
|
+
onUpgrade: handleOpenPaywall,
|
|
196
121
|
upgradePrompt,
|
|
197
|
-
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
|
|
122
|
+
},
|
|
123
|
+
}), [
|
|
124
|
+
translations, isPremium, statusType, display, formattedExpirationDate,
|
|
125
|
+
formattedPurchaseDate, expiresAtIso, daysRemaining, willRenew,
|
|
126
|
+
creditsArray, handleOpenPaywall, upgradePrompt,
|
|
127
|
+
]);
|
|
201
128
|
};
|