@umituz/react-native-subscription 2.27.92 → 2.27.94

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.
Files changed (39) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditsInitializer.ts +99 -40
  3. package/src/domains/credits/application/DeductCreditsCommand.ts +31 -13
  4. package/src/domains/credits/application/PurchaseMetadataGenerator.ts +17 -23
  5. package/src/domains/credits/core/Credits.ts +39 -39
  6. package/src/domains/credits/core/CreditsMapper.ts +11 -10
  7. package/src/domains/credits/core/UserCreditsDocument.ts +33 -33
  8. package/src/domains/credits/infrastructure/CreditsRepository.ts +46 -59
  9. package/src/domains/paywall/components/PaywallModal.tsx +1 -1
  10. package/src/domains/subscription/application/SubscriptionInitializer.ts +59 -18
  11. package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +20 -20
  12. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +46 -27
  13. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +106 -42
  14. package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +4 -2
  15. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +1 -2
  16. package/src/domains/subscription/infrastructure/utils/RenewalDetector.ts +1 -1
  17. package/src/domains/subscription/presentation/components/details/PremiumStatusBadge.tsx +6 -4
  18. package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.tsx +1 -1
  19. package/src/domains/subscription/presentation/types/SubscriptionDetailTypes.ts +4 -2
  20. package/src/domains/subscription/presentation/types/SubscriptionSettingsTypes.ts +1 -1
  21. package/src/domains/subscription/presentation/usePremiumGate.ts +1 -1
  22. package/src/domains/subscription/presentation/useSavedPurchaseAutoExecution.ts +1 -1
  23. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.ts +4 -3
  24. package/src/domains/subscription/presentation/useSubscriptionSettingsConfig.utils.ts +1 -1
  25. package/src/domains/trial/application/TrialEligibilityService.ts +1 -1
  26. package/src/domains/trial/infrastructure/DeviceTrialRepository.ts +2 -2
  27. package/src/shared/application/ports/IRevenueCatService.ts +2 -0
  28. package/src/shared/infrastructure/SubscriptionEventBus.ts +5 -2
  29. package/src/presentation/README.md +0 -125
  30. package/src/presentation/hooks/README.md +0 -156
  31. package/src/presentation/hooks/useAuthSubscriptionSync.md +0 -94
  32. package/src/presentation/hooks/useCredits.md +0 -103
  33. package/src/presentation/hooks/useDeductCredit.md +0 -100
  34. package/src/presentation/hooks/useFeatureGate.md +0 -112
  35. package/src/presentation/hooks/usePaywall.md +0 -89
  36. package/src/presentation/hooks/usePaywallOperations.md +0 -92
  37. package/src/presentation/hooks/usePaywallVisibility.md +0 -95
  38. package/src/presentation/hooks/usePremium.md +0 -88
  39. package/src/presentation/hooks/useSubscriptionSettingsConfig.md +0 -94
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.92",
3
+ "version": "2.27.94",
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,13 +1,14 @@
1
1
  import { Platform } from "react-native";
2
2
  import Constants from "expo-constants";
3
+ import {
4
+ getFirestore,
5
+ } from "@umituz/react-native-firebase";
3
6
  import {
4
7
  runTransaction,
5
8
  serverTimestamp,
6
- Timestamp,
7
9
  type Transaction,
8
10
  type DocumentReference,
9
- type Firestore,
10
- } from "@umituz/react-native-firebase";
11
+ } from "firebase/firestore";
11
12
  import type { CreditsConfig } from "../core/Credits";
12
13
  import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
13
14
  import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionStatus";
@@ -17,41 +18,72 @@ import { creditAllocationContext } from "./credit-strategies/CreditAllocationCon
17
18
  import type { InitializeCreditsMetadata, InitializationResult } from "../../subscription/application/SubscriptionInitializerTypes";
18
19
 
