@umituz/react-native-subscription 2.27.141 → 2.27.143

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.141",
3
+ "version": "2.27.143",
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,19 +1,12 @@
1
- /**
2
- * PaywallContainer Component
3
- * Uses centralized pending purchase state - no local auth handling
4
- * Apple Guideline 3.1.2 compliant trial display
5
- */
6
-
7
- import React, { useMemo, useEffect } from "react";
1
+ import React, { useMemo } from "react";
8
2
  import { usePaywallVisibility } from "../../subscription/presentation/usePaywallVisibility";
9
3
  import { useSubscriptionPackages } from "../../subscription/infrastructure/hooks/useSubscriptionPackages";
10
4
  import { useRevenueCatTrialEligibility } from "../../subscription/infrastructure/hooks/useRevenueCatTrialEligibility";
11
5
  import { createCreditAmountsFromPackages } from "../../../utils/creditMapper";
12
6
  import { PaywallModal } from "./PaywallModal";
13
- import type { TrialEligibilityInfo } from "./PaywallModal.types";
14
-
15
7
  import { usePaywallActions } from "../hooks/usePaywallActions";
16
8
  import { useAuthAwarePurchase } from "../../subscription/presentation/useAuthAwarePurchase";
9
+ import { useTrialEligibilityCheck } from "../hooks/useTrialEligibilityCheck";
17
10
  import type { PaywallContainerProps } from "./PaywallContainer.types";
18
11
 
19
12
  export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
@@ -59,62 +52,13 @@ export const PaywallContainer: React.FC<PaywallContainerProps> = (props) => {
59
52
  onClose: handleClose,
60
53
  });
61
54
 
