@umituz/react-native-subscription 2.35.4 → 2.35.6

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 +27 -9
  6. package/src/domains/subscription/application/SubscriptionSyncService.ts +8 -4
  7. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +2 -0
  8. package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +10 -10
  9. package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +3 -3
  10. package/src/domains/subscription/application/statusChangeHandlers.ts +1 -0
  11. package/src/domains/subscription/application/syncConstants.ts +1 -0
  12. package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +3 -0
  13. package/src/domains/subscription/infrastructure/services/RestoreHandler.ts +1 -4
  14. package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +15 -15
  15. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +5 -3
  16. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -0
  17. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +1 -0
  18. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.tsx +2 -0
  19. package/src/domains/subscription/presentation/screens/components/SubscriptionHeader.types.ts +1 -0
  20. package/src/domains/subscription/presentation/screens/components/SubscriptionHeaderContent.tsx +20 -3
  21. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -0
  22. package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.35.4",
3
+ "version": "2.35.6",
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,16 +7,30 @@ 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
- constructor(private entitlementId: string) {}
13
+ constructor(
14
+ private entitlementId: string,
15
+ private getAnonymousUserId: () => Promise<string>
16
+ ) {}
13
17
 
14
- async processPurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) {
18
+ private async getCreditsUserId(revenueCatUserId: string): Promise<string> {
19
+ if (!revenueCatUserId || revenueCatUserId.length === 0) {
20
+ return this.getAnonymousUserId();
21
+ }
22
+ return revenueCatUserId;
23
+ }
24
+
25
+ async processPurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource, packageType?: PackageType | null) {
15
26
  const revenueCatData = extractRevenueCatData(customerInfo, this.entitlementId);
27
+ revenueCatData.packageType = packageType ?? null;
16
28
  const purchaseId = generatePurchaseId(revenueCatData.originalTransactionId, productId);
17
29
 
30
+ const creditsUserId = await this.getCreditsUserId(userId);
31
+
18
32
  await getCreditsRepository().initializeCredits(
19
- userId,
33
+ creditsUserId,
20
34
  purchaseId,
21
35
  productId,
22
36
  source ?? PURCHASE_SOURCE.SETTINGS,
@@ -24,7 +38,7 @@ export class SubscriptionSyncProcessor {
24
38
  PURCHASE_TYPE.INITIAL
25
39
  );
26
40
 
27
- emitCreditsUpdated(userId);
41
+ emitCreditsUpdated(creditsUserId);
28
42
  }
29
43
 
30
44
  async processRenewal(userId: string, productId: string, newExpirationDate: string, customerInfo: CustomerInfo) {
@@ -32,8 +46,10 @@ export class SubscriptionSyncProcessor {
32
46
  revenueCatData.expirationDate = newExpirationDate || revenueCatData.expirationDate;
33
47
  const purchaseId = generateRenewalId(revenueCatData.originalTransactionId, productId, newExpirationDate);
34
48
 
49
+ const creditsUserId = await this.getCreditsUserId(userId);
50
+
35
51
  await getCreditsRepository().initializeCredits(
36
- userId,
52
+ creditsUserId,
37
53
  purchaseId,
38
54
  productId,
39
55
  PURCHASE_SOURCE.RENEWAL,
@@ -41,7 +57,7 @@ export class SubscriptionSyncProcessor {
41
57
  PURCHASE_TYPE.RENEWAL
42
58
  );
43
59
 
44
- emitCreditsUpdated(userId);
60
+ emitCreditsUpdated(creditsUserId);
45
61
  }
46
62
 
47
63
  async processStatusChange(
@@ -52,21 +68,23 @@ export class SubscriptionSyncProcessor {
52
68
  willRenew?: boolean,
53
69
  periodType?: PeriodType
54
70
  ) {
71
+ const creditsUserId = await this.getCreditsUserId(userId);
72
+
55
73
  // Expired subscription case
56
74
  if (!isPremium && productId) {
57
- await handleExpiredSubscription(userId);
75
+ await handleExpiredSubscription(creditsUserId);
58
76
  return;
59
77
  }
60
78
 
61
79
  // Free user case
62
80
  if (!isPremium && !productId) {
63
- await handleFreeUserInitialization(userId);
81
+ await handleFreeUserInitialization(creditsUserId);
64
82
  return;
65
83
  }
66
84
 
67
85
  // Premium user case
68
86
  await handlePremiumStatusSync(
69
- userId,
87
+ creditsUserId,
70
88
  isPremium,
71
89
  productId ?? NO_SUBSCRIPTION_PRODUCT_ID,
72
90
  expiresAt ?? null,
@@ -2,17 +2,21 @@ 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;
8
9
 
9
- constructor(entitlementId: string) {
10
- this.processor = new SubscriptionSyncProcessor(entitlementId);
10
+ constructor(
11
+ entitlementId: string,
12
+ getAnonymousUserId: () => Promise<string>
13
+ ) {
14
+ this.processor = new SubscriptionSyncProcessor(entitlementId, getAnonymousUserId);
11
15
  }
12
16
 
13
- async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource) {
17
+ async handlePurchase(userId: string, productId: string, customerInfo: CustomerInfo, source?: PurchaseSource, packageType?: PackageType | null) {
14
18
  try {
15
- await this.processor.processPurchase(userId, productId, customerInfo, source);
19
+ await this.processor.processPurchase(userId, productId, customerInfo, source, packageType);
16
20
  subscriptionEventBus.emit(SUBSCRIPTION_EVENTS.PURCHASE_COMPLETED, { userId, productId });
17
21
  } catch (error) {
18
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,
@@ -5,11 +5,11 @@ import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
5
5
  declare const __DEV__: boolean;
6
6
 
7
7
  export async function startBackgroundInitialization(config: SubscriptionInitConfig): Promise<() => void> {
8
- const initializeInBackground = async (userId?: string): Promise<void> => {
8
+ const initializeInBackground = async (revenueCatUserId?: string): Promise<void> => {
9
9
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
10
- console.log('[BackgroundInitializer] initializeInBackground called with userId:', userId || '(undefined - anonymous)');
10
+ console.log('[BackgroundInitializer] initializeInBackground called with userId:', revenueCatUserId || '(undefined - anonymous)');
11
11
  }
12
- await SubscriptionManager.initialize(userId);
12
+ await SubscriptionManager.initialize(revenueCatUserId);
13
13
  };
14
14
 
15
15
  const auth = config.getFirebaseAuth();
@@ -21,22 +21,22 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
21
21
  console.log('[BackgroundInitializer] Starting background initialization');
22
22
  }
23
23
 
24
- const initialUserId = getCurrentUserId(() => auth);
24
+ const initialRevenueCatUserId = getCurrentUserId(() => auth);
25
25
 
26
26
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
27
- console.log('[BackgroundInitializer] Initial userId:', initialUserId || '(undefined - anonymous)');
27
+ console.log('[BackgroundInitializer] Initial RevenueCat userId:', initialRevenueCatUserId || '(undefined - anonymous)');
28
28
  }
29
29
 
30
- await initializeInBackground(initialUserId);
30
+ await initializeInBackground(initialRevenueCatUserId);
31
31
 
32
- const unsubscribe = setupAuthStateListener(() => auth, async (newUserId) => {
32
+ const unsubscribe = setupAuthStateListener(() => auth, async (newRevenueCatUserId) => {
33
33
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
34
- console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', newUserId || '(undefined - anonymous)');
34
+ console.log('[BackgroundInitializer] Auth state listener triggered, reinitializing with userId:', newRevenueCatUserId || '(undefined - anonymous)');
35
35
  }
36
36
  try {
37
- await initializeInBackground(newUserId);
37
+ await initializeInBackground(newRevenueCatUserId);
38
38
  } catch (error) {
39
- console.error('[BackgroundInitializer] Failed to reinitialize on auth change', { userId: newUserId, error });
39
+ console.error('[BackgroundInitializer] Failed to reinitialize on auth change', { userId: newRevenueCatUserId, error });
40
40
  }
41
41
  });
42
42
 
@@ -5,7 +5,7 @@ import { SubscriptionSyncService } from "../SubscriptionSyncService";
5
5
  import type { SubscriptionInitConfig } from "../SubscriptionInitializerTypes";
6
6
 
7
7
  export function configureServices(config: SubscriptionInitConfig, apiKey: string): SubscriptionSyncService {
8
- const { entitlementId, credits, creditPackages, getFirebaseAuth, showAuthModal, onCreditsUpdated } = config;
8
+ const { entitlementId, credits, creditPackages, getFirebaseAuth, showAuthModal, onCreditsUpdated, getAnonymousUserId } = config;
9
9
 
10
10
  if (!creditPackages) {
11
11
  throw new Error('[ServiceConfigurator] creditPackages configuration is required');
@@ -16,14 +16,14 @@ export function configureServices(config: SubscriptionInitConfig, apiKey: string
16
16
  creditPackageAmounts: creditPackages.amounts
17
17
  });
18
18
 
19
- const syncService = new SubscriptionSyncService(entitlementId);
19
+ const syncService = new SubscriptionSyncService(entitlementId, getAnonymousUserId);
20
20
 
21
21
  SubscriptionManager.configure({
22
22
  config: {
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;
@@ -3,6 +3,21 @@ import { View } from "react-native";
3
3
  import { DetailRow } from "../../components/details/DetailRow";
4
4
  import type { SubscriptionHeaderProps } from "./SubscriptionHeader.types";
5
5
 
6
+ declare const __DEV__: boolean;
7
+
8
+ function formatSubscriptionPeriod(packageType: string | null | undefined, periodType: string | null | undefined): string {
9
+ if (packageType) {
10
+ const formatted = packageType.toLowerCase().replace(/_/g, ' ');
11
+ return formatted.charAt(0).toUpperCase() + formatted.slice(1);
12
+ }
13
+
14
+ if (periodType === "NORMAL") {
15
+ return "Standard";
16
+ }
17
+
18
+ return periodType || "Unknown";
19
+ }
20
+
6
21
  interface SubscriptionHeaderContentProps {
7
22
  isLifetime: boolean;
8
23
  showExpirationDate: boolean;
@@ -13,6 +28,7 @@ interface SubscriptionHeaderContentProps {
13
28
  styles: any;
14
29
  willRenew?: boolean | null;
15
30
  periodType?: string | null;
31
+ packageType?: string | null;
16
32
  store?: string | null;
17
33
  originalPurchaseDate?: string | null;
18
34
  latestPurchaseDate?: string | null;
@@ -30,6 +46,7 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
30
46
  styles,
31
47
  willRenew,
32
48
  periodType,
49
+ packageType,
33
50
  store,
34
51
  originalPurchaseDate,
35
52
  latestPurchaseDate,
@@ -76,10 +93,10 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
76
93
  valueStyle={styles.value}
77
94
  />
78
95
  )}
79
- {periodType && translations.periodTypeLabel && (
96
+ {(packageType || periodType) && translations.periodTypeLabel && (
80
97
  <DetailRow
81
98
  label={translations.periodTypeLabel}
82
- value={periodType === "NORMAL" ? "Standard" : periodType}
99
+ value={formatSubscriptionPeriod(packageType, periodType)}
83
100
  style={styles.row}
84
101
  labelStyle={styles.label}
85
102
  valueStyle={styles.value}
@@ -122,7 +139,7 @@ export const SubscriptionHeaderContent: React.FC<SubscriptionHeaderContentProps>
122
139
  valueStyle={styles.value}
123
140
  />
124
141
  )}
125
- {isSandbox && translations.sandboxLabel && (
142
+ {typeof __DEV__ !== 'undefined' && __DEV__ && isSandbox && translations.sandboxLabel && (
126
143
  <DetailRow
127
144
  label={translations.sandboxLabel}
128
145
  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;