19
20
  export async function initializeCreditsTransaction(
20
- db: Firestore,
21
+ db: ReturnType<typeof getFirestore>,
21
22
  creditsRef: DocumentReference,
22
23
  config: CreditsConfig,
23
- purchaseId?: string,
24
- metadata?: InitializeCreditsMetadata
24
+ purchaseId: string,
25
+ metadata: InitializeCreditsMetadata
25
26
  ): Promise<InitializationResult> {
27
+ if (!db) {
28
+ throw new Error("Firestore instance is not available");
29
+ }
30
+
26
31
  return runTransaction(db, async (transaction: Transaction) => {
27
32
  const creditsDoc = await transaction.get(creditsRef);
28
33
  const now = serverTimestamp();
29
- const existingData = creditsDoc.exists() ? creditsDoc.data() as UserCreditsDocumentRead : null;
34
+ const existingData = creditsDoc.exists()
35
+ ? creditsDoc.data() as UserCreditsDocumentRead
36
+ : null;
37
+
38
+ if (!existingData) {
39
+ throw new Error("Credits document does not exist");
40
+ }
41
+
42
+ if (existingData.processedPurchases.includes(purchaseId)) {
43
+ return {
44
+ credits: existingData.credits,
45
+ alreadyProcessed: true,
46
+ finalData: existingData
47
+ };
48
+ }
30
49
 
31
- if (existingData && purchaseId && existingData.processedPurchases?.includes(purchaseId)) {
32
- return { credits: existingData.credits, alreadyProcessed: true };
50
+ const creditLimit = CreditLimitCalculator.calculate(metadata.productId, config);
51
+
52
+ const platform = Platform.OS;
53
+ if (platform !== "ios" && platform !== "android") {
54
+ throw new Error(`Invalid platform: ${platform}`);
55
+ }
56
+
57
+ const appVersion = Constants.expoConfig?.version;
58
+ if (!appVersion) {
59
+ throw new Error("appVersion is required in expoConfig");
33
60
  }
34
61
 
35
- const creditLimit = CreditLimitCalculator.calculate(metadata?.productId, config);
36
62
  const { purchaseHistory } = PurchaseMetadataGenerator.generate({
37
- productId: metadata?.productId,
38
- source: metadata?.source,
39
- type: metadata?.type,
40
- creditLimit,
41
- platform: Platform.OS as "ios" | "android",
42
- appVersion: Constants.expoConfig?.version,
63
+ productId: metadata.productId,
64
+ source: metadata.source,
65
+ type: metadata.type,
66
+ creditLimit,
67
+ platform,
68
+ appVersion,
43
69
  }, existingData);
44
70
 
45
- const isPremium = metadata?.isPremium ?? true;
46
- const isExpired = metadata?.expirationDate ? new Date(metadata.expirationDate).getTime() < Date.now() : false;
47
- const status = resolveSubscriptionStatus({
48
- isPremium, willRenew: metadata?.willRenew, isExpired, periodType: metadata?.periodType
71
+ const isPremium = metadata.isPremium;
72
+
73
+ let isExpired = false;
74
+ if (metadata.expirationDate) {
75
+ isExpired = new Date(metadata.expirationDate).getTime() < Date.now();
76
+ }
77
+
78
+ const status = resolveSubscriptionStatus({
79
+ isPremium,
80
+ willRenew: metadata.willRenew ?? false,
81
+ isExpired,
82
+ periodType: metadata.periodType ?? undefined,
49
83
  });
50
84
 
51
- // Resolve credits using Strategy Pattern
52
- const isStatusSync = purchaseId?.startsWith("status_sync_") ?? false;
85
+ const isStatusSync = purchaseId.startsWith("status_sync_");
53
86
  const isSubscriptionActive = isPremium && !isExpired;
54
- const productId = metadata?.productId;
55
87
 
56
88
  const newCredits = creditAllocationContext.allocate({
57
89
  status,
@@ -59,30 +91,57 @@ export async function initializeCreditsTransaction(
59
91
  existingData,
60
92
  creditLimit,
61
93
  isSubscriptionActive,
62
- productId,
94
+ productId: metadata.productId,
63
95
  });
64
96
 
97
+ const newProcessedPurchases = [...existingData.processedPurchases, purchaseId].slice(-50);
98
+
65
99
  const creditsData: Record<string, any> = {
66
- isPremium, status, credits: newCredits, creditLimit,
67
- lastUpdatedAt: now,
68
- // Increase history window to 50 for better idempotency
69
- processedPurchases: (purchaseId ? [...(existingData?.processedPurchases || []), purchaseId].slice(-50) : existingData?.processedPurchases) || [],
70
- purchaseHistory: purchaseHistory.length ? purchaseHistory : undefined
100
+ isPremium,
101
+ status,
102
+ credits: newCredits,
103
+ creditLimit,
104
+ lastUpdatedAt: now,
105
+ processedPurchases: newProcessedPurchases,
71
106
  };
72
107
 
73
- const isNewPurchaseOrRenewal = purchaseId?.startsWith("purchase_") || purchaseId?.startsWith("renewal_");
74
- if (isNewPurchaseOrRenewal) creditsData.lastPurchaseAt = now;
75
- if (metadata?.expirationDate) creditsData.expirationDate = Timestamp.fromDate(new Date(metadata.expirationDate));
76
- if (metadata?.willRenew !== undefined) creditsData.willRenew = metadata.willRenew;
77
- if (metadata?.originalTransactionId) creditsData.originalTransactionId = metadata.originalTransactionId;
78
- if (metadata?.productId) {
79
- creditsData.productId = metadata.productId;
80
- creditsData.platform = Platform.OS;
108
+ if (purchaseHistory.length > 0) {
109
+ creditsData.purchaseHistory = purchaseHistory;
110
+ }
111
+
112
+ const isNewPurchaseOrRenewal = purchaseId.startsWith("purchase_")
113
+ || purchaseId.startsWith("renewal_");
114
+
115
+ if (isNewPurchaseOrRenewal) {
116
+ creditsData.lastPurchaseAt = now;
117
+ }
118
+
119
+ if (metadata.expirationDate) {
120
+ creditsData.expirationDate = serverTimestamp();
121
+ }
122
+
123
+ if (metadata.willRenew !== undefined) {
124
+ creditsData.willRenew = metadata.willRenew;
125
+ }
126
+
127
+ if (metadata.originalTransactionId) {
128
+ creditsData.originalTransactionId = metadata.originalTransactionId;
81
129
  }
82
130
 
131
+ creditsData.productId = metadata.productId;
132
+ creditsData.platform = platform;
133
+
83
134
  transaction.set(creditsRef, creditsData, { merge: true });
84
-
85
- const finalData = { ...(existingData || {}), ...creditsData } as UserCreditsDocumentRead;
86
- return { credits: newCredits, finalData };
135
+
136
+ const finalData: UserCreditsDocumentRead = {
137
+ ...existingData,
138
+ ...creditsData,
139
+ };
140
+
141
+ return {
142
+ credits: newCredits,
143
+ alreadyProcessed: false,
144
+ finalData
145
+ };
87
146
  });
88
147
  }
@@ -16,37 +16,55 @@ export class DeductCreditsCommand implements IDeductCreditsCommand {
16
16
  private getCreditsRef: (db: Firestore, userId: string) => DocumentReference
17
17
  ) {}
18
18
 
19
- async execute(userId: string, cost: number = 1): Promise<DeductCreditsResult> {
19
+ async execute(userId: string, cost: number): Promise<DeductCreditsResult> {
20
20
  const db = getFirestore();
21
- if (!db) return { success: false, error: { message: "No DB", code: "ERR" } };
21
+ if (!db) {
22
+ return {
23
+ success: false,
24
+ remainingCredits: null,
25
+ error: { message: "No DB", code: "ERR" }
26
+ };
27
+ }
22
28
 
23
29
  try {
24
30
  const remaining = await runTransaction(db, async (tx: Transaction) => {
25
31
  const ref = this.getCreditsRef(db, userId);
26
32
  const docSnap = await tx.get(ref);
27
-
28
- if (!docSnap.exists()) throw new Error("NO_CREDITS");
29
-
33
+
34
+ if (!docSnap.exists()) {
35
+ throw new Error("NO_CREDITS");
36
+ }
37
+
30
38
  const current = docSnap.data().credits as number;
31
- if (current < cost) throw new Error("CREDITS_EXHAUSTED");
32
-
39
+ if (current < cost) {
40
+ throw new Error("CREDITS_EXHAUSTED");
41
+ }
42
+
33
43
  const updated = current - cost;
34
- tx.update(ref, {
35
- credits: updated,
36
- lastUpdatedAt: serverTimestamp()
44
+ tx.update(ref, {
45
+ credits: updated,
46
+ lastUpdatedAt: serverTimestamp()
37
47
  });
38
-
48
+
39
49
  return updated;
40
50
  });
41
51
 
42
52
  // Emit event via EventBus (Observer Pattern)
43
53
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, userId);
44
54
 
45
- return { success: true, remainingCredits: remaining };
55
+ return {
56
+ success: true,
57
+ remainingCredits: remaining,
58
+ error: null
59
+ };
46
60
  } catch (e: unknown) {
47
61
  const message = e instanceof Error ? e.message : String(e);
48
62
  const code = message === "NO_CREDITS" || message === "CREDITS_EXHAUSTED" ? message : "DEDUCT_ERR";
49
- return { success: false, error: { message, code } };
63
+ return {
64
+ success: false,
65
+ remainingCredits: null,
66
+ error: { message, code }
67
+ };
50
68
  }
51
69
  }
52
70
  }
@@ -1,6 +1,6 @@
1
1
  import { Timestamp } from "firebase/firestore";
2
- import type {
3
- PurchaseType,
2
+ import type {
3
+ PurchaseType,
4
4
  PurchaseMetadata,
5
5
  UserCreditsDocumentRead,
6
6
  PurchaseSource
@@ -8,37 +8,31 @@ import type {
8
8
  import { detectPackageType } from "../../../utils/packageTypeDetector";
9
9
 
10
10
  export interface MetadataGeneratorConfig {
11
- productId?: string;
12
- source?: PurchaseSource;
13
- type?: PurchaseType;
11
+ productId: string;
12
+ source: PurchaseSource;
13
+ type: PurchaseType;
14
14
  creditLimit: number;
15
15
  platform: "ios" | "android";
16
- appVersion?: string;
16
+ appVersion: string;
17
17
  }
18
18
 
19
19
  export class PurchaseMetadataGenerator {
20
20
  static generate(
21
21
  config: MetadataGeneratorConfig,
22
- existingData: UserCreditsDocumentRead | null
22
+ existingData: UserCreditsDocumentRead
23
23
  ): { purchaseType: PurchaseType; purchaseHistory: PurchaseMetadata[] } {
24
24
  const { productId, source, type, creditLimit, platform, appVersion } = config;
25
-
26
- if (!productId || !source) {
27
- return {
28
- purchaseType: type ?? "initial",
29
- purchaseHistory: existingData?.purchaseHistory || []
30
- };
31
- }
32
25
 
33
26
  const packageType = detectPackageType(productId);
34
- let purchaseType: PurchaseType = type ?? "initial";
27
+ let purchaseType: PurchaseType = type;
35
28
 
36
- if (existingData?.packageType && packageType !== "unknown") {
37
- const oldLimit = existingData.creditLimit || 0;
38
- if (creditLimit > oldLimit) purchaseType = "upgrade";
39
- else if (creditLimit < oldLimit) purchaseType = "downgrade";
40
- // This check is a bit fragile if purchaseId is not passed here,
41
- // but we use the explicit 'type' if provided.
29
+ if (packageType !== "unknown") {
30
+ const oldLimit = existingData.creditLimit;
31
+ if (creditLimit > oldLimit) {
32
+ purchaseType = "upgrade";
33
+ } else if (creditLimit < oldLimit) {
34
+ purchaseType = "downgrade";
35
+ }
42
36
  }
43
37
 
44
38
  const newMetadata: PurchaseMetadata = {
@@ -49,10 +43,10 @@ export class PurchaseMetadataGenerator {
49
43
  type: purchaseType,
50
44
  platform,
51
45
  appVersion,
52
- timestamp: Timestamp.fromDate(new Date()) as any,
46
+ timestamp: Timestamp.fromDate(new Date()),
53
47
  };
54
48
 
55
- const purchaseHistory = [...(existingData?.purchaseHistory || []), newMetadata].slice(-10);
49
+ const purchaseHistory = [...existingData.purchaseHistory, newMetadata].slice(-10);
56
50
 
57
51
  return { purchaseType, purchaseHistory };
58
52
  }
@@ -6,22 +6,22 @@
6
6
  */
7
7
 
8
8
  import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector";
9
- import type {
10
- SubscriptionStatusType,
11
- PeriodType,
12
- PackageType,
13
- Platform,
14
- PurchaseSource,
15
- PurchaseType
9
+ import type {
10
+ SubscriptionStatusType,
11
+ PeriodType,
12
+ PackageType,
13
+ Platform,
14
+ PurchaseSource,
15
+ PurchaseType
16
16
  } from "../../subscription/core/SubscriptionConstants";
17
17
 
18
- export type {
19
- SubscriptionStatusType,
20
- PeriodType,
21
- PackageType,
22
- Platform,
23
- PurchaseSource,
24
- PurchaseType
18
+ export type {
19
+ SubscriptionStatusType,
20
+ PeriodType,
21
+ PackageType,
22
+ Platform,
23
+ PurchaseSource,
24
+ PurchaseType
25
25
  };
26
26
 
27
27
  export type CreditType = "text" | "image";
@@ -36,30 +36,31 @@ export interface UserCredits {
36
36
  purchasedAt: Date | null;
37
37
  expirationDate: Date | null;
38
38
  lastUpdatedAt: Date | null;
39
+ lastPurchaseAt: Date | null;
39
40
 
40
41
  // RevenueCat subscription details
41
- willRenew: boolean;
42
- productId?: string;
43
- packageType?: PackageType;
44
- originalTransactionId?: string;
42
+ willRenew: boolean | null;
43
+ productId: string | null;
44
+ packageType: PackageType | null;
45
+ originalTransactionId: string | null;
45
46
 
46
47
  // Trial fields
47
- periodType?: PeriodType;
48
- isTrialing?: boolean;
49
- trialStartDate?: Date | null;
50
- trialEndDate?: Date | null;
51
- trialCredits?: number;
52
- convertedFromTrial?: boolean;
48
+ periodType: PeriodType | null;
49
+ isTrialing: boolean | null;
50
+ trialStartDate: Date | null;
51
+ trialEndDate: Date | null;
52
+ trialCredits: number | null;
53
+ convertedFromTrial: boolean | null;
53
54
 
54
55
  // Credits
55
56
  credits: number;
56
- creditLimit?: number;
57
+ creditLimit: number;
57
58
 
58
59
  // Metadata
59
- purchaseSource?: PurchaseSource;
60
- purchaseType?: PurchaseType;
61
- platform?: Platform;
62
- appVersion?: string;
60
+ purchaseSource: PurchaseSource | null;
61
+ purchaseType: PurchaseType | null;
62
+ platform: Platform;
63
+ appVersion: string | null;
63
64
  }
64
65
 
65
66
  export interface CreditAllocation {
@@ -75,28 +76,27 @@ export interface CreditsConfig {
75
76
  collectionName: string;
76
77
  creditLimit: number;
77
78
  /** When true, stores credits at users/{userId}/credits instead of {collectionName}/{userId} */
78
- useUserSubcollection?: boolean;
79
+ useUserSubcollection: boolean;
79
80
  /** Credit amounts per product ID for consumable credit packages */
80
- creditPackageAmounts?: Record<string, number>;
81
+ creditPackageAmounts: Record<string, number>;
81
82
  /** Credit allocations for different subscription types (weekly, monthly, yearly) */
82
- packageAllocations?: PackageAllocationMap;
83
+ packageAllocations: PackageAllocationMap;
83
84
  }
84
85
 
85
86
  export interface CreditsResult<T = UserCredits> {
86
87
  success: boolean;
87
- data?: T;
88
- error?: {
88
+ data: T | null;
89
+ error: {
89
90
  message: string;
90
91
  code: string;
91
- };
92
+ } | null;
92
93
  }
93
94
 
94
95
  export interface DeductCreditsResult {
95
96
  success: boolean;
96
- remainingCredits?: number;
97
- error?: {
97
+ remainingCredits: number | null;
98
+ error: {
98
99
  message: string;
99
100
  code: string;
100
- };
101
+ } | null;
101
102
  }
102
-
@@ -6,8 +6,8 @@ import type { UserCreditsDocumentRead } from "./UserCreditsDocument";
6
6
  /** Maps Firestore document to domain entity with expiration validation */
7
7
  export class CreditsMapper {
8
8
  static toEntity(doc: UserCreditsDocumentRead): UserCredits {
9
- const expirationDate = doc.expirationDate?.toDate?.() ?? null;
10
- const periodType = doc.periodType as PeriodType | undefined;
9
+ const expirationDate = doc.expirationDate ? doc.expirationDate.toDate() : null;
10
+ const periodType = doc.periodType;
11
11
 
12
12
  // Validate isPremium against expirationDate (real-time check)
13
13
  const { isPremium, status } = CreditsMapper.validateSubscription(doc, expirationDate, periodType);
@@ -18,12 +18,13 @@ export class CreditsMapper {
18
18
  status,
19
19
 
20
20
  // Dates
21
- purchasedAt: doc.purchasedAt?.toDate?.() ?? null,
21
+ purchasedAt: doc.purchasedAt.toDate(),
22
22
  expirationDate,
23
- lastUpdatedAt: doc.lastUpdatedAt?.toDate?.() ?? null,
23
+ lastUpdatedAt: doc.lastUpdatedAt.toDate(),
24
+ lastPurchaseAt: doc.lastPurchaseAt ? doc.lastPurchaseAt.toDate() : null,
24
25
 
25
26
  // RevenueCat details
26
- willRenew: doc.willRenew ?? false,
27
+ willRenew: doc.willRenew,
27
28
  productId: doc.productId,
28
29
  packageType: doc.packageType,
29
30
  originalTransactionId: doc.originalTransactionId,
@@ -31,8 +32,8 @@ export class CreditsMapper {
31
32
  // Trial fields
32
33
  periodType,
33
34
  isTrialing: doc.isTrialing,
34
- trialStartDate: doc.trialStartDate?.toDate?.() ?? null,
35
- trialEndDate: doc.trialEndDate?.toDate?.() ?? null,
35
+ trialStartDate: doc.trialStartDate ? doc.trialStartDate.toDate() : null,
36
+ trialEndDate: doc.trialEndDate ? doc.trialEndDate.toDate() : null,
36
37
  trialCredits: doc.trialCredits,
37
38
  convertedFromTrial: doc.convertedFromTrial,
38
39
 
@@ -52,9 +53,9 @@ export class CreditsMapper {
52
53
  private static validateSubscription(
53
54
  doc: UserCreditsDocumentRead,
54
55
  expirationDate: Date | null,
55
- periodType?: PeriodType
56
+ periodType: PeriodType | null
56
57
  ): { isPremium: boolean; status: SubscriptionStatusType } {
57
- const isPremium = doc.isPremium ?? false;
58
+ const isPremium = doc.isPremium;
58
59
  const willRenew = doc.willRenew ?? false;
59
60
  const isExpired = expirationDate ? expirationDate < new Date() : false;
60
61
 
@@ -62,7 +63,7 @@ export class CreditsMapper {
62
63
  isPremium,
63
64
  willRenew,
64
65
  isExpired,
65
- periodType,
66
+ periodType: periodType ?? undefined,
66
67
  });
67
68
 
68
69
  // Override isPremium if expired
@@ -1,17 +1,17 @@
1
- import type {
2
- PurchaseSource,
3
- PurchaseType,
4
- SubscriptionStatusType,
1
+ import type {
2
+ PurchaseSource,
3
+ PurchaseType,
4
+ SubscriptionStatusType,
5
5
  PeriodType,
6
6
  PackageType,
7
7
  Platform
8
8
  } from "../../subscription/core/SubscriptionConstants";
9
9
 
10
- export type {
11
- PurchaseSource,
12
- PurchaseType,
13
- SubscriptionStatusType,
14
- PeriodType
10
+ export type {
11
+ PurchaseSource,
12
+ PurchaseType,
13
+ SubscriptionStatusType,
14
+ PeriodType
15
15
  };
16
16
 
17
17
  export interface FirestoreTimestamp {
@@ -25,45 +25,45 @@ export interface PurchaseMetadata {
25
25
  source: PurchaseSource;
26
26
  type: PurchaseType;
27
27
  platform: Platform;
28
- appVersion?: string;
28
+ appVersion: string;
29
29
  timestamp: FirestoreTimestamp;
30
30
  }
31
31
 
32
32
  /** Single Source of Truth for user subscription data */
33
33
  export interface UserCreditsDocumentRead {
34
34
  // Core subscription status
35
- isPremium?: boolean;
36
- status?: SubscriptionStatusType;
35
+ isPremium: boolean;
36
+ status: SubscriptionStatusType;
37
37
 
38
38
  // Dates (all from RevenueCat)
39
- purchasedAt?: FirestoreTimestamp;
40
- expirationDate?: FirestoreTimestamp;
41
- lastUpdatedAt?: FirestoreTimestamp;
42
- lastPurchaseAt?: FirestoreTimestamp;
39
+ purchasedAt: FirestoreTimestamp;
40
+ expirationDate: FirestoreTimestamp | null;
41
+ lastUpdatedAt: FirestoreTimestamp;
42
+ lastPurchaseAt: FirestoreTimestamp | null;
43
43
 
44
44
  // RevenueCat subscription details
45
- willRenew?: boolean;
46
- productId?: string;
47
- packageType?: PackageType;
48
- originalTransactionId?: string;
45
+ willRenew: boolean | null;
46
+ productId: string | null;
47
+ packageType: PackageType | null;
48
+ originalTransactionId: string | null;
49
49
 
50
50
  // Trial fields
51
- periodType?: PeriodType;
52
- isTrialing?: boolean;
53
- trialStartDate?: FirestoreTimestamp;
54
- trialEndDate?: FirestoreTimestamp;
55
- trialCredits?: number;
56
- convertedFromTrial?: boolean;
51
+ periodType: PeriodType | null;
52
+ isTrialing: boolean | null;
53
+ trialStartDate: FirestoreTimestamp | null;
54
+ trialEndDate: FirestoreTimestamp | null;
55
+ trialCredits: number | null;
56
+ convertedFromTrial: boolean | null;
57
57
 
58
58
  // Credits
59
59
  credits: number;
60
- creditLimit?: number;
60
+ creditLimit: number;
61
61
 
62
62
  // Metadata
63
- purchaseSource?: PurchaseSource;
64
- purchaseType?: PurchaseType;
65
- platform?: Platform;
66
- appVersion?: string;
67
- processedPurchases?: string[];
68
- purchaseHistory?: PurchaseMetadata[];
63
+ purchaseSource: PurchaseSource | null;
64
+ purchaseType: PurchaseType | null;
65
+ platform: Platform;
66
+ appVersion: string | null;
67
+ processedPurchases: string[];
68
+ purchaseHistory: PurchaseMetadata[];
69
69
  }