@umituz/react-native-subscription 2.35.5 → 2.35.7

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 (22) hide show
  1. package/package.json +1 -1
  2. package/src/domains/revenuecat/core/types/RevenueCatConfig.ts +3 -1
  3. package/src/domains/revenuecat/core/types/RevenueCatData.ts +3 -2
  4. package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +8 -1
  5. package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +3 -1
  6. package/src/domains/subscription/application/SubscriptionSyncService.ts +3 -2
  7. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +2 -0
  8. package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +1 -1
  9. package/src/domains/subscription/application/statusChangeHandlers.ts +1 -0
  10. package/src/domains/subscription/application/syncConstants.ts +1 -0
  11. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +3 -0
  12. package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +1 -4
  13. package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +15 -15
  14. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +5 -3
  15. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -0
  16. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +1 -0
  17. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +2 -0
  18. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.types.ts +1 -0
  19. package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +8 -3
  20. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -0
  21. package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +1 -0
  22. package/src/domains/subscription/utils/packageTypeFormatter.ts +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.35.5",
3
+ "version": "2.35.7",
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,4 +1,5 @@
1
1
  import type { CustomerInfo } from "react-native-purchases";
2
+ import type { PackageType } from "./RevenueCatTypes";
2
3
 
3
4
  /**
4
5
  * RevenueCat Configuration
@@ -20,7 +21,8 @@ export interface RevenueCatConfig {
20
21
  userId: string,
21
22
  productId: string,
22
23
  customerInfo: CustomerInfo,
23
- source?: string // Purchase source tracking (app-specific)
24
+ source?: string, // Purchase source tracking (app-specific)
25
+ packageType?: PackageType | null // From PurchasesPackage.packageType - subscription duration (WEEKLY, MONTHLY, ANNUAL, etc.)
24
26
  ) => Promise<void> | void;
25
27
  onRestoreCompleted?: (
26
28
  userId: string,
@@ -1,4 +1,4 @@
1
- import type { Store, OwnershipType } from "./RevenueCatTypes";
1
+ import type { Store, OwnershipType, PackageType } from "./RevenueCatTypes";
2
2
 
3
3
  /**
4
4
  * RevenueCat subscription data (Single Source of Truth)
@@ -10,7 +10,8 @@ export interface RevenueCatData {
10
10
  willRenew: boolean | null;
11
11
  originalTransactionId: string | null;
12
12
  isPremium: boolean;
13
- periodType: string | null; // From RevenueCat SDK (NORMAL, INTRO, TRIAL)
13
+ periodType: string | null; // From RevenueCat SDK (NORMAL, INTRO, TRIAL) - pricing type
14
+ packageType: PackageType | null; // From PurchasesPackage.packageType - subscription duration (WEEKLY, MONTHLY, ANNUAL, etc.)
14
15
  unsubscribeDetectedAt: string | null;
15
16
  billingIssueDetectedAt: string | null;
16
17
  store: Store | null; // From PurchasesEntitlementInfo['store']
@@ -3,7 +3,7 @@
3
3
  * Proper typing for RevenueCat entitlements and errors
4
4
  */
5
5
 
6
- import type { CustomerInfo, PurchasesEntitlementInfo } from "react-native-purchases";
6
+ import type { CustomerInfo, PurchasesEntitlementInfo, PurchasesPackage } from "react-native-purchases";
7
7
 
8
8
  /**
9
9
  * Default entitlement identifier
@@ -23,6 +23,13 @@ export type Store = PurchasesEntitlementInfo['store'];
23
23
  */
24
24
  export type OwnershipType = PurchasesEntitlementInfo['ownershipType'];
25
25
 
