@umituz/react-native-subscription 2.37.85 → 2.37.87

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.37.85",
3
+ "version": "2.37.87",
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",
@@ -7,7 +7,7 @@ import { deductCreditsOperation } from "../application/DeductCreditsCommand";
7
7
  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
- import { fetchCredits, checkHasCredits } from "./operations/CreditsFetcher";
10
+ import { fetchCredits, checkHasCredits, documentExists } from "./operations/CreditsFetcher";
11
11
  import { syncExpiredStatus, syncPremiumMetadata, createRecoveryCreditsDocument, type PremiumMetadata } from "./operations/CreditsWriter";
12
12
  import { initializeCreditsWithRetry } from "./operations/CreditsInitializer";
13
13
  import { calculateCreditLimit } from "../application/CreditLimitCalculator";
@@ -71,6 +71,11 @@ export class CreditsRepository extends BaseRepository {
71
71
  return checkHasCredits(this.getRef(db, userId), cost);
72
72
  }
73
73
 
74
+ async creditsDocumentExists(userId: string): Promise<boolean> {
75
+ const db = requireFirestore();
76
+ return documentExists(this.getRef(db, userId));
77
+ }
78
+
74
79
  async syncExpiredStatus(userId: string): Promise<void> {
75
80
  const db = requireFirestore();
76
81
  await syncExpiredStatus(this.getRef(db, userId));
@@ -4,6 +4,11 @@ import type { CreditsResult } from "../../core/Credits";
4
4
  import type { UserCreditsDocumentRead } from "../../core/UserCreditsDocument";
5
5
  import { mapCreditsDocumentToEntity } from "../../core/CreditsMapper";
6
6
 
7
+ export async function documentExists(ref: DocumentReference): Promise<boolean> {
8
+ const snap = await getDoc(ref);
9
+ return snap.exists();
10
+ }
11
+
7
12
  export async function fetchCredits(ref: DocumentReference): Promise<CreditsResult> {
8
13
  const snap = await getDoc(ref);
9
14
 
@@ -10,7 +10,6 @@ import { getAppVersion, validatePlatform } from "../../../../utils/appUtils";
10
10
  export async function syncExpiredStatus(ref: DocumentReference): Promise<void> {
11
11
  const doc = await getDoc(ref);
12
12
  if (!doc.exists()) {
13
- console.warn("[CreditsWriter] syncExpiredStatus: credits document does not exist, skipping.", ref.path);
14
13
  return;
15
14
  }
16
15
 
@@ -40,7 +39,6 @@ export async function syncPremiumMetadata(
40
39
  ): Promise<void> {
41
40
  const doc = await getDoc(ref);
42
41
  if (!doc.exists()) {
43
- console.warn("[CreditsWriter] syncPremiumMetadata: credits document does not exist, skipping.", ref.path);
44
42
  return;
45
43
  }
46
44
 
@@ -137,9 +137,13 @@ export class SubscriptionSyncProcessor {
137
137
  }
138
138
 
139
139
  if (!isPremium && !productId) {
140
- // Cancellation: RevenueCat removed entitlement, no productId available.
141
- // Must still update Firestore to reflect expired/canceled status.
142
- await handleExpiredSubscription(creditsUserId);
140
+ // No entitlement and no productId — could be:
141
+ // 1. Free user who never purchased (no credits doc) → skip
142
+ // 2. Previously premium user whose entitlement was removed → expire
143
+ const hasDoc = await getCreditsRepository().creditsDocumentExists(creditsUserId);
144
+ if (hasDoc) {
145
+ await handleExpiredSubscription(creditsUserId);
146
+ }
143
147
  return;
144
148
  }
145
149
 
@@ -21,6 +21,14 @@ export async function handlePurchase(
21
21
  const consumableIds = deps.config.consumableProductIdentifiers || [];
22
22
  const isConsumable = isConsumableProduct(pkg, consumableIds);
23
23
 
24
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
25
+ console.log("[PurchaseHandler] handlePurchase:", {
26
+ productId: pkg.product.identifier,
27
+ userId,
28
+ isConsumable,
29
+ });
30
+ }
31
+
24
32
  try {
25
33
  const result = await executePurchase(deps.config, userId, pkg, isConsumable);
26
34
  return result;
@@ -86,13 +86,44 @@ async function executeSubscriptionPurchase(
86
86
  };
87
87
  }
88
88
 
89
+ /** Default timeout for purchase operations (2 minutes) */
90
+ const PURCHASE_TIMEOUT_MS = 120_000;
91
+
92
+ function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
93
+ return new Promise<T>((resolve, reject) => {
94
+ const timer = setTimeout(() => {
95
+ reject(new Error(`[PurchaseExecutor] ${label} timed out after ${ms / 1000}s`));
96
+ }, ms);
97
+
98
+ promise
99
+ .then((result) => { clearTimeout(timer); resolve(result); })
100
+ .catch((error) => { clearTimeout(timer); reject(error); });
101
+ });
102
+ }
103
+
89
104
  export async function executePurchase(
90
105
  config: RevenueCatConfig,
91
106
  userId: string,
92
107
  pkg: PurchasesPackage,
93
108
  isConsumable: boolean
94
109
  ): Promise<PurchaseResult> {
95
- const { customerInfo } = await Purchases.purchasePackage(pkg);
110
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
111
+ console.log("[PurchaseExecutor] Starting Purchases.purchasePackage:", {
112
+ productId: pkg.product.identifier,
113
+ userId,
114
+ isConsumable,
115
+ });
116
+ }
117
+
118
+ const { customerInfo } = await withTimeout(
119
+ Purchases.purchasePackage(pkg),
120
+ PURCHASE_TIMEOUT_MS,
121
+ `Purchases.purchasePackage(${pkg.product.identifier})`
122
+ );
123
+
124
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
125
+ console.log("[PurchaseExecutor] Purchases.purchasePackage completed successfully");
126
+ }
96
127
 
97
128
  const productId = pkg.product.identifier;
98
129
  const packageType = pkg.packageType ?? null;