@umituz/react-native-subscription 2.22.9 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.22.9",
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
- // Determine status from document or derive from isPremium/expirationDate
8
- const status = doc.status ?? CreditsMapper.deriveStatus(doc);
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: doc.isPremium ?? false,
13
+ // Core subscription (validated)
14
+ isPremium,
13
15
  status,
14
16
 
15
17
  // Dates
16
18
  purchasedAt: doc.purchasedAt?.toDate?.() ?? null,
17
- expirationDate: doc.expirationDate?.toDate?.() ?? null,
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
- /** Derive status from isPremium and expirationDate for backward compatibility */
39
- private static deriveStatus(doc: UserCreditsDocumentRead): SubscriptionStatus {
40
- if (!doc.isPremium && !doc.expirationDate) return "free";
41
- if (doc.isPremium) return "active";
42
- if (doc.expirationDate) {
43
- const expDate = doc.expirationDate.toDate?.();
44
- if (expDate && expDate < new Date()) return "expired";
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
- return "free";
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);
@@ -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
- if (__DEV__) console.log("[useCredits] Query success:", { hasData: !!result.data, credits: result.data?.credits });
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, // Refetch when component mounts
98
- refetchOnWindowFocus: true, // Refetch when app becomes active
99
- refetchOnReconnect: true, // Refetch when network reconnects
104
+ refetchOnMount: true,
105
+ refetchOnWindowFocus: true,
106
+ refetchOnReconnect: true,
100
107
  });
101
108
 
102
109
  const credits = data ?? null;