@umituz/react-native-subscription 2.16.0 → 2.16.2

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/README.md CHANGED
@@ -67,6 +67,51 @@ This package provides comprehensive subscription and credit management with:
67
67
  - **State Management**: `@tanstack/react-query` >= 5.0.0
68
68
  - **React Native**: `react-native` >= 0.74.0
69
69
 
70
+ ## RevenueCat Best Practices
71
+
72
+ This package follows RevenueCat's official best practices:
73
+
74
+ ### 1. Trust RevenueCat Data
75
+
76
+ - **Expiration dates**: Use RevenueCat's `expirationDate` directly without modification
77
+ - **Premium status**: Check `customerInfo.entitlements.active['premium']`
78
+ - **Server-side validation**: RevenueCat handles receipt validation server-side
79
+
80
+ ### 2. CustomerInfo Listener
81
+
82
+ ```typescript
83
+ // Real-time subscription updates via listener
84
+ Purchases.addCustomerInfoUpdateListener((info) => {
85
+ const isPremium = !!info.entitlements.active['premium'];
86
+ const expirationDate = info.entitlements.active['premium']?.expirationDate;
87
+ });
88
+ ```
89
+
90
+ ### 3. Entitlements-Based Access
91
+
92
+ - Use entitlements (not product IDs) to gate features
93
+ - Entitlements abstract away platform differences (iOS/Android)
94
+ - Single source of truth for premium access
95
+
96
+ ### 4. Testing Guidelines
97
+
98
+ - **Real devices only**: Simulators don't support in-app purchases
99
+ - **TestFlight uses Sandbox**: Short expiration times (5 min for monthly)
100
+ - **App Store Connect delays**: Changes can take hours to propagate
101
+
102
+ ### 5. Anonymous to Identified User Transfer
103
+
104
+ When user converts from anonymous to identified:
105
+ - `Purchases.logIn(userId)` handles user identity
106
+ - Configure "Transfer if no purchases" in RevenueCat dashboard
107
+ - Use `restorePurchases()` for explicit restore
108
+
109
+ ### Sources
110
+
111
+ - [RevenueCat React Native SDK](https://github.com/RevenueCat/react-native-purchases)
112
+ - [RevenueCat Documentation](https://www.revenuecat.com/docs/getting-started/installation/reactnative)
113
+ - [Best Practices Guide](https://www.revenuecat.com/blog/engineering/ad-free-subscriptions-in-react-native/)
114
+
70
115
  ## Restrictions
71
116
 
72
117
  ### REQUIRED
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.16.0",
3
+ "version": "2.16.2",
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",
@@ -16,7 +16,6 @@ import { useCreditsArray, getSubscriptionStatusType } from "./useSubscriptionSet
16
16
  import { getCreditsConfig } from "../../infrastructure/repositories/CreditsRepositoryProvider";
17
17
  import { detectPackageType } from "../../utils/packageTypeDetector";
18
18
  import { getCreditAllocation } from "../../utils/creditMapper";
19
- import { getExpirationDate } from "../../revenuecat/infrastructure/utils/ExpirationDateCalculator";
20
19
  import type {
21
20
  SubscriptionSettingsConfig,
22
21
  SubscriptionStatusType,
@@ -75,12 +74,8 @@ export const useSubscriptionSettingsConfig = (
75
74
  return allocation ?? creditLimit ?? config.creditLimit;
76
75
  }, [premiumEntitlement?.productIdentifier, creditLimit]);
77
76
 
78
- // Get expiration date from RevenueCat entitlement (source of truth)
79
- // Apply sandbox-to-production conversion for better testing UX
80
- const entitlementExpirationDate = useMemo(() => {
81
- if (!premiumEntitlement) return null;
82
- return getExpirationDate(premiumEntitlement);
83
- }, [premiumEntitlement]);
77
+ // Get expiration date directly from RevenueCat (source of truth)
78
+ const entitlementExpirationDate = premiumEntitlement?.expirationDate ?? null;
84
79
 
85
80
  // Prefer CustomerInfo expiration (real-time) over cached status
86
81
  const expiresAtIso = entitlementExpirationDate || (statusExpirationDate
@@ -6,7 +6,6 @@
6
6
  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
- import { getExpirationDate } from "../utils/ExpirationDateCalculator";
10
9
 
11
10
  export interface PremiumStatus {
12
11
  isPremium: boolean;
@@ -115,55 +114,17 @@ export class PackageHandler {
115
114
  }
116
115
 
117
116
  checkPremiumStatusFromInfo(customerInfo: CustomerInfo): PremiumStatus {
118
- // First, check active entitlements (standard case)
119
- const activeEntitlement = getPremiumEntitlement(
120
- customerInfo,
121
- this.entitlementId
122
- );
123
-
124
- if (activeEntitlement) {
125
- const adjustedExpiration = getExpirationDate(activeEntitlement);
117
+ const entitlement = getPremiumEntitlement(customerInfo, this.entitlementId);
118
+
119
+ if (entitlement) {
126
120
  return {
127
121
  isPremium: true,
128
- expirationDate: adjustedExpiration ? new Date(adjustedExpiration) : null,
122
+ expirationDate: entitlement.expirationDate
123
+ ? new Date(entitlement.expirationDate)
124
+ : null,
129
125
  };
130
126
  }
131
127
 
132
- // Edge case: Check all entitlements (including expired ones)
133
- // This handles the bug where RevenueCat hasn't updated the expiration date yet
134
- const allEntitlements = customerInfo.entitlements.all[this.entitlementId];
135
-
136
- if (allEntitlements) {
137
- const entitlementData = {
138
- identifier: allEntitlements.identifier,
139
- productIdentifier: allEntitlements.productIdentifier,
140
- isSandbox: allEntitlements.isSandbox,
141
- willRenew: allEntitlements.willRenew,
142
- periodType: allEntitlements.periodType,
143
- latestPurchaseDate: allEntitlements.latestPurchaseDate,
144
- originalPurchaseDate: allEntitlements.originalPurchaseDate,
145
- expirationDate: allEntitlements.expirationDate,
146
- unsubscribeDetectedAt: allEntitlements.unsubscribeDetectedAt,
147
- billingIssueDetectedAt: allEntitlements.billingIssueDetectedAt,
148
- };
149
-
150
- // Get adjusted expiration date
151
- const adjustedExpiration = getExpirationDate(entitlementData);
152
-
153
- if (adjustedExpiration) {
154
- const expirationDate = new Date(adjustedExpiration);
155
- const now = new Date();
156
-
157
- // If adjusted expiration is in the future, user is premium
158
- if (expirationDate > now) {
159
- return {
160
- isPremium: true,
161
- expirationDate,
162
- };
163
- }
164
- }
165
- }
166
-
167
128
  return {
168
129
  isPremium: false,
169
130
  expirationDate: null,
@@ -6,7 +6,6 @@
6
6
  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
- import { getExpirationDate } from "./ExpirationDateCalculator";
10
9
 
11
10
  export async function syncPremiumStatus(
12
11
  config: RevenueCatConfig,
@@ -17,21 +16,18 @@ export async function syncPremiumStatus(
17
16
  return;
18
17
  }
19
18
 
20
- const entitlementIdentifier = config.entitlementIdentifier;
21
19
  const premiumEntitlement = getPremiumEntitlement(
22
20
  customerInfo,
23
- entitlementIdentifier
21
+ config.entitlementIdentifier
24
22
  );
25
23
 
26
24
  try {
27
25
  if (premiumEntitlement) {
28
- const productId = premiumEntitlement.productIdentifier;
29
- const expiresAt = getExpirationDate(premiumEntitlement);
30
26
  await config.onPremiumStatusChanged(
31
27
  userId,
32
28
  true,
33
- productId,
34
- expiresAt || undefined
29
+ premiumEntitlement.productIdentifier,
30
+ premiumEntitlement.expirationDate ?? undefined
35
31
  );
36
32
  } else {
37
33
  await config.onPremiumStatusChanged(userId, false);
@@ -1,19 +0,0 @@
1
- /**
2
- * Expiration Date Calculator
3
- * Returns RevenueCat expiration date directly
4
- *
5
- * Best Practice: Trust RevenueCat's expiration date
6
- * RevenueCat handles all subscription logic server-side
7
- */
8
-
9
- import type { RevenueCatEntitlement } from '../../domain/types/RevenueCatTypes';
10
-
11
- /**
12
- * Get expiration date from entitlement
13
- * Returns RevenueCat's expiration date directly without modification
14
- */
15
- export function getExpirationDate(
16
- entitlement: RevenueCatEntitlement | null
17
- ): string | null {
18
- return entitlement?.expirationDate ?? null;
19
- }