26
+ /**
27
+ * PackageType - Directly from RevenueCat SDK
28
+ * Represents subscription duration (WEEKLY, MONTHLY, ANNUAL, LIFETIME, etc.)
29
+ * Automatically stays in sync with RevenueCat SDK updates
30
+ */
31
+ export type PackageType = PurchasesPackage['packageType'];
32
+
26
33
  /**
27
34
  * RevenueCat Entitlement Info
28
35
  * Represents active entitlement data from CustomerInfo
@@ -7,6 +7,7 @@ import { emitCreditsUpdated } from "./syncEventEmitter";
7
7
  import { generatePurchaseId, generateRenewalId } from "./syncIdGenerators";
8
8
  import { handleExpiredSubscription, handleFreeUserInitialization, handlePremiumStatusSync } from "./statusChangeHandlers";
9
9
  import { NO_SUBSCRIPTION_PRODUCT_ID } from "./syncConstants";
10
+ import type { PackageType } from "../../revenuecat/core/types";
10
11
 
11
12
  export class SubscriptionSyncProcessor {
12
13
  constructor(
@@ -21,8 +22,9 @@ export class SubscriptionSyncProcessor {
21
22
  return revenueCatUserId;
22
23
  }
23
24
 
24
- async processPurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) {
25
+ async processPurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource, packageType?: PackageType | null) {
25
26
  const revenueCatData = extractRevenueCatData(customerInfo, this.entitlementId);
27
+ revenueCatData.packageType = packageType ?? null;
26
28
  const purchaseId = generatePurchaseId(revenueCatData.originalTransactionId, productId);
27
29
 
28
30
  const creditsUserId = await this.getCreditsUserId(userId);
@@ -2,6 +2,7 @@ import type { CustomerInfo } from "react-native-purchases";
2
2
  import { type PeriodType, type PurchaseSource } from "../core/SubscriptionConstants";
3
3
  import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
4
4
  import { SubscriptionSyncProcessor } from "./SubscriptionSyncProcessor";
5
+ import type { PackageType } from "../../revenuecat/core/types";
5
6
 
6
7
  export class SubscriptionSyncService {
7
8
  private processor: SubscriptionSyncProcessor;
@@ -13,9 +14,9 @@ export class SubscriptionSyncService {
13
14
  this.processor = new SubscriptionSyncProcessor(entitlementId, getAnonymousUserId);
14
15
  }
15
16
 
16
- async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) {
17
+ async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource, packageType?: PackageType | null) {
17
18
  try {
18
- await this.processor.processPurchase(userId, productId, customerInfo, source);
19
+ await this.processor.processPurchase(userId, productId, customerInfo, source, packageType);
19
20
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.PURCHASE_COMPLETED, { userId, productId });
20
21
  } catch (error) {
21
22
  console.error('[SubscriptionSyncService] Purchase processing failed', {
@@ -28,6 +28,7 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
28
28
  willRenew: null,
29
29
  originalTransactionId: null,
30
30
  periodType: null,
31
+ packageType: null,
31
32
  isPremium: false,
32
33
  unsubscribeDetectedAt: null,
33
34
  billingIssueDetectedAt: null,
@@ -41,6 +42,7 @@ export const extractRevenueCatData = (customerInfo: CustomerInfo, entitlementId:
41
42
  willRenew: entitlement.willRenew ?? null,
42
43
  originalTransactionId: entitlement.originalPurchaseDate ?? null,
43
44
  periodType: validatePeriodType(entitlement.periodType),
45
+ packageType: null,
44
46
  isPremium,
45
47
  unsubscribeDetectedAt: entitlement.unsubscribeDetectedAt ?? null,
46
48
  billingIssueDetectedAt: entitlement.billingIssueDetectedAt ?? null,
@@ -23,7 +23,7 @@ export function configureServices(config: SubscriptionInitConfig, apiKey: string
23
23
  apiKey,
24
24
  entitlementIdentifier: entitlementId,
25
25
  consumableProductIdentifiers: [creditPackages.identifierPattern],
26
- onPurchaseCompleted: (u: string, p: string, c: any, s: any) => syncService.handlePurchase(u, p, c, s),
26
+ onPurchaseCompleted: (u: string, p: string, c: any, s: any, pkgType: any) => syncService.handlePurchase(u, p, c, s, pkgType),
27
27
  onRenewalDetected: (u: string, p: string, expires: string, c: any) => syncService.handleRenewal(u, p, expires, c),
28
28
  onPremiumStatusChanged: (u: string, isP: boolean, pId: any, exp: any, willR: any, pt: any) => syncService.handlePremiumStatusChanged(u, isP, pId, exp, willR, pt),
29
29
  onCreditsUpdated,
@@ -50,6 +50,7 @@ export const handlePremiumStatusSync = async (
50
50
  willRenew,
51
51
  isPremium,
52
52
  periodType,
53
+ packageType: null,
53
54
  originalTransactionId: null,
54
55
  unsubscribeDetectedAt: null,
55
56
  billingIssueDetectedAt: null,
@@ -7,6 +7,7 @@ export const DEFAULT_FREE_USER_DATA: RevenueCatData = {
7
7
  expirationDate: null,
8
8
  willRenew: false,
9
9
  periodType: null,
10
+ packageType: null,
10
11
  originalTransactionId: null,
11
12
  unsubscribeDetectedAt: null,
12
13
  billingIssueDetectedAt: null,
@@ -12,6 +12,7 @@ export interface PremiumStatus {
12
12
  billingIssuesDetected: boolean;
13
13
  isSandbox: boolean;
14
14
  periodType: string | null;
15
+ packageType: string | null;
15
16
  store: string | null;
16
17
  gracePeriodExpiresDate: Date | null;
17
18
  unsubscribeDetectedAt: Date | null;
@@ -32,6 +33,7 @@ export class PurchaseStatusResolver {
32
33
  billingIssuesDetected: entitlement.billingIssueDetectedAt !== null && entitlement.billingIssueDetectedAt !== undefined,
33
34
  isSandbox: entitlement.isSandbox ?? false,
34
35
  periodType: entitlement.periodType ?? null,
36
+ packageType: null,
35
37
  store: null,
36
38
  gracePeriodExpiresDate: null,
37
39
  unsubscribeDetectedAt: toDate(entitlement.unsubscribeDetectedAt) ?? null,
@@ -48,6 +50,7 @@ export class PurchaseStatusResolver {
48
50
  billingIssuesDetected: false,
49
51
  isSandbox: false,
50
52
  periodType: null,
53
+ packageType: null,
51
54
  store: null,
52
55
  gracePeriodExpiresDate: null,
53
56
  unsubscribeDetectedAt: null,
@@ -12,7 +12,7 @@ import {
12
12
  isNetworkError,
13
13
  isInvalidCredentialsError,
14
14
  } from "../../../revenuecat/core/types";
15
- import { syncPremiumStatus, notifyRestoreCompleted } from "../utils/PremiumStatusSyncer";
15
+ import { notifyRestoreCompleted } from "../utils/PremiumStatusSyncer";
16
16
 
17
17
  export interface RestoreHandlerDeps {
18
18
  config: RevenueCatConfig;
@@ -28,9 +28,6 @@ export async function handleRestore(deps: RestoreHandlerDeps, userId: string): P
28
28
  const isPremium = !!entitlement;
29
29
  const productId = entitlement?.productIdentifier ?? null;
30
30
 
31
- if (isPremium) {
32
- await syncPremiumStatus(deps.config, userId, customerInfo);
33
- }
34
31
  await notifyRestoreCompleted(deps.config, userId, isPremium, customerInfo);
35
32
 
36
33
  return { success: true, isPremium, productId, customerInfo };
@@ -1,14 +1,15 @@
1
1
  import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
2
2
  import type { PurchaseResult } from "../../../../../shared/application/ports/IRevenueCatService";
3
- import type { RevenueCatConfig } from "../../../../revenuecat/core/types";
4
- import { syncPremiumStatus, notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
3
+ import type { RevenueCatConfig, PackageType } from "../../../../revenuecat/core/types";
4
+ import { notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
5
5
  import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
6
6
 
7
7
  async function executeConsumablePurchase(
8
8
  config: RevenueCatConfig,
9
9
  userId: string,
10
10
  productId: string,
11
- customerInfo: CustomerInfo
11
+ customerInfo: CustomerInfo,
12
+ packageType: PackageType | null
12
13
  ): Promise<PurchaseResult> {
13
14
  const savedPurchase = getSavedPurchase();
14
15
  const source = savedPurchase?.source;
@@ -16,7 +17,7 @@ async function executeConsumablePurchase(
16
17
  clearSavedPurchase();
17
18
  }
18
19
 
19
- await notifyPurchaseCompleted(config, userId, productId, customerInfo, source);
20
+ await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
20
21
 
21
22
  return {
22
23
  success: true,
@@ -34,7 +35,8 @@ async function executeSubscriptionPurchase(
34
35
  userId: string,
35
36
  productId: string,
36
37
  customerInfo: CustomerInfo,
37
- entitlementIdentifier: string
38
+ entitlementIdentifier: string,
39
+ packageType: PackageType | null
38
40
  ): Promise<PurchaseResult> {
39
41
  const isPremium = !!customerInfo.entitlements.active[entitlementIdentifier];
40
42
  const savedPurchase = getSavedPurchase();
@@ -51,16 +53,11 @@ async function executeSubscriptionPurchase(
51
53
  entitlementIdentifier,
52
54
  activeEntitlements: Object.keys(customerInfo.entitlements.active),
53
55
  source,
56
+ packageType,
54
57
  });
55
58
  }
56
59
 
57
- await syncPremiumStatus(config, userId, customerInfo);
58
-
59
- if (typeof __DEV__ !== "undefined" && __DEV__) {
60
- console.log("[PurchaseExecutor] syncPremiumStatus completed");
61
- }
62
-
63
- await notifyPurchaseCompleted(config, userId, productId, customerInfo, source);
60
+ await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
64
61
 
65
62
  if (typeof __DEV__ !== "undefined" && __DEV__) {
66
63
  console.log("[PurchaseExecutor] Purchase flow completed successfully");
@@ -83,7 +80,8 @@ export async function executePurchase(
83
80
  console.log('🔵 [PurchaseExecutor] executePurchase called', {
84
81
  productId: pkg.product.identifier,
85
82
  userId,
86
- isConsumable
83
+ isConsumable,
84
+ packageType: pkg.packageType
87
85
  });
88
86
 
89
87
  console.log('🚀 [PurchaseExecutor] Calling Purchases.purchasePackage (RevenueCat SDK)');
@@ -91,10 +89,11 @@ export async function executePurchase(
91
89
  console.log('✅ [PurchaseExecutor] Purchases.purchasePackage completed');
92
90
 
93
91
  const productId = pkg.product.identifier;
92
+ const packageType = pkg.packageType ?? null;
94
93
 
95
94
  if (isConsumable) {
96
95
  console.log('💰 [PurchaseExecutor] Processing as consumable purchase');
97
- return executeConsumablePurchase(config, userId, productId, customerInfo);
96
+ return executeConsumablePurchase(config, userId, productId, customerInfo, packageType);
98
97
  }
99
98
 
100
99
  console.log('📅 [PurchaseExecutor] Processing as subscription purchase');
@@ -103,6 +102,7 @@ export async function executePurchase(
103
102
  userId,
104
103
  productId,
105
104
  customerInfo,
106
- config.entitlementIdentifier
105
+ config.entitlementIdentifier,
106
+ packageType
107
107
  );
108
108
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { CustomerInfo } from "react-native-purchases";
7
- import type { RevenueCatConfig } from "../../../revenuecat/core/types";
7
+ import type { RevenueCatConfig, PackageType } from "../../../revenuecat/core/types";
8
8
  import type { PurchaseSource } from "../../../subscription/core/SubscriptionConstants";
9
9
  import { getPremiumEntitlement } from "../../../revenuecat/core/types";
10
10
 
@@ -88,19 +88,21 @@ export async function notifyPurchaseCompleted(
88
88
  userId: string,
89
89
  productId: string,
90
90
  customerInfo: CustomerInfo,
91
- source?: PurchaseSource
91
+ source?: PurchaseSource,
92
+ packageType?: PackageType | null
92
93
  ): Promise<void> {
93
94
  if (!config.onPurchaseCompleted) {
94
95
  return;
95
96
  }
96
97
 
97
98
  try {
98
- await config.onPurchaseCompleted(userId, productId, customerInfo, source);
99
+ await config.onPurchaseCompleted(userId, productId, customerInfo, source, packageType);
99
100
  } catch (error) {
100
101
  console.error('[PremiumStatusSyncer] Purchase completion callback failed', {
101
102
  userId,
102
103
  productId,
103
104
  source,
105
+ packageType,
104
106
  error
105
107
  });
106
108
  // Silently fail callback notifications to prevent crashing the main flow
@@ -55,6 +55,7 @@ export const SubscriptionDetailScreen: React.FC<SubscriptionDetailScreenProps> =
55
55
  productIdentifier={config.productIdentifier}
56
56
  productName={config.productName}
57
57
  periodType={config.periodType}
58
+ packageType={config.packageType}
58
59
  store={config.store}
59
60
  originalPurchaseDate={config.originalPurchaseDate}
60
61
  latestPurchaseDate={config.latestPurchaseDate}
@@ -62,6 +62,7 @@ export interface SubscriptionDetailConfig {
62
62
  productIdentifier?: string | null;
63
63
  productName?: string | null;
64
64
  periodType?: string | null;
65
+ packageType?: string | null;
65
66
  store?: string | null;
66
67
  originalPurchaseDate?: string | null;
67
68
  latestPurchaseDate?: string | null;
@@ -20,6 +20,7 @@ export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
20
20
  translations,
21
21
  willRenew,
22
22
  periodType,
23
+ packageType,
23
24
  store,
24
25
  originalPurchaseDate,
25
26
  latestPurchaseDate,
@@ -59,6 +60,7 @@ export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
59
60
  styles={styles}
60
61
  willRenew={willRenew}
61
62
  periodType={periodType}
63
+ packageType={packageType}
62
64
  store={store}
63
65
  originalPurchaseDate={originalPurchaseDate}
64
66
  latestPurchaseDate={latestPurchaseDate}
@@ -31,6 +31,7 @@ export interface SubscriptionHeaderProps {
31
31
  productIdentifier?: string | null;
32
32
  productName?: string | null;
33
33
  periodType?: string | null;
34
+ packageType?: string | null;
34
35
  store?: string | null;
35
36
  originalPurchaseDate?: string | null;
36
37
  latestPurchaseDate?: string | null;
@@ -2,6 +2,9 @@ import React from "react";
2
2
  import { View } from "react-native";
3
3
  import { DetailRow } from "../../components/details/DetailRow";
4
4
  import type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
5
+ import { formatPackageTypeForDisplay } from "../../../../subscription/utils/packageTypeFormatter";
6
+
7
+ declare const __DEV__: boolean;
5
8
 
6
9
  interface SubscriptionHeaderContentProps {
7
10
  isLifetime: boolean;
@@ -13,6 +16,7 @@ interface SubscriptionHeaderContentProps {
13
16
  styles: any;
14
17
  willRenew?: boolean | null;
15
18
  periodType?: string | null;
19
+ packageType?: string | null;
16
20
  store?: string | null;
17
21
  originalPurchaseDate?: string | null;
18
22
  latestPurchaseDate?: string | null;
@@ -30,6 +34,7 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
30
34
  styles,
31
35
  willRenew,
32
36
  periodType,
37
+ packageType,
33
38
  store,
34
39
  originalPurchaseDate,
35
40
  latestPurchaseDate,
@@ -76,10 +81,10 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
76
81
  valueStyle={styles.value}
77
82
  />
78
83
  )}
79
- {periodType && translations.periodTypeLabel && (
84
+ {packageType && translations.periodTypeLabel && (
80
85
  <DetailRow
81
86
  label={translations.periodTypeLabel}
82
- value={periodType === "NORMAL" ? "Standard" : periodType}
87
+ value={formatPackageTypeForDisplay(packageType)}
83
88
  style={styles.row}
84
89
  labelStyle={styles.label}
85
90
  valueStyle={styles.value}
@@ -122,7 +127,7 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
122
127
  valueStyle={styles.value}
123
128
  />
124
129
  )}
125
- {isSandbox && translations.sandboxLabel && (
130
+ {typeof __DEV__ !== 'undefined' && __DEV__ && isSandbox && translations.sandboxLabel && (
126
131
  <DetailRow
127
132
  label={translations.sandboxLabel}
128
133
  value="Test Mode"
@@ -89,6 +89,7 @@ export const useSubscriptionStatus = (): SubscriptionStatusResult => {
89
89
  billingIssuesDetected: data?.billingIssuesDetected ?? false,
90
90
  isSandbox: data?.isSandbox ?? false,
91
91
  periodType: data?.periodType ?? null,
92
+ packageType: data?.packageType ?? null,
92
93
  store: data?.store ?? null,
93
94
  gracePeriodExpiresDate: data?.gracePeriodExpiresDate ?? null,
94
95
  unsubscribeDetectedAt: data?.unsubscribeDetectedAt ?? null,
@@ -8,6 +8,7 @@ export interface SubscriptionStatusResult {
8
8
  billingIssuesDetected: boolean;
9
9
  isSandbox: boolean;
10
10
  periodType: string | null;
11
+ packageType: string | null;
11
12
  store: string | null;
12
13
  gracePeriodExpiresDate: Date | null;
13
14
  unsubscribeDetectedAt: Date | null;
@@ -0,0 +1,10 @@
1
+ import type { PackageType } from "../../revenuecat/core/types";
2
+
3
+ export function formatPackageTypeForDisplay(packageType: PackageType | string | null | undefined): string {
4
+ if (!packageType) {
5
+ return "";
6
+ }
7
+
8
+ const formatted = packageType.toLowerCase().replace(/_/g, " ");
9
+ return formatted.charAt(0).toUpperCase() + formatted.slice(1);
10
+ }