62
- // Check trial eligibility only if trialConfig is enabled
63
- // Use ref to track if we've already checked for these packages to avoid redundant calls
64
- const checkedPackagesRef = React.useRef<string[]>([]);
65
-
66
- useEffect(() => {
67
- if (!trialConfig?.enabled) return;
68
- if (packages.length === 0) return;
69
- if (isLoading) return; // Wait for packages to fully load
70
-
71
- // Get current package identifiers
72
- const currentPackageIds = packages.map((pkg) => pkg.product.identifier);
73
- const sortedIds = [...currentPackageIds].sort().join(",");
74
-
75
- // Skip if we've already checked these exact packages
76
- if (checkedPackagesRef.current.join(",") === sortedIds) {
77
- return;
78
- }
79
-
80
- // Update ref
81
- checkedPackagesRef.current = currentPackageIds;
82
-
83
- // Get all actual product IDs from packages
84
- const allProductIds = packages.map((pkg) => pkg.product.identifier);
85
-
86
- // If eligibleProductIds are provided, filter to matching packages (partial match)
87
- // e.g., "yearly" matches "futureus.yearly"
88
- let productIdsToCheck: string[];
89
- if (trialConfig.eligibleProductIds?.length) {
90
- productIdsToCheck = allProductIds.filter((actualId) =>
91
- trialConfig.eligibleProductIds?.some((configId) =>
92
- actualId.toLowerCase().includes(configId.toLowerCase())
93
- )
94
- );
95
- } else {
96
- productIdsToCheck = allProductIds;
97
- }
98
-
99
- if (productIdsToCheck.length > 0) {
100
- checkEligibility(productIdsToCheck);
101
- }
102
- }, [packages, isLoading, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
103
-
104
- // Convert eligibility map to format expected by PaywallModal
105
- // Only process if trial is enabled
106
- const trialEligibility = useMemo((): Record<string, TrialEligibilityInfo> => {
107
- if (!trialConfig?.enabled) return {};
108
-
109
- const result: Record<string, TrialEligibilityInfo> = {};
110
- for (const [productId, info] of Object.entries(eligibilityMap)) {
111
- result[productId] = {
112
- eligible: info.eligible,
113
- durationDays: trialConfig.durationDays ?? info.trialDurationDays ?? 7,
114
- };
115
- }
116
- return result;
117
- }, [eligibilityMap, trialConfig?.enabled, trialConfig?.durationDays]);
55
+ const trialEligibility = useTrialEligibilityCheck({
56
+ packages,
57
+ isLoading,
58
+ eligibilityMap,
59
+ checkEligibility,
60
+ trialConfig,
61
+ });
118
62
 
119
63
  // Compute credit amounts from packageAllocations if not provided directly
120
64
  const creditAmounts = useMemo(() => {
@@ -0,0 +1,65 @@
1
+ import { useEffect, useRef, useMemo } from "react";
2
+ import type { PurchasesPackage } from "react-native-purchases";
3
+ import type { TrialEligibilityInfo } from "../components/PaywallModal.types";
4
+ import type { PaywallContainerProps } from "../components/PaywallContainer.types";
5
+
6
+ interface UseTrialEligibilityCheckParams {
7
+ packages: PurchasesPackage[];
8
+ isLoading: boolean;
9
+ eligibilityMap: Record<string, { eligible: boolean; trialDurationDays?: number }>;
10
+ checkEligibility: (productIds: string[]) => void;
11
+ trialConfig: PaywallContainerProps["trialConfig"];
12
+ }
13
+
14
+ export const useTrialEligibilityCheck = ({
15
+ packages,
16
+ isLoading,
17
+ eligibilityMap,
18
+ checkEligibility,
19
+ trialConfig,
20
+ }: UseTrialEligibilityCheckParams) => {
21
+ const checkedPackagesRef = useRef<string[]>([]);
22
+
23
+ useEffect(() => {
24
+ if (!trialConfig?.enabled || packages.length === 0 || isLoading) return;
25
+
26
+ const currentPackageIds = packages.map((pkg) => pkg.product.identifier);
27
+ const sortedIds = [...currentPackageIds].sort().join(",");
28
+
29
+ if (checkedPackagesRef.current.join(",") === sortedIds) return;
30
+
31
+ checkedPackagesRef.current = currentPackageIds;
32
+
33
+ const allProductIds = packages.map((pkg) => pkg.product.identifier);
34
+
35
+ let productIdsToCheck: string[];
36
+ if (trialConfig.eligibleProductIds?.length) {
37
+ productIdsToCheck = allProductIds.filter((actualId) =>
38
+ trialConfig.eligibleProductIds?.some((configId) =>
39
+ actualId.toLowerCase().includes(configId.toLowerCase())
40
+ )
41
+ );
42
+ } else {
43
+ productIdsToCheck = allProductIds;
44
+ }
45
+
46
+ if (productIdsToCheck.length > 0) {
47
+ checkEligibility(productIdsToCheck);
48
+ }
49
+ }, [packages, isLoading, checkEligibility, trialConfig?.enabled, trialConfig?.eligibleProductIds]);
50
+
51
+ const trialEligibility = useMemo((): Record<string, TrialEligibilityInfo> => {
52
+ if (!trialConfig?.enabled) return {};
53
+
54
+ const result: Record<string, TrialEligibilityInfo> = {};
55
+ for (const [productId, info] of Object.entries(eligibilityMap)) {
56
+ result[productId] = {
57
+ eligible: info.eligible,
58
+ durationDays: trialConfig.durationDays ?? info.trialDurationDays ?? 7,
59
+ };
60
+ }
61
+ return result;
62
+ }, [eligibilityMap, trialConfig?.enabled, trialConfig?.durationDays]);
63
+
64
+ return trialEligibility;
65
+ };
@@ -2,6 +2,7 @@ import Purchases, { type CustomerInfo, type PurchasesOfferings } from "react-nat
2
2
  import type { InitializeResult } from "../../../../shared/application/ports/IRevenueCatService";
3
3
  import type { RevenueCatConfig } from "../../core/RevenueCatConfig";
4
4
  import { resolveApiKey } from "../utils/ApiKeyResolver";
5
+ import { REVENUE_CAT_IGNORED_LOG_MESSAGES } from "../../core/SubscriptionDisplayConfig";
5
6
 
6
7
  export interface InitializerDeps {
7
8
  config: RevenueCatConfig;
@@ -27,12 +28,10 @@ function configureLogHandler(): void {
27
28
  if (typeof Purchases.setLogHandler !== 'function') return;
28
29
  try {
29
30
  Purchases.setLogHandler((_logLevel, message) => {
30
- const ignoreMessages = ['Purchase was cancelled', 'AppTransaction', "Couldn't find previous transactions"];
31
- if (ignoreMessages.some(m => message.includes(m))) return;
31
+ if (REVENUE_CAT_IGNORED_LOG_MESSAGES.some(m => message.includes(m))) return;
32
32
  });
33
33
  configurationState.isLogHandlerConfigured = true;
34
34
  } catch {
35
- // Failing to set log handler should not block initialization
36
35
  }
37
36
  }
38
37
 
@@ -1,141 +1,17 @@
1
- /**
2
- * Wallet Error
3
- *
4
- * Custom error classes for wallet and payment operations.
5
- * Follows the same pattern as SubscriptionError.
6
- */
7
-
8
- import { WALLET_ERROR_MESSAGES } from "./WalletErrorMessages";
9
-
10
- export type WalletErrorCategory =
11
- | "PAYMENT"
12
- | "VALIDATION"
13
- | "INFRASTRUCTURE"
14
- | "BUSINESS";
15
-
16
- export abstract class WalletError extends Error {
17
- abstract readonly code: string;
18
- abstract readonly userMessage: string;
19
- abstract readonly category: WalletErrorCategory;
20
-
21
- constructor(
22
- message: string,
23
- public readonly cause?: Error
24
- ) {
25
- super(message);
26
- this.name = this.constructor.name;
27
- }
28
-
29
- toJSON() {
30
- return {
31
- name: this.name,
32
- code: this.code,
33
- userMessage: this.userMessage,
34
- category: this.category,
35
- message: this.message,
36
- cause: this.cause?.message,
37
- };
38
- }
39
- }
40
-
41
- export class PaymentValidationError extends WalletError {
42
- readonly code = "PAYMENT_VALIDATION_ERROR";
43
- readonly category = "PAYMENT" as const;
44
- readonly userMessage = WALLET_ERROR_MESSAGES.PAYMENT_VALIDATION_FAILED;
45
-
46
- constructor(message: string, cause?: Error) {
47
- super(message, cause);
48
- }
49
- }
50
-
51
- export class PaymentProviderError extends WalletError {
52
- readonly code = "PAYMENT_PROVIDER_ERROR";
53
- readonly category = "PAYMENT" as const;
54
- readonly userMessage = WALLET_ERROR_MESSAGES.PAYMENT_PROVIDER_ERROR;
55
-
56
- constructor(message: string, cause?: Error) {
57
- super(message, cause);
58
- }
59
- }
60
-
61
- export class DuplicatePaymentError extends WalletError {
62
- readonly code = "DUPLICATE_PAYMENT";
63
- readonly category = "PAYMENT" as const;
64
- readonly userMessage = WALLET_ERROR_MESSAGES.DUPLICATE_PAYMENT;
65
- }
66
-
67
- export class UserValidationError extends WalletError {
68
- readonly code = "USER_VALIDATION_ERROR";
69
- readonly category = "VALIDATION" as const;
70
- readonly userMessage = WALLET_ERROR_MESSAGES.USER_VALIDATION_FAILED;
71
- }
72
-
73
- export class PackageValidationError extends WalletError {
74
- readonly code = "PACKAGE_VALIDATION_ERROR";
75
- readonly category = "VALIDATION" as const;
76
- readonly userMessage = WALLET_ERROR_MESSAGES.PACKAGE_VALIDATION_FAILED;
77
- }
78
-
79
- export class ReceiptValidationError extends WalletError {
80
- readonly code = "RECEIPT_VALIDATION_ERROR";
81
- readonly category = "VALIDATION" as const;
82
- readonly userMessage = WALLET_ERROR_MESSAGES.RECEIPT_VALIDATION_FAILED;
83
- }
84
-
85
- export class TransactionError extends WalletError {
86
- readonly code = "TRANSACTION_ERROR";
87
- readonly category = "INFRASTRUCTURE" as const;
88
- readonly userMessage = WALLET_ERROR_MESSAGES.TRANSACTION_FAILED;
89
-
90
- constructor(message: string, cause?: Error) {
91
- super(message, cause);
92
- }
93
- }
94
-
95
- export class NetworkError extends WalletError {
96
- readonly code = "NETWORK_ERROR";
97
- readonly category = "INFRASTRUCTURE" as const;
98
- readonly userMessage = WALLET_ERROR_MESSAGES.NETWORK_ERROR;
99
-
100
- constructor(message: string, cause?: Error) {
101
- super(message, cause);
102
- }
103
- }
104
-
105
- export class CreditLimitError extends WalletError {
106
- readonly code = "CREDIT_LIMIT_ERROR";
107
- readonly category = "BUSINESS" as const;
108
- readonly userMessage = WALLET_ERROR_MESSAGES.CREDIT_LIMIT_EXCEEDED;
109
- }
110
-
111
- export class RefundError extends WalletError {
112
- readonly code = "REFUND_ERROR";
113
- readonly category = "BUSINESS" as const;
114
- readonly userMessage = WALLET_ERROR_MESSAGES.REFUND_FAILED;
115
-
116
- constructor(message: string, cause?: Error) {
117
- super(message, cause);
118
- }
119
- }
120
-
121
- export function handleWalletError(error: unknown): WalletError {
122
- if (error instanceof WalletError) {
123
- return error;
124
- }
125
-
126
- if (error instanceof Error) {
127
- const message = error.message.toLowerCase();
128
-
129
- if (message.includes("network") || message.includes("timeout")) {
130
- return new NetworkError(error.message, error);
131
- }
132
-
133
- if (message.includes("permission") || message.includes("unauthorized")) {
134
- return new UserValidationError("Authentication failed");
135
- }
136
-
137
- return new TransactionError(error.message, error);
138
- }
139
-
140
- return new TransactionError("Unexpected error occurred");
141
- }
1
+ export type { WalletErrorCategory } from "./WalletError.types";
2
+ export { WalletError } from "./WalletError.types";
3
+
4
+ export {
5
+ PaymentValidationError,
6
+ PaymentProviderError,
7
+ DuplicatePaymentError,
8
+ UserValidationError,
9
+ PackageValidationError,
10
+ ReceiptValidationError,
11
+ TransactionError,
12
+ NetworkError,
13
+ CreditLimitError,
14
+ RefundError,
15
+ } from "./WalletErrorClasses";
16
+
17
+ export { handleWalletError } from "./WalletErrorFactory";
@@ -0,0 +1,30 @@
1
+ export type WalletErrorCategory =
2
+ | "PAYMENT"
3
+ | "VALIDATION"
4
+ | "INFRASTRUCTURE"
5
+ | "BUSINESS";
6
+
7
+ export abstract class WalletError extends Error {
8
+ abstract readonly code: string;
9
+ abstract readonly userMessage: string;
10
+ abstract readonly category: WalletErrorCategory;
11
+
12
+ constructor(
13
+ message: string,
14
+ public readonly cause?: Error
15
+ ) {
16
+ super(message);
17
+ this.name = this.constructor.name;
18
+ }
19
+
20
+ toJSON() {
21
+ return {
22
+ name: this.name,
23
+ code: this.code,
24
+ userMessage: this.userMessage,
25
+ category: this.category,
26
+ message: this.message,
27
+ cause: this.cause?.message,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,82 @@
1
+ import { WalletError } from "./WalletError.types";
2
+ import { WALLET_ERROR_MESSAGES } from "./WalletErrorMessages";
3
+
4
+ export class PaymentValidationError extends WalletError {
5
+ readonly code = "PAYMENT_VALIDATION_ERROR";
6
+ readonly category = "PAYMENT" as const;
7
+ readonly userMessage = WALLET_ERROR_MESSAGES.PAYMENT_VALIDATION_FAILED;
8
+
9
+ constructor(message: string, cause?: Error) {
10
+ super(message, cause);
11
+ }
12
+ }
13
+
14
+ export class PaymentProviderError extends WalletError {
15
+ readonly code = "PAYMENT_PROVIDER_ERROR";
16
+ readonly category = "PAYMENT" as const;
17
+ readonly userMessage = WALLET_ERROR_MESSAGES.PAYMENT_PROVIDER_ERROR;
18
+
19
+ constructor(message: string, cause?: Error) {
20
+ super(message, cause);
21
+ }
22
+ }
23
+
24
+ export class DuplicatePaymentError extends WalletError {
25
+ readonly code = "DUPLICATE_PAYMENT";
26
+ readonly category = "PAYMENT" as const;
27
+ readonly userMessage = WALLET_ERROR_MESSAGES.DUPLICATE_PAYMENT;
28
+ }
29
+
30
+ export class UserValidationError extends WalletError {
31
+ readonly code = "USER_VALIDATION_ERROR";
32
+ readonly category = "VALIDATION" as const;
33
+ readonly userMessage = WALLET_ERROR_MESSAGES.USER_VALIDATION_FAILED;
34
+ }
35
+
36
+ export class PackageValidationError extends WalletError {
37
+ readonly code = "PACKAGE_VALIDATION_ERROR";
38
+ readonly category = "VALIDATION" as const;
39
+ readonly userMessage = WALLET_ERROR_MESSAGES.PACKAGE_VALIDATION_FAILED;
40
+ }
41
+
42
+ export class ReceiptValidationError extends WalletError {
43
+ readonly code = "RECEIPT_VALIDATION_ERROR";
44
+ readonly category = "VALIDATION" as const;
45
+ readonly userMessage = WALLET_ERROR_MESSAGES.RECEIPT_VALIDATION_FAILED;
46
+ }
47
+
48
+ export class TransactionError extends WalletError {
49
+ readonly code = "TRANSACTION_ERROR";
50
+ readonly category = "INFRASTRUCTURE" as const;
51
+ readonly userMessage = WALLET_ERROR_MESSAGES.TRANSACTION_FAILED;
52
+
53
+ constructor(message: string, cause?: Error) {
54
+ super(message, cause);
55
+ }
56
+ }
57
+
58
+ export class NetworkError extends WalletError {
59
+ readonly code = "NETWORK_ERROR";
60
+ readonly category = "INFRASTRUCTURE" as const;
61
+ readonly userMessage = WALLET_ERROR_MESSAGES.NETWORK_ERROR;
62
+
63
+ constructor(message: string, cause?: Error) {
64
+ super(message, cause);
65
+ }
66
+ }
67
+
68
+ export class CreditLimitError extends WalletError {
69
+ readonly code = "CREDIT_LIMIT_ERROR";
70
+ readonly category = "BUSINESS" as const;
71
+ readonly userMessage = WALLET_ERROR_MESSAGES.CREDIT_LIMIT_EXCEEDED;
72
+ }
73
+
74
+ export class RefundError extends WalletError {
75
+ readonly code = "REFUND_ERROR";
76
+ readonly category = "BUSINESS" as const;
77
+ readonly userMessage = WALLET_ERROR_MESSAGES.REFUND_FAILED;
78
+
79
+ constructor(message: string, cause?: Error) {
80
+ super(message, cause);
81
+ }
82
+ }
@@ -0,0 +1,24 @@
1
+ import { WalletError } from "./WalletError.types";
2
+ import { NetworkError, UserValidationError, TransactionError } from "./WalletErrorClasses";
3
+
4
+ export const handleWalletError = (error: unknown): WalletError => {
5
+ if (error instanceof WalletError) {
6
+ return error;
7
+ }
8
+
9
+ if (error instanceof Error) {
10
+ const message = error.message.toLowerCase();
11
+
12
+ if (message.includes("network") || message.includes("timeout")) {
13
+ return new NetworkError(error.message, error);
14
+ }
15
+
16
+ if (message.includes("permission") || message.includes("unauthorized")) {
17
+ return new UserValidationError("Authentication failed");
18
+ }
19
+
20
+ return new TransactionError(error.message, error);
21
+ }
22
+
23
+ return new TransactionError("Unexpected error occurred");
24
+ };