@umituz/react-native-subscription 2.37.109 → 2.37.111

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 (29) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditsInitializer.ts +5 -5
  3. package/src/domains/credits/application/creditDocumentHelpers.ts +1 -1
  4. package/src/domains/credits/application/creditOperationUtils.ts +1 -1
  5. package/src/domains/credits/core/Credits.ts +1 -1
  6. package/src/domains/credits/core/CreditsMapper.ts +1 -1
  7. package/src/domains/credits/core/UserCreditsDocument.ts +1 -1
  8. package/src/domains/credits/infrastructure/CreditsRepository.ts +5 -4
  9. package/src/domains/credits/infrastructure/operations/CreditsInitializer.ts +1 -1
  10. package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +7 -18
  11. package/src/domains/revenuecat/core/types/RevenueCatData.ts +4 -10
  12. package/src/domains/revenuecat/infrastructure/services/userSwitchHandler.ts +6 -2
  13. package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +3 -11
  14. package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +9 -9
  15. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +6 -6
  16. package/src/domains/subscription/application/syncIdGenerators.ts +6 -6
  17. package/src/domains/subscription/core/SubscriptionEvents.ts +4 -7
  18. package/src/domains/subscription/core/SubscriptionStatus.ts +4 -4
  19. package/src/domains/subscription/core/types/CreditInfo.ts +10 -0
  20. package/src/domains/subscription/core/types/PremiumStatus.ts +21 -0
  21. package/src/domains/subscription/core/types/SubscriptionMetadata.ts +18 -0
  22. package/src/domains/subscription/core/types/index.ts +3 -0
  23. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +2 -15
  24. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +1 -1
  25. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +6 -2
  26. package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +2 -6
  27. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +3 -7
  28. package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +3 -14
  29. package/src/index.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.37.109",
