@umituz/react-native-subscription 2.27.28 → 2.27.29

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.27.28",
3
+ "version": "2.27.29",
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",
@@ -101,4 +101,6 @@ export interface DeductCreditsResult {
101
101
  export const DEFAULT_CREDITS_CONFIG: CreditsConfig = {
102
102
  collectionName: "user_credits",
103
103
  creditLimit: 100,
104
+ enableFreeCredits: false,
105
+ freeCredits: 0,
104
106
  };
package/src/index.ts CHANGED
@@ -87,6 +87,8 @@ export type {
87
87
  DeductCreditsResult,
88
88
  PurchaseSource,
89
89
  PurchaseType,
90
+ CreditAllocation,
91
+ PackageAllocationMap,
90
92
  } from "./domain/entities/Credits";
91
93
  export { DEFAULT_CREDITS_CONFIG } from "./domain/entities/Credits";
92
94
  export { InsufficientCreditsError } from "./domain/errors/InsufficientCreditsError";
@@ -29,6 +29,7 @@ import { getCreditAllocation } from "../../utils/creditMapper";
29
29
 
30
30
  interface InitializationResult {
31
31
  credits: number;
32
+ alreadyProcessed?: boolean;
32
33
  }
33
34
 
34
35
  export interface InitializeCreditsMetadata {
@@ -58,7 +59,7 @@ export async function initializeCreditsTransaction(
58
59
  let processedPurchases: string[] = existingData?.processedPurchases || [];
59
60
 
60
61
  if (existingData && purchaseId && processedPurchases.includes(purchaseId)) {
61
- return { credits: existingData.credits, alreadyProcessed: true } as InitializationResult & { alreadyProcessed: boolean };
62
+ return { credits: existingData.credits, alreadyProcessed: true };
62
63
  }
63
64
 
64
65
  if (existingData?.purchasedAt) {
@@ -16,23 +16,40 @@ export interface PurchaseAuthProvider {
16
16
  }
17
17
 
18
18
  let globalAuthProvider: PurchaseAuthProvider | null = null;
19
- let savedPackage: PurchasesPackage | null = null;
20
- let savedSource: PurchaseSource | null = null;
19
+
20
+ interface SavedPurchaseState {
21
+ pkg: PurchasesPackage;
22
+ source: PurchaseSource;
23
+ timestamp: number;
24
+ }
25
+
26
+ const SAVED_PURCHASE_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
27
+ let savedPurchaseState: SavedPurchaseState | null = null;
21
28
 
22
29
  export const configureAuthProvider = (provider: PurchaseAuthProvider): void => {
23
30
  globalAuthProvider = provider;
24
31
  };
25
32
 
33
+ const savePurchase = (pkg: PurchasesPackage, source: PurchaseSource): void => {
34
+ savedPurchaseState = { pkg, source, timestamp: Date.now() };
35
+ };
36
+
26
37
  export const getSavedPurchase = (): { pkg: PurchasesPackage; source: PurchaseSource } | null => {
27
- if (savedPackage && savedSource) {
28
- return { pkg: savedPackage, source: savedSource };
38
+ if (!savedPurchaseState) {
39
+ return null;
40
+ }
41
+
42
+ const isExpired = Date.now() - savedPurchaseState.timestamp > SAVED_PURCHASE_EXPIRY_MS;
43
+ if (isExpired) {
44
+ savedPurchaseState = null;
45
+ return null;
29
46
  }
30
- return null;
47
+
48
+ return { pkg: savedPurchaseState.pkg, source: savedPurchaseState.source };
31
49
  };
32
50
 
33
51
  export const clearSavedPurchase = (): void => {
34
- savedPackage = null;
35
- savedSource = null;
52
+ savedPurchaseState = null;
36
53
  };
37
54
 
38
55
  export interface UseAuthAwarePurchaseParams {
@@ -69,8 +86,7 @@ export const useAuthAwarePurchase = (
69
86
  const isAuth = globalAuthProvider.isAuthenticated();
70
87
 
71
88
  if (!isAuth) {
72
- savedPackage = pkg;
73
- savedSource = source || params?.source || "settings";
89
+ savePurchase(pkg, source || params?.source || "settings");
74
90
  globalAuthProvider.showAuthModal();
75
91
  return false;
76
92
  }
@@ -86,9 +86,9 @@ export const useCredits = (): UseCreditsResult => {
86
86
  return result.data || null;
87
87
  },
88
88
  enabled: queryEnabled,
89
- staleTime: 0,
90
- gcTime: 0,
91
- refetchOnMount: true,
89
+ staleTime: 30 * 1000, // 30 seconds - data considered fresh
90
+ gcTime: 5 * 60 * 1000, // 5 minutes - keep in cache after unmount
91
+ refetchOnMount: "always",
92
92
  refetchOnWindowFocus: true,
93
93
  refetchOnReconnect: true,
94
94
  });
@@ -63,6 +63,13 @@ async function initializeFreeCreditsForUser(
63
63
 
64
64
  const promise = (async () => {
65
65
  try {
66
+ if (!isCreditsRepositoryConfigured()) {
67
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
68
+ console.warn("[useFreeCreditsInit] Credits repository not configured");
69
+ }
70
+ return false;
71
+ }
72
+
66
73
  const repository = getCreditsRepository();
67
74
  const result = await repository.initializeFreeCredits(userId);
68
75
 
@@ -78,6 +85,11 @@ async function initializeFreeCreditsForUser(
78
85
  }
79
86
  return false;
80
87
  }
88
+ } catch (error) {
89
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
90
+ console.error("[useFreeCreditsInit] Unexpected error:", error);
91
+ }
92
+ return false;
81
93
  } finally {
82
94
  freeCreditsInitInProgress.delete(userId);
83
95
  initPromises.delete(userId);
@@ -142,7 +154,11 @@ export function useFreeCreditsInit(params: UseFreeCreditsInitParams): UseFreeCre
142
154
  if (needsInit) {
143
155
  // Double-check inside effect to handle race conditions
144
156
  if (!freeCreditsInitAttempted.has(userId)) {
145
- initializeFreeCreditsForUser(userId, stableOnComplete);
157
+ initializeFreeCreditsForUser(userId, stableOnComplete).catch((error) => {
158
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
159
+ console.error("[useFreeCreditsInit] Init failed:", error);
160
+ }
161
+ });
146
162
  }
147
163
  } else if (querySuccess && isAnonymous && !hasCredits && isFreeCreditsEnabled) {
148
164
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -37,9 +37,9 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
37
37
  return SubscriptionManager.checkPremiumStatus();
38
38
  },
39
39
  enabled: !!userId && SubscriptionManager.isInitializedForUser(userId),
40
- staleTime: 0,
41
- gcTime: 0,
42
- refetchOnMount: true,
40
+ staleTime: 30 * 1000, // 30 seconds
41
+ gcTime: 5 * 60 * 1000, // 5 minutes
42
+ refetchOnMount: "always",
43
43
  refetchOnWindowFocus: true,
44
44
  refetchOnReconnect: true,
45
45
  });
@@ -7,6 +7,8 @@ import type { PurchasesPackage, CustomerInfo } from "react-native-purchases";
7
7
  import type { IRevenueCatService } from "../../application/ports/IRevenueCatService";
8
8
  import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
9
9
 
10
+ declare const __DEV__: boolean;
11
+
10
12
  export interface PremiumStatus {
11
13
  isPremium: boolean;
12
14
  expirationDate: Date | null;
@@ -76,7 +76,13 @@ export class CustomerInfoListenerManager {
76
76
 
77
77
  this.renewalState = updateRenewalState(this.renewalState, renewalResult);
78
78
 
79
- syncPremiumStatus(config, this.currentUserId, customerInfo);
79
+ try {
80
+ await syncPremiumStatus(config, this.currentUserId, customerInfo);
81
+ } catch (error) {
82
+ if (__DEV__) {
83
+ console.error("[CustomerInfoListener] syncPremiumStatus failed:", error);
84
+ }
85
+ }
80
86
  };
81
87
 
82
88
  Purchases.addCustomerInfoUpdateListener(this.listener);
@@ -7,6 +7,8 @@ import type { CustomerInfo } from "react-native-purchases";
7
7
  import type { RevenueCatConfig } from "../../domain/value-objects/RevenueCatConfig";
8
8
  import { getPremiumEntitlement } from "../../domain/types/RevenueCatTypes";
9
9
 
10
+ declare const __DEV__: boolean;
11
+
10
12
  export async function syncPremiumStatus(
11
13
  config: RevenueCatConfig,
12
14
  userId: string,
@@ -34,13 +36,13 @@ export async function syncPremiumStatus(
34
36
  } else {
35
37
  await config.onPremiumStatusChanged(userId, false, undefined, undefined, undefined, undefined);
36
38
  }
37
- } catch {
38
- // Silent error handling
39
+ } catch (error) {
40
+ if (__DEV__) {
41
+ console.error('[PremiumStatusSyncer] syncPremiumStatus failed:', error);
42
+ }
39
43
  }
40
44
  }
41
45
 
42
- declare const __DEV__: boolean;
43
-
44
46
  export async function notifyPurchaseCompleted(
45
47
  config: RevenueCatConfig,
46
48
  userId: string,
@@ -88,7 +90,9 @@ export async function notifyRestoreCompleted(
88
90
 
89
91
  try {
90
92
  await config.onRestoreCompleted(userId, isPremium, customerInfo);
91
- } catch {
92
- // Silent error handling
93
+ } catch (error) {
94
+ if (__DEV__) {
95
+ console.error('[PremiumStatusSyncer] notifyRestoreCompleted failed:', error);
96
+ }
93
97
  }
94
98
  }