3
+ "version": "2.37.111",
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",
@@ -41,14 +41,14 @@ export async function initializeCreditsTransaction(
41
41
 
42
42
  // Global cross-user deduplication: prevent the same Apple/Google transaction
43
43
  // from allocating credits under multiple Firebase UIDs.
44
- if (metadata.originalTransactionId) {
45
- const globalRef = doc(_db, GLOBAL_TRANSACTION_COLLECTION, metadata.originalTransactionId);
44
+ if (metadata.storeTransactionId) {
45
+ const globalRef = doc(_db, GLOBAL_TRANSACTION_COLLECTION, metadata.storeTransactionId);
46
46
  const globalDoc = await transaction.get(globalRef);
47
47
  if (globalDoc.exists()) {
48
48
  const globalData = globalDoc.data();
49
49
  if (globalData?.ownerUserId && globalData.ownerUserId !== userId) {
50
50
  console.warn(
51
- `[CreditsInitializer] Transaction ${metadata.originalTransactionId} already processed by user ${globalData.ownerUserId}, skipping for ${userId}`
51
+ `[CreditsInitializer] Transaction ${metadata.storeTransactionId} already processed by user ${globalData.ownerUserId}, skipping for ${userId}`
52
52
  );
53
53
  return {
54
54
  credits: existingData.credits,
@@ -89,8 +89,8 @@ export async function initializeCreditsTransaction(
89
89
  transaction.set(creditsRef, creditsData, { merge: true });
90
90
 
91
91
  // Register transaction globally so other UIDs cannot claim the same purchase.
92
- if (metadata.originalTransactionId) {
93
- const globalRef = doc(_db, GLOBAL_TRANSACTION_COLLECTION, metadata.originalTransactionId);
92
+ if (metadata.storeTransactionId) {
93
+ const globalRef = doc(_db, GLOBAL_TRANSACTION_COLLECTION, metadata.storeTransactionId);
94
94
  transaction.set(globalRef, {
95
95
  ownerUserId: userId,
96
96
  purchaseId,
@@ -38,7 +38,7 @@ export function getCreditDocumentOrDefault(
38
38
  willRenew: false,
39
39
  productId: null,
40
40
  packageType: null,
41
- originalTransactionId: null,
41
+ storeTransactionId: null,
42
42
  store: null,
43
43
  ownershipType: null,
44
44
  appVersion: null,
@@ -68,7 +68,7 @@ export function buildCreditsData({
68
68
  ...(isPurchaseOrRenewal && { lastPurchaseAt: serverTimestamp() }),
69
69
  ...(expirationTimestamp && { expirationDate: expirationTimestamp }),
70
70
  ...(metadata.willRenew !== undefined && { willRenew: metadata.willRenew }),
71
- ...(metadata.originalTransactionId && { originalTransactionId: metadata.originalTransactionId }),
71
+ ...(metadata.storeTransactionId && { storeTransactionId: metadata.storeTransactionId }),
72
72
  ...(canceledAtTimestamp && { canceledAt: canceledAtTimestamp }),
73
73
  ...(billingIssueTimestamp && { billingIssueDetectedAt: billingIssueTimestamp }),
74
74
  ...(metadata.store && { store: metadata.store }),
@@ -19,7 +19,7 @@ export interface UserCredits {
19
19
  willRenew: boolean | null;
20
20
  productId: string | null;
21
21
  packageType: PackageType | null;
22
- originalTransactionId: string | null;
22
+ storeTransactionId: string | null;
23
23
  periodType: string | null;
24
24
  credits: number;
25
25
  creditLimit: number;
@@ -41,7 +41,7 @@ export function mapCreditsDocumentToEntity(doc: UserCreditsDocumentRead): UserCr
41
41
  willRenew: doc.willRenew,
42
42
  productId: doc.productId,
43
43
  packageType: doc.packageType,
44
- originalTransactionId: doc.originalTransactionId,
44
+ storeTransactionId: doc.storeTransactionId,
45
45
  periodType,
46
46
  credits: doc.credits,
47
47
  creditLimit: doc.creditLimit,
@@ -42,7 +42,7 @@ export interface UserCreditsDocumentRead {
42
42
  willRenew: boolean | null;
43
43
  productId: string | null;
44
44
  packageType: PackageType | null;
45
- originalTransactionId: string | null;
45
+ storeTransactionId: string | null;
46
46
  store: Store | null;
47
47
  ownershipType: OwnershipType | null;
48
48
  periodType: string | null;
@@ -8,7 +8,8 @@ import { refundCreditsOperation } from "../application/RefundCreditsCommand";
8
8
  import { PURCHASE_TYPE, type PurchaseType } from "../../subscription/core/SubscriptionConstants";
9
9
  import { requireFirestore, buildDocRef, type CollectionConfig } from "../../../shared/infrastructure/firestore";
10
10
  import { fetchCredits, checkHasCredits, documentExists } from "./operations/CreditsFetcher";
11
- import { syncExpiredStatus, syncPremiumMetadata, createRecoveryCreditsDocument, type PremiumMetadata } from "./operations/CreditsWriter";
11
+ import { syncExpiredStatus, syncPremiumMetadata, createRecoveryCreditsDocument } from "./operations/CreditsWriter";
12
+ import type { SubscriptionMetadata } from "../../subscription/core/types";
12
13
  import { initializeCreditsWithRetry } from "./operations/CreditsInitializer";
13
14
  import { calculateCreditLimit } from "../application/CreditLimitCalculator";
14
15
 
@@ -81,7 +82,7 @@ export class CreditsRepository extends BaseRepository {
81
82
  await syncExpiredStatus(this.getRef(db, userId));
82
83
  }
83
84
 
84
- async syncPremiumMetadata(userId: string, metadata: PremiumMetadata): Promise<void> {
85
+ async syncPremiumMetadata(userId: string, metadata: SubscriptionMetadata): Promise<void> {
85
86
  const db = requireFirestore();
86
87
  await syncPremiumMetadata(this.getRef(db, userId), metadata);
87
88
  }
@@ -92,7 +93,7 @@ export class CreditsRepository extends BaseRepository {
92
93
  willRenew: boolean,
93
94
  expirationDate: string | null,
94
95
  periodType: string | null,
95
- originalTransactionId?: string | null,
96
+ storeTransactionId?: string | null,
96
97
  ): Promise<boolean> {
97
98
  const db = requireFirestore();
98
99
  const creditLimit = calculateCreditLimit(productId, this.config);
@@ -105,7 +106,7 @@ export class CreditsRepository extends BaseRepository {
105
106
  periodType,
106
107
  db,
107
108
  userId,
108
- originalTransactionId,
109
+ storeTransactionId,
109
110
  );
110
111
  }
111
112
  }
@@ -63,7 +63,7 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
63
63
  source,
64
64
  expirationDate: revenueCatData.expirationDate,
65
65
  willRenew: revenueCatData.willRenew,
66
- originalTransactionId: revenueCatData.originalTransactionId,
66
+ storeTransactionId: revenueCatData.storeTransactionId,
67
67
  isPremium: revenueCatData.isPremium,
68
68
  periodType: revenueCatData.periodType,
69
69
  unsubscribeDetectedAt: revenueCatData.unsubscribeDetectedAt,
@@ -3,6 +3,7 @@ import { runTransaction, serverTimestamp } from "@umituz/react-native-firebase";
3
3
  import { doc, getDoc, setDoc } from "firebase/firestore";
4
4
  import { SUBSCRIPTION_STATUS } from "../../../subscription/core/SubscriptionConstants";
5
5
  import { resolveSubscriptionStatus } from "../../../subscription/core/SubscriptionStatus";
6
+ import type { SubscriptionMetadata } from "../../../subscription/core/types";
6
7
  import { toTimestamp } from "../../../../shared/utils/dateConverter";
7
8
  import { isPast } from "../../../../utils/dateUtils";
8
9
  import { getAppVersion, validatePlatform } from "../../../../utils/appUtils";
@@ -25,22 +26,10 @@ export async function syncExpiredStatus(ref: DocumentReference): Promise<void> {
25
26
  });
26
27
  }
27
28
 
28
- export interface PremiumMetadata {
29
- isPremium: boolean;
30
- willRenew: boolean;
31
- expirationDate: string | null;
32
- productId: string;
33
- periodType: string | null;
34
- unsubscribeDetectedAt: string | null;
35
- billingIssueDetectedAt: string | null;
36
- store: string | null;
37
- ownershipType: string | null;
38
- }
39
-
40
29
  // Fix: was getDoc+setDoc (non-atomic) — now uses runTransaction.
41
30
  export async function syncPremiumMetadata(
42
31
  ref: DocumentReference,
43
- metadata: PremiumMetadata
32
+ metadata: SubscriptionMetadata
44
33
  ): Promise<void> {
45
34
  await runTransaction(async (tx: Transaction) => {
46
35
  const doc = await tx.get(ref);
@@ -78,7 +67,7 @@ export async function syncPremiumMetadata(
78
67
  * This handles edge cases like test store purchases, reinstalls, or failed initializations.
79
68
  * Returns true if a new document was created, false if one already existed.
80
69
  *
81
- * Cross-user guard: if originalTransactionId is provided and already registered
70
+ * Cross-user guard: if storeTransactionId is provided and already registered
82
71
  * to a different user in the global processedTransactions collection, the recovery
83
72
  * document is NOT created (the subscription belongs to another UID).
84
73
  */
@@ -91,22 +80,22 @@ export async function createRecoveryCreditsDocument(
91
80
  periodType: string | null,
92
81
  db?: Firestore,
93
82
  userId?: string,
94
- originalTransactionId?: string | null,
83
+ storeTransactionId?: string | null,
95
84
  ): Promise<boolean> {
96
85
  const existingDoc = await getDoc(ref);
97
86
  if (existingDoc.exists()) return false;
98
87
 
99
88
  // Cross-user deduplication: if this transaction was already processed by another
100
89
  // user, skip recovery to prevent double credit allocation across UIDs.
101
- if (db && userId && originalTransactionId) {
90
+ if (db && userId && storeTransactionId) {
102
91
  try {
103
- const globalRef = doc(db, GLOBAL_TRANSACTION_COLLECTION, originalTransactionId);
92
+ const globalRef = doc(db, GLOBAL_TRANSACTION_COLLECTION, storeTransactionId);
104
93
  const globalDoc = await getDoc(globalRef);
105
94
  if (globalDoc.exists()) {
106
95
  const globalData = globalDoc.data();
107
96
  if (globalData?.ownerUserId && globalData.ownerUserId !== userId) {
108
97
  console.warn(
109
- `[CreditsWriter] Recovery skipped: transaction ${originalTransactionId} belongs to user ${globalData.ownerUserId}, not ${userId}`
98
+ `[CreditsWriter] Recovery skipped: transaction ${storeTransactionId} belongs to user ${globalData.ownerUserId}, not ${userId}`
110
99
  );
111
100
  return false;
112
101
  }
@@ -1,15 +1,9 @@
1
- import type { Store, OwnershipType, PackageType } from "./RevenueCatTypes";
1
+ import type { SubscriptionMetadata } from "../../../subscription/core/types";
2
+ import type { PackageType } from "./RevenueCatTypes";
2
3
 
3
- export interface RevenueCatData {
4
- expirationDate: string | null;
4
+ export interface RevenueCatData extends Omit<SubscriptionMetadata, 'willRenew' | 'productId'> {
5
5
  willRenew: boolean | null;
6
- originalTransactionId: string | null;
7
- isPremium: boolean;
8
- periodType: string | null;
6
+ storeTransactionId: string | null;
9
7
  packageType: PackageType | null;
10
- unsubscribeDetectedAt: string | null;
11
- billingIssueDetectedAt: string | null;
12
- store: Store | null;
13
- ownershipType: OwnershipType | null;
14
8
  revenueCatUserId?: string | null;
15
9
  }
@@ -193,10 +193,14 @@ export async function handleInitialConfiguration(
193
193
  userId: normalizedUserId,
194
194
  isPremium: true,
195
195
  productId: premiumEntitlement.productIdentifier,
196
- expiresAt: premiumEntitlement.expirationDate ?? undefined,
196
+ expirationDate: premiumEntitlement.expirationDate ?? null,
197
197
  willRenew: premiumEntitlement.willRenew,
198
198
  periodType: premiumEntitlement.periodType as PeriodType | undefined,
199
- originalTransactionId: subscription?.storeTransactionId ?? undefined,
199
+ storeTransactionId: subscription?.storeTransactionId ?? undefined,
200
+ unsubscribeDetectedAt: premiumEntitlement.unsubscribeDetectedAt ?? null,
201
+ billingIssueDetectedAt: premiumEntitlement.billingIssueDetectedAt ?? null,
202
+ store: premiumEntitlement.store ?? null,
203
+ ownershipType: premiumEntitlement.ownershipType ?? null,
200
204
  });
201
205
  } else {
202
206
  await deps.config.onPremiumStatusChanged({
@@ -1,7 +1,7 @@
1
1
  import type { CreditsConfig } from "../../credits/core/Credits";
2
2
  import type { UserCreditsDocumentRead } from "../../credits/core/UserCreditsDocument";
3
3
  import type { PurchaseSource, PurchaseType } from "../core/SubscriptionConstants";
4
- import type { Store, OwnershipType } from "../../revenuecat/core/types";
4
+ import type { SubscriptionMetadata } from "../core/types";
5
5
 
6
6
  export interface FirebaseAuthLike {
7
7
  currentUser: { uid: string; isAnonymous: boolean } | null;
@@ -28,19 +28,11 @@ export interface SubscriptionInitConfig {
28
28
  authStateTimeoutMs?: number;
29
29
  }
30
30
 
31
- export interface InitializeCreditsMetadata {
32
- productId: string;
31
+ export interface InitializeCreditsMetadata extends Omit<SubscriptionMetadata, 'willRenew'> {
33
32
  source: PurchaseSource;
34
33
  type: PurchaseType;
35
- expirationDate: string | null;
36
34
  willRenew: boolean | null;
37
- originalTransactionId: string | null;
38
- isPremium: boolean;
39
- periodType: string | null;
40
- unsubscribeDetectedAt: string | null;
41
- billingIssueDetectedAt: string | null;
42
- store: Store | null;
43
- ownershipType: OwnershipType | null;
35
+ storeTransactionId: string | null;
44
36
  revenueCatUserId?: string | null;
45
37
  }
46
38
 
@@ -110,7 +110,7 @@ export class SubscriptionSyncProcessor {
110
110
  revenueCatData.packageType = event.packageType ?? null;
111
111
  const revenueCatAppUserId = await this.getRevenueCatAppUserId();
112
112
  revenueCatData.revenueCatUserId = revenueCatAppUserId ?? event.userId;
113
- const purchaseId = generatePurchaseId(revenueCatData.originalTransactionId, event.productId);
113
+ const purchaseId = generatePurchaseId(revenueCatData.storeTransactionId, event.productId);
114
114
 
115
115
  const creditsUserId = await this.getCreditsUserId(event.userId);
116
116
 
@@ -140,7 +140,7 @@ export class SubscriptionSyncProcessor {
140
140
  revenueCatData.expirationDate = event.newExpirationDate ?? revenueCatData.expirationDate;
141
141
  const revenueCatAppUserId = await this.getRevenueCatAppUserId();
142
142
  revenueCatData.revenueCatUserId = revenueCatAppUserId ?? event.userId;
143
- const purchaseId = generateRenewalId(revenueCatData.originalTransactionId, event.productId, event.newExpirationDate);
143
+ const purchaseId = generateRenewalId(revenueCatData.storeTransactionId, event.productId, event.newExpirationDate);
144
144
 
145
145
  const creditsUserId = await this.getCreditsUserId(event.userId);
146
146
 
@@ -220,9 +220,9 @@ export class SubscriptionSyncProcessor {
220
220
  userId,
221
221
  event.productId!,
222
222
  event.willRenew ?? false,
223
- event.expiresAt ?? null,
223
+ event.expirationDate ?? null,
224
224
  event.periodType ?? null,
225
- event.originalTransactionId,
225
+ event.storeTransactionId,
226
226
  );
227
227
  if (__DEV__ && created) {
228
228
  console.log('[SubscriptionSyncProcessor] Recovery: created missing credits document for premium user', {
@@ -235,13 +235,13 @@ export class SubscriptionSyncProcessor {
235
235
  await repo.syncPremiumMetadata(userId, {
236
236
  isPremium: event.isPremium,
237
237
  willRenew: event.willRenew ?? false,
238
- expirationDate: event.expiresAt ?? null,
238
+ expirationDate: event.expirationDate ?? null,
239
239
  productId: event.productId!,
240
240
  periodType: event.periodType ?? null,
241
- unsubscribeDetectedAt: null,
242
- billingIssueDetectedAt: null,
243
- store: null,
244
- ownershipType: null,
241
+ unsubscribeDetectedAt: event.unsubscribeDetectedAt ?? null,
242
+ billingIssueDetectedAt: event.billingIssueDetectedAt ?? null,
243
+ store: event.store ?? null,
244
+ ownershipType: event.ownershipType ?? null,
245
245
  });
246
246
  this.emitCreditsUpdated(userId);
247
247
  }
@@ -23,7 +23,7 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
23
23
  return {
24
24
  expirationDate: null,
25
25
  willRenew: null,
26
- originalTransactionId: null,
26
+ storeTransactionId: null,
27
27
  periodType: null,
28
28
  packageType: null,
29
29
  isPremium: false,
@@ -39,11 +39,11 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
39
39
  return {
40
40
  expirationDate: entitlement.expirationDate ?? null,
41
41
  willRenew: entitlement.willRenew ?? null,
42
- // Maps SDK's `storeTransactionId` (current transaction ID from Apple/Google)
43
- // to our domain's `originalTransactionId`. Used as dedup key in processedTransactions.
44
- // Note: The SDK does not expose `original_store_transaction_id` (the first purchase's
45
- // ID that stays constant across renewals) only available via server-side webhooks.
46
- originalTransactionId: subscription?.storeTransactionId ?? null,
42
+ // Current transaction ID from Apple/Google — changes with each renewal.
43
+ // Used as dedup key in processedTransactions collection.
44
+ // Note: original_store_transaction_id (stable across renewals) is only
45
+ // available server-side (webhooks/REST API), not in the client SDK.
46
+ storeTransactionId: subscription?.storeTransactionId ?? null,
47
47
  periodType: validatePeriodType(entitlement.periodType),
48
48
  packageType: null,
49
49
  isPremium: true,
@@ -1,13 +1,13 @@
1
1
  const uniqueSuffix = (): string => `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
2
2
 
3
- export const generatePurchaseId = (originalTransactionId: string | null, productId: string): string => {
4
- return originalTransactionId
5
- ? `purchase_${originalTransactionId}`
3
+ export const generatePurchaseId = (storeTransactionId: string | null, productId: string): string => {
4
+ return storeTransactionId
5
+ ? `purchase_${storeTransactionId}`
6
6
  : `purchase_${productId}_${uniqueSuffix()}`;
7
7
  };
8
8
 
9
- export const generateRenewalId = (originalTransactionId: string | null, productId: string, expirationDate: string): string => {
10
- return originalTransactionId
11
- ? `renewal_${originalTransactionId}_${expirationDate}`
9
+ export const generateRenewalId = (storeTransactionId: string | null, productId: string, expirationDate: string): string => {
10
+ return storeTransactionId
11
+ ? `renewal_${storeTransactionId}_${expirationDate}`
12
12
  : `renewal_${productId}_${uniqueSuffix()}`;
13
13
  };
@@ -1,5 +1,6 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
- import type { PeriodType, PurchaseSource } from "./SubscriptionConstants";
2
+ import type { PurchaseSource } from "./SubscriptionConstants";
3
+ import type { SubscriptionMetadata } from "./types";
3
4
  import type { PackageType } from "../../revenuecat/core/types/RevenueCatTypes";
4
5
 
5
6
  export interface PurchaseCompletedEvent {
@@ -17,14 +18,10 @@ export interface RenewalDetectedEvent {
17
18
  customerInfo: CustomerInfo;
18
19
  }
19
20
 
20
- export interface PremiumStatusChangedEvent {
21
+ export interface PremiumStatusChangedEvent extends Partial<SubscriptionMetadata> {
21
22
  userId: string;
22
23
  isPremium: boolean;
23
- productId?: string;
24
- expiresAt?: string;
25
- willRenew?: boolean;
26
- periodType?: PeriodType;
27
- originalTransactionId?: string;
24
+ storeTransactionId?: string;
28
25
  }
29
26
 
30
27
  export interface PlanChangedEvent {
@@ -12,7 +12,7 @@ export type { SubscriptionStatusType };
12
12
 
13
13
  export interface SubscriptionStatus {
14
14
  isPremium: boolean;
15
- expiresAt: string | null;
15
+ expirationDate: string | null;
16
16
  productId: string | null;
17
17
  purchasedAt?: string | null;
18
18
  customerId?: string | null;
@@ -23,7 +23,7 @@ export interface SubscriptionStatus {
23
23
 
24
24
  export const createDefaultSubscriptionStatus = (): SubscriptionStatus => ({
25
25
  isPremium: false,
26
- expiresAt: null,
26
+ expirationDate: null,
27
27
  productId: null,
28
28
  purchasedAt: null,
29
29
  customerId: null,
@@ -33,8 +33,8 @@ export const createDefaultSubscriptionStatus = (): SubscriptionStatus => ({
33
33
 
34
34
  export const isSubscriptionValid = (status: SubscriptionStatus | null): boolean => {
35
35
  if (!status || !status.isPremium) return false;
36
- if (!status.expiresAt) return true;
37
- return timezoneService.isFuture(new Date(status.expiresAt));
36
+ if (!status.expirationDate) return true;
37
+ return timezoneService.isFuture(new Date(status.expirationDate));
38
38
  };
39
39
 
40
40
  export interface StatusResolverInput {
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Credit usage information for display in subscription UI components.
3
+ * Single source of truth — used by SubscriptionDetailScreen and PremiumDetailsCard.
4
+ */
5
+ export interface CreditInfo {
6
+ id: string;
7
+ label: string;
8
+ current: number;
9
+ total: number;
10
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Resolved premium status from RevenueCat CustomerInfo.
3
+ * Uses Date objects (presentation-ready).
4
+ *
5
+ * Extended by SubscriptionStatusResult which adds hook state (isLoading, error, refetch).
6
+ */
7
+ export interface PremiumStatus {
8
+ isPremium: boolean;
9
+ expirationDate: Date | null;
10
+ willRenew: boolean;
11
+ productIdentifier: string | null;
12
+ originalPurchaseDate: Date | null;
13
+ latestPurchaseDate: Date | null;
14
+ billingIssuesDetected: boolean;
15
+ isSandbox: boolean;
16
+ periodType: string | null;
17
+ packageType: string | null;
18
+ store: string | null;
19
+ gracePeriodExpiresDate: Date | null;
20
+ unsubscribeDetectedAt: Date | null;
21
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Base subscription metadata — the single source of truth for subscription state fields.
3
+ *
4
+ * All subscription-related types (RevenueCatData, PremiumStatusChangedEvent,
5
+ * InitializeCreditsMetadata) extend or compose from this base to eliminate
6
+ * field duplication and ensure consistent naming.
7
+ */
8
+ export interface SubscriptionMetadata {
9
+ isPremium: boolean;
10
+ willRenew: boolean;
11
+ expirationDate: string | null;
12
+ productId: string;
13
+ periodType: string | null;
14
+ unsubscribeDetectedAt: string | null;
15
+ billingIssueDetectedAt: string | null;
16
+ store: string | null;
17
+ ownershipType: string | null;
18
+ }
@@ -0,0 +1,3 @@
1
+ export type { SubscriptionMetadata } from "./SubscriptionMetadata";
2
+ export type { PremiumStatus } from "./PremiumStatus";
3
+ export type { CreditInfo } from "./CreditInfo";
@@ -2,22 +2,9 @@ import type { CustomerInfo } from "react-native-purchases";
2
2
  import { getPremiumEntitlement } from "../../../revenuecat/core/types";
3
3
  import { toDate } from "../../../../shared/utils/dateConverter";
4
4
  import { detectPackageType } from "../../../../utils/packageTypeDetector";
5
+ import type { PremiumStatus } from "../../core/types";
5
6
 
6
- export interface PremiumStatus {
7
- isPremium: boolean;
8
- expirationDate: Date | null;
9
- willRenew: boolean;
10
- productIdentifier: string | null;
11
- originalPurchaseDate: Date | null;
12
- latestPurchaseDate: Date | null;
13
- billingIssuesDetected: boolean;
14
- isSandbox: boolean;
15
- periodType: string | null;
16
- packageType: string | null;
17
- store: string | null;
18
- gracePeriodExpiresDate: Date | null;
19
- unsubscribeDetectedAt: Date | null;
20
- }
7
+ export type { PremiumStatus };
21
8
 
22
9
  export class PurchaseStatusResolver {
23
10
  static resolve(customerInfo: CustomerInfo, entitlementId: string): PremiumStatus {
@@ -1,5 +1,5 @@
1
1
  import type { RevenueCatConfig } from "../../../revenuecat/core/types";
2
- import type { PremiumStatus } from "../handlers/PurchaseStatusResolver";
2
+ import type { PremiumStatus } from "../../core/types";
3
3
  import type { RestoreResultInfo } from "../handlers/package-operations/types";
4
4
 
5
5
  export interface SubscriptionManagerConfig {
@@ -36,10 +36,14 @@ export async function syncPremiumStatus(
36
36
  userId,
37
37
  isPremium: true,
38
38
  productId: premiumEntitlement.productIdentifier,
39
- expiresAt: premiumEntitlement.expirationDate ?? undefined,
39
+ expirationDate: premiumEntitlement.expirationDate ?? null,
40
40
  willRenew: premiumEntitlement.willRenew,
41
41
  periodType: premiumEntitlement.periodType as PeriodType | undefined,
42
- originalTransactionId: subscription?.storeTransactionId ?? undefined,
42
+ storeTransactionId: subscription?.storeTransactionId ?? undefined,
43
+ unsubscribeDetectedAt: premiumEntitlement.unsubscribeDetectedAt ?? null,
44
+ billingIssueDetectedAt: premiumEntitlement.billingIssueDetectedAt ?? null,
45
+ store: premiumEntitlement.store ?? null,
46
+ ownershipType: premiumEntitlement.ownershipType ?? null,
43
47
  });
44
48
  } else {
45
49
  await config.onPremiumStatusChanged({ userId, isPremium: false });
@@ -1,11 +1,7 @@
1
1
  import type { SubscriptionStatusType } from "./PremiumStatusBadge";
2
+ import type { CreditInfo } from "../../../core/types";
2
3
 
3
- export interface CreditInfo {
4
- id: string;
5
- label: string;
6
- current: number;
7
- total: number;
8
- }
4
+ export type { CreditInfo };
9
5
 
10
6
  export interface PremiumDetailsTranslations {
11
7
  title: string;
@@ -1,4 +1,7 @@
1
1
  import type { SubscriptionStatusType } from "../../core/SubscriptionConstants";
2
+ import type { CreditInfo } from "../../core/types";
3
+
4
+ export type { CreditInfo };
2
5
 
3
6
  export interface SubscriptionDisplayFlags {
4
7
  showHeader: boolean;
@@ -40,13 +43,6 @@ export interface UpgradePromptConfig {
40
43
  onUpgrade?: () => void;
41
44
  }
42
45
 
43
- export interface CreditInfo {
44
- id: string;
45
- label: string;
46
- current: number;
47
- total: number;
48
- }
49
-
50
46
  export interface SubscriptionDetailConfig {
51
47
  display: SubscriptionDisplayFlags;
52
48
  statusType: SubscriptionStatusType;
@@ -1,17 +1,6 @@
1
- export interface SubscriptionStatusResult {
2
- isPremium: boolean;
3
- expirationDate: Date | null;
4
- willRenew: boolean;
5
- productIdentifier: string | null;
6
- originalPurchaseDate: Date | null;
7
- latestPurchaseDate: Date | null;
8
- billingIssuesDetected: boolean;
9
- isSandbox: boolean;
10
- periodType: string | null;
11
- packageType: string | null;
12
- store: string | null;
13
- gracePeriodExpiresDate: Date | null;
14
- unsubscribeDetectedAt: Date | null;
1
+ import type { PremiumStatus } from "../core/types";
2
+
3
+ export interface SubscriptionStatusResult extends PremiumStatus {
15
4
  isLoading: boolean;
16
5
  error: Error | null;
17
6
  refetch: () => void;
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Domain Layer - Constants & Types
2
2
  export * from "./domains/subscription/core/SubscriptionConstants";
3
+ export type { SubscriptionMetadata, PremiumStatus, CreditInfo } from "./domains/subscription/core/types";
3
4
  export {
4
5
  createDefaultSubscriptionStatus,
5
6
  isSubscriptionValid,
@@ -57,7 +58,6 @@ export type {
57
58
  SubscriptionDetailTranslations,
58
59
  SubscriptionDisplayFlags,
59
60
  UpgradePromptConfig,
60
- CreditInfo,
61
61
  } from "./domains/subscription/presentation/screens/SubscriptionDetailScreen.types";
62
62
  export * from "./domains/paywall/components/PaywallContainer";
63
63