@umituz/react-native-subscription 2.37.38 → 2.37.40
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 +1 -1
- package/src/domains/credits/application/CreditLimitCalculator.ts +1 -9
- package/src/domains/credits/application/CreditsInitializer.ts +5 -20
- package/src/domains/credits/application/DeductCreditsCommand.ts +13 -6
- package/src/domains/credits/application/RefundCreditsCommand.ts +1 -5
- package/src/domains/credits/application/credit-strategies/CreditAllocationOrchestrator.ts +1 -9
- package/src/domains/credits/application/credit-strategies/ICreditStrategy.ts +1 -5
- package/src/domains/credits/application/credit-strategies/TrialCreditStrategy.ts +1 -5
- package/src/domains/credits/application/creditDocumentHelpers.ts +2 -9
- package/src/domains/credits/application/creditOperationUtils.ts +1 -43
- package/src/domains/credits/core/Credits.ts +0 -23
- package/src/domains/credits/core/CreditsConstants.ts +0 -11
- package/src/domains/credits/core/CreditsMapper.ts +0 -6
- package/src/domains/credits/core/UserCreditsDocument.ts +0 -12
- package/src/domains/credits/infrastructure/CreditsRepository.ts +6 -1
- package/src/domains/credits/infrastructure/CreditsRepositoryManager.ts +0 -21
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +52 -1
- package/src/domains/credits/presentation/deduct-credit/useDeductCredit.ts +2 -2
- package/src/domains/credits/presentation/useCredits.ts +10 -9
- package/src/domains/paywall/components/PaywallContainer.types.ts +0 -28
- package/src/domains/paywall/components/PaywallModal.styles.ts +0 -4
- package/src/domains/paywall/entities/types.ts +0 -5
- package/src/domains/paywall/hooks/usePaywallActions.ts +1 -15
- package/src/domains/revenuecat/core/errors/RevenueCatError.ts +0 -6
- package/src/domains/revenuecat/core/errors/RevenueCatErrorHandler.ts +0 -24
- package/src/domains/revenuecat/core/errors/RevenueCatErrorMessages.ts +0 -18
- package/src/domains/revenuecat/core/errors/index.ts +0 -4
- package/src/domains/revenuecat/core/types/RevenueCatConfig.ts +3 -7
- package/src/domains/revenuecat/core/types/RevenueCatData.ts +4 -9
- package/src/domains/revenuecat/core/types/RevenueCatTypes.ts +5 -65
- package/src/domains/revenuecat/core/types/index.ts +0 -4
- package/src/domains/revenuecat/infrastructure/services/UserSwitchMutex.ts +1 -24
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +5 -21
- package/src/domains/subscription/application/SubscriptionInitializerTypes.ts +1 -5
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +0 -4
- package/src/domains/subscription/application/SubscriptionSyncService.ts +4 -8
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +1 -1
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +15 -2
- package/src/domains/subscription/application/initializer/ServiceConfigurator.ts +9 -2
- package/src/domains/subscription/application/statusChangeHandlers.ts +14 -27
- package/src/domains/subscription/application/syncIdGenerators.ts +0 -4
- package/src/domains/subscription/constants/thresholds.ts +0 -9
- package/src/domains/subscription/core/SubscriptionConstants.ts +0 -4
- package/src/domains/subscription/core/SubscriptionStatus.ts +11 -21
- package/src/domains/subscription/core/SubscriptionStatusHandlers.ts +4 -7
- package/src/domains/subscription/infrastructure/handlers/PurchaseStatusResolver.ts +1 -1
- package/src/domains/subscription/infrastructure/hooks/subscriptionQueryKeys.ts +0 -13
- package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +0 -18
- package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +3 -17
- package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +0 -17
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionPackages.ts +0 -19
- package/src/domains/subscription/infrastructure/hooks/useSubscriptionQueries.ts +0 -6
- package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +0 -17
- package/src/domains/subscription/infrastructure/state/initializationState.ts +0 -25
- package/src/domains/subscription/infrastructure/utils/InitializationCache.ts +0 -21
- package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -17
- package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +0 -5
- package/src/domains/subscription/infrastructure/utils/renewal/PackageTierComparator.ts +1 -0
- package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +0 -18
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.styles.ts +0 -5
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCardTypes.ts +0 -5
- package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -5
- package/src/domains/subscription/presentation/stores/index.ts +0 -4
- package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +0 -13
- package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +30 -21
- package/src/domains/subscription/presentation/usePaywallVisibility.ts +0 -9
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +8 -11
- package/src/domains/subscription/utils/authGuards.ts +3 -0
- package/src/domains/trial/application/TrialService.ts +0 -9
- package/src/domains/trial/core/TrialTypes.ts +0 -8
- package/src/domains/wallet/domain/mappers/TransactionMapper.ts +0 -5
- package/src/domains/wallet/domain/types/transaction.types.ts +0 -7
- package/src/domains/wallet/index.ts +0 -7
- package/src/domains/wallet/infrastructure/config/walletConfig.ts +0 -11
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +6 -3
- package/src/domains/wallet/presentation/hooks/useWallet.ts +0 -7
- package/src/domains/wallet/utils/transactionIconMap.ts +0 -10
- package/src/global.d.ts +0 -6
- package/src/index.ts +1 -4
- package/src/init/createSubscriptionInitModule.ts +12 -2
- package/src/init/index.ts +1 -5
- package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -11
- package/src/shared/application/FeedbackService.ts +3 -21
- package/src/shared/application/ports/ISubscriptionRepository.ts +0 -4
- package/src/shared/infrastructure/SubscriptionEventBus.ts +0 -13
- package/src/shared/infrastructure/firestore/collectionUtils.ts +1 -17
- package/src/shared/infrastructure/firestore/index.ts +0 -4
- package/src/shared/infrastructure/firestore/resultUtils.ts +0 -12
- package/src/shared/infrastructure/react-query/hooks/usePreviousUserCleanup.ts +0 -17
- package/src/shared/infrastructure/react-query/queryConfig.ts +0 -15
- package/src/shared/utils/BaseError.ts +0 -5
- package/src/shared/utils/Result.ts +0 -20
- package/src/shared/utils/dateConverter.ts +6 -46
- package/src/utils/appUtils.ts +0 -16
- package/src/utils/creditMapper.ts +0 -7
- package/src/utils/dateUtils.compare.ts +0 -24
- package/src/utils/dateUtils.core.ts +0 -39
- package/src/utils/dateUtils.format.ts +0 -41
- package/src/utils/dateUtils.math.ts +0 -41
- package/src/utils/dateUtils.ts +0 -5
- package/src/utils/packagePeriodUtils.ts +0 -20
- package/src/utils/packageTypeDetector.ts +1 -21
- package/src/utils/premiumStatusUtils.ts +1 -14
- package/src/utils/priceUtils.ts +0 -35
- package/src/utils/tierUtils.ts +1 -8
- package/src/utils/types.ts +1 -25
- package/src/utils/validation.ts +1 -7
- package/src/domains/README.md +0 -52
- package/src/domains/config/domain/README.md +0 -37
- package/src/domains/config/domain/entities/README.md +0 -41
- package/src/domains/credits/application/credit-strategies/SyncCreditStrategy.ts +0 -24
- package/src/domains/paywall/README.md +0 -101
- package/src/domains/paywall/entities/README.md +0 -40
- package/src/domains/paywall/hooks/README.md +0 -41
- package/src/domains/subscription/application/syncConstants.ts +0 -1
- package/src/domains/subscription/infrastructure/README.md +0 -41
- package/src/domains/subscription/infrastructure/config/README.md +0 -49
- package/src/domains/subscription/infrastructure/handlers/README.md +0 -41
- package/src/domains/subscription/infrastructure/hooks/README.md +0 -50
- package/src/domains/subscription/infrastructure/managers/README.md +0 -41
- package/src/domains/subscription/infrastructure/services/README.md +0 -42
- package/src/domains/subscription/infrastructure/utils/README.md +0 -41
- package/src/domains/subscription/presentation/components/README.md +0 -155
- package/src/domains/subscription/presentation/components/details/CreditRow.md +0 -92
- package/src/domains/subscription/presentation/components/details/DetailRow.md +0 -91
- package/src/domains/subscription/presentation/components/details/PremiumDetailsCard.md +0 -93
- package/src/domains/subscription/presentation/components/details/PremiumStatusBadge.md +0 -91
- package/src/domains/subscription/presentation/components/details/README.md +0 -99
- package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.md +0 -90
- package/src/domains/subscription/presentation/components/feedback/README.md +0 -99
- package/src/domains/subscription/presentation/components/paywall/PaywallModal.md +0 -94
- package/src/domains/subscription/presentation/components/paywall/README.md +0 -54
- package/src/domains/subscription/presentation/components/sections/README.md +0 -99
- package/src/domains/subscription/presentation/components/sections/SubscriptionSection.md +0 -94
- package/src/domains/subscription/presentation/utils/README.md +0 -31
- package/src/domains/wallet/README.md +0 -51
- package/src/domains/wallet/domain/README.md +0 -41
- package/src/domains/wallet/infrastructure/README.md +0 -41
- package/src/domains/wallet/presentation/components/README.md +0 -41
- package/src/domains/wallet/presentation/hooks/README.md +0 -41
- package/src/shared/application/ports/README.md +0 -48
|
@@ -1,57 +1,29 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PaywallContainer Types
|
|
3
|
-
* Props for subscription paywall
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import type { ImageSourcePropType } from "react-native";
|
|
7
2
|
import type { PaywallTranslations, PaywallLegalUrls, SubscriptionFeature } from "../entities/types";
|
|
8
3
|
import type { PurchaseSource } from "../../subscription/core/SubscriptionConstants";
|
|
9
4
|
import type { PackageAllocationMap } from "../../credits/core/Credits";
|
|
10
5
|
|
|
11
|
-
/**
|
|
12
|
-
* Trial display configuration
|
|
13
|
-
* Controls how free trial info is displayed (Apple-compliant)
|
|
14
|
-
*/
|
|
15
6
|
export interface TrialConfig {
|
|
16
|
-
/** Enable trial display (default: false) */
|
|
17
7
|
readonly enabled: boolean;
|
|
18
|
-
/** Product IDs that have trial offers (if empty, checks all via RevenueCat) */
|
|
19
8
|
readonly eligibleProductIds?: readonly string[];
|
|
20
|
-
/** Trial duration in days (default: 7) */
|
|
21
9
|
readonly durationDays?: number;
|
|
22
|
-
/** Text to show for trial (e.g., "7 days free, then billed") */
|
|
23
10
|
readonly trialText?: string;
|
|
24
11
|
}
|
|
25
12
|
|
|
26
13
|
export interface PaywallContainerProps {
|
|
27
|
-
/** Paywall translations - no defaults, must be provided */
|
|
28
14
|
readonly translations: PaywallTranslations;
|
|
29
|
-
/** Legal URLs for privacy and terms */
|
|
30
15
|
readonly legalUrls?: PaywallLegalUrls;
|
|
31
|
-
/** Feature list to display */
|
|
32
16
|
readonly features?: readonly SubscriptionFeature[];
|
|
33
|
-
/** Hero image for paywall header */
|
|
34
17
|
readonly heroImage?: ImageSourcePropType;
|
|
35
|
-
/** Best value package identifier for badge */
|
|
36
18
|
readonly bestValueIdentifier?: string;
|
|
37
|
-
/** Credit amounts per product identifier (takes precedence over packageAllocations) */
|
|
38
19
|
readonly creditAmounts?: Record<string, number>;
|
|
39
|
-
/** Credits label text (e.g., "credits") */
|
|
40
20
|
readonly creditsLabel?: string;
|
|
41
|
-
/** Package allocations for auto-computing creditAmounts (used when creditAmounts not provided) */
|
|
42
21
|
readonly packageAllocations?: PackageAllocationMap;
|
|
43
|
-
/** Source of the paywall - affects pending purchase handling */
|
|
44
22
|
readonly source?: PurchaseSource;
|
|
45
|
-
/** Callback when purchase succeeds */
|
|
46
23
|
readonly onPurchaseSuccess?: () => void;
|
|
47
|
-
/** Callback when purchase fails */
|
|
48
24
|
readonly onPurchaseError?: (error: Error | string) => void;
|
|
49
|
-
/** Callback when auth is required (for anonymous users) */
|
|
50
25
|
readonly onAuthRequired?: () => void;
|
|
51
|
-
/** Visibility override */
|
|
52
26
|
readonly visible?: boolean;
|
|
53
|
-
/** Callback when paywall is closed */
|
|
54
27
|
readonly onClose?: () => void;
|
|
55
|
-
/** Trial display configuration (Apple-compliant) */
|
|
56
28
|
readonly trialConfig?: TrialConfig;
|
|
57
29
|
}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* usePaywallActions Hook
|
|
3
|
-
* Encapsulates purchase and restore flow for the paywall.
|
|
4
|
-
*/
|
|
5
1
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
6
2
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
7
3
|
import { usePurchaseLoadingStore } from "../../subscription/presentation/stores";
|
|
@@ -52,7 +48,6 @@ export function usePaywallActions({
|
|
|
52
48
|
});
|
|
53
49
|
|
|
54
50
|
const handlePurchase = useCallback(async () => {
|
|
55
|
-
|
|
56
51
|
if (!selectedPlanId) {
|
|
57
52
|
return;
|
|
58
53
|
}
|
|
@@ -71,7 +66,7 @@ export function usePaywallActions({
|
|
|
71
66
|
|
|
72
67
|
if (!pkg) {
|
|
73
68
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
74
|
-
console.error("[usePaywallActions]
|
|
69
|
+
console.error("[usePaywallActions] Package not found", {
|
|
75
70
|
selectedPlanId,
|
|
76
71
|
availablePackages: packages.map(p => p.product.identifier),
|
|
77
72
|
});
|
|
@@ -85,18 +80,12 @@ export function usePaywallActions({
|
|
|
85
80
|
startPurchase(selectedPlanId, "manual");
|
|
86
81
|
|
|
87
82
|
try {
|
|
88
|
-
|
|
89
83
|
const success = await onPurchaseRef.current(pkg);
|
|
90
84
|
|
|
91
85
|
if (success === true) {
|
|
92
86
|
onPurchaseSuccessRef.current?.();
|
|
93
87
|
onCloseRef.current?.();
|
|
94
|
-
} else if (success === false) {
|
|
95
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
96
|
-
console.warn("[usePaywallActions] ⚠️ Purchase returned false (user cancelled or failed)");
|
|
97
|
-
}
|
|
98
88
|
}
|
|
99
|
-
// else: success is undefined/null - no action needed
|
|
100
89
|
} catch (error) {
|
|
101
90
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
102
91
|
onPurchaseErrorRef.current?.(err);
|
|
@@ -107,7 +96,6 @@ export function usePaywallActions({
|
|
|
107
96
|
}, [selectedPlanId, packages, isProcessing, startPurchase, endPurchase]);
|
|
108
97
|
|
|
109
98
|
const handleRestore = useCallback(async () => {
|
|
110
|
-
|
|
111
99
|
if (!onRestoreRef.current) {
|
|
112
100
|
const err = new Error("Restore handler not configured");
|
|
113
101
|
onPurchaseErrorRef.current?.(err);
|
|
@@ -120,13 +108,11 @@ export function usePaywallActions({
|
|
|
120
108
|
|
|
121
109
|
setIsLocalProcessing(true);
|
|
122
110
|
try {
|
|
123
|
-
|
|
124
111
|
const success = await onRestoreRef.current();
|
|
125
112
|
|
|
126
113
|
if (success === true) {
|
|
127
114
|
onPurchaseSuccessRef.current?.();
|
|
128
115
|
}
|
|
129
|
-
// else: success is false/undefined - restore failed or user cancelled, no action needed
|
|
130
116
|
} catch (error) {
|
|
131
117
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
132
118
|
onPurchaseErrorRef.current?.(err);
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RevenueCat Error Classes
|
|
3
|
-
* Domain-specific error types for RevenueCat operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { BaseError } from "../../../../shared/utils/BaseError";
|
|
7
2
|
|
|
8
3
|
class RevenueCatError extends BaseError {
|
|
@@ -49,4 +44,3 @@ export class RevenueCatNetworkError extends RevenueCatError {
|
|
|
49
44
|
this.name = "RevenueCatNetworkError";
|
|
50
45
|
}
|
|
51
46
|
}
|
|
52
|
-
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RevenueCat Error Handler
|
|
3
|
-
* Error code mapping and message resolution utilities
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import Purchases from "react-native-purchases";
|
|
7
2
|
import {
|
|
8
3
|
ERROR_MESSAGES_MAP,
|
|
@@ -11,12 +6,7 @@ import {
|
|
|
11
6
|
type PurchasesErrorCode,
|
|
12
7
|
} from "./RevenueCatErrorMessages";
|
|
13
8
|
|
|
14
|
-
/**
|
|
15
|
-
* Error Code to Enum Mapping
|
|
16
|
-
* Maps both string keys and numeric codes to Purchases error enum values
|
|
17
|
-
*/
|
|
18
9
|
const ERROR_CODE_MAP = new Map<string, PurchasesErrorCode>([
|
|
19
|
-
// String error codes
|
|
20
10
|
["PURCHASE_CANCELLED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
|
|
21
11
|
["PURCHASE_NOT_ALLOWED_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
|
|
22
12
|
["PURCHASE_INVALID_ERROR", Purchases.PURCHASES_ERROR_CODE.PURCHASE_INVALID_ERROR],
|
|
@@ -31,7 +21,6 @@ const ERROR_CODE_MAP = new Map<string, PurchasesErrorCode>([
|
|
|
31
21
|
["STORE_PROBLEM_ERROR", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
|
|
32
22
|
["PAYMENT_PENDING_ERROR", Purchases.PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR],
|
|
33
23
|
|
|
34
|
-
// Numeric error codes as fallback
|
|
35
24
|
["1", Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR],
|
|
36
25
|
["2", Purchases.PURCHASES_ERROR_CODE.STORE_PROBLEM_ERROR],
|
|
37
26
|
["3", Purchases.PURCHASES_ERROR_CODE.PURCHASE_NOT_ALLOWED_ERROR],
|
|
@@ -47,13 +36,6 @@ const ERROR_CODE_MAP = new Map<string, PurchasesErrorCode>([
|
|
|
47
36
|
["0", Purchases.PURCHASES_ERROR_CODE.UNKNOWN_ERROR],
|
|
48
37
|
]);
|
|
49
38
|
|
|
50
|
-
/**
|
|
51
|
-
* Get error message configuration for a given error code
|
|
52
|
-
* Strategy Pattern with Map lookup - O(1) complexity
|
|
53
|
-
*
|
|
54
|
-
* @param errorCode - Error code string from RevenueCat error
|
|
55
|
-
* @returns ErrorMessage configuration
|
|
56
|
-
*/
|
|
57
39
|
function getErrorMessageForCode(errorCode: string | null | undefined): ErrorMessage {
|
|
58
40
|
if (!errorCode) {
|
|
59
41
|
return DEFAULT_ERROR_MESSAGE;
|
|
@@ -70,12 +52,6 @@ function getErrorMessageForCode(errorCode: string | null | undefined): ErrorMess
|
|
|
70
52
|
return DEFAULT_ERROR_MESSAGE;
|
|
71
53
|
}
|
|
72
54
|
|
|
73
|
-
/**
|
|
74
|
-
* Get error message from RevenueCat error object
|
|
75
|
-
*
|
|
76
|
-
* @param error - RevenueCat error object
|
|
77
|
-
* @returns ErrorMessage configuration
|
|
78
|
-
*/
|
|
79
55
|
export function getErrorMessage(error: unknown): ErrorMessage {
|
|
80
56
|
if (!error || typeof error !== "object") {
|
|
81
57
|
return DEFAULT_ERROR_MESSAGE;
|
|
@@ -1,28 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RevenueCat Error Messages
|
|
3
|
-
* User-friendly error message configurations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import Purchases from "react-native-purchases";
|
|
7
2
|
|
|
8
|
-
/**
|
|
9
|
-
* Error Message Configuration
|
|
10
|
-
*/
|
|
11
3
|
export interface ErrorMessage {
|
|
12
4
|
title: string;
|
|
13
5
|
message: string;
|
|
14
6
|
shouldShowAlert?: boolean;
|
|
15
7
|
}
|
|
16
8
|
|
|
17
|
-
/**
|
|
18
|
-
* RevenueCat Error Code Type
|
|
19
|
-
*/
|
|
20
9
|
export type PurchasesErrorCode = typeof Purchases.PURCHASES_ERROR_CODE[keyof typeof Purchases.PURCHASES_ERROR_CODE];
|
|
21
10
|
|
|
22
|
-
/**
|
|
23
|
-
* User-friendly error messages mapped by error code enum
|
|
24
|
-
* Strategy Pattern: Each error code has its own message configuration
|
|
25
|
-
*/
|
|
26
11
|
export const ERROR_MESSAGES_MAP = new Map<PurchasesErrorCode, ErrorMessage>([
|
|
27
12
|
[
|
|
28
13
|
Purchases.PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR,
|
|
@@ -130,9 +115,6 @@ export const ERROR_MESSAGES_MAP = new Map<PurchasesErrorCode, ErrorMessage>([
|
|
|
130
115
|
],
|
|
131
116
|
]);
|
|
132
117
|
|
|
133
|
-
/**
|
|
134
|
-
* Default error message for unknown errors
|
|
135
|
-
*/
|
|
136
118
|
export const DEFAULT_ERROR_MESSAGE: ErrorMessage = {
|
|
137
119
|
title: "Error",
|
|
138
120
|
message: "An error occurred. Please try again.",
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { PackageType } from "./RevenueCatTypes";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* RevenueCat Configuration
|
|
6
|
-
* All callbacks receive data directly from RevenueCat SDK
|
|
7
|
-
*/
|
|
8
4
|
export interface RevenueCatConfig {
|
|
9
5
|
apiKey?: string;
|
|
10
6
|
entitlementIdentifier: string;
|
|
@@ -15,14 +11,14 @@ export interface RevenueCatConfig {
|
|
|
15
11
|
productId?: string,
|
|
16
12
|
expiresAt?: string,
|
|
17
13
|
willRenew?: boolean,
|
|
18
|
-
periodType?: string
|
|
14
|
+
periodType?: string
|
|
19
15
|
) => Promise<void> | void;
|
|
20
16
|
onPurchaseCompleted?: (
|
|
21
17
|
userId: string,
|
|
22
18
|
productId: string,
|
|
23
19
|
customerInfo: CustomerInfo,
|
|
24
|
-
source?: string,
|
|
25
|
-
packageType?: PackageType | null
|
|
20
|
+
source?: string,
|
|
21
|
+
packageType?: PackageType | null
|
|
26
22
|
) => Promise<void> | void;
|
|
27
23
|
onRestoreCompleted?: (
|
|
28
24
|
userId: string,
|
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type { Store, OwnershipType, PackageType } from "./RevenueCatTypes";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* RevenueCat subscription data (Single Source of Truth)
|
|
5
|
-
* Used across the subscription package for storing RevenueCat data in Firestore
|
|
6
|
-
* All fields come directly from RevenueCat SDK - no manual definitions
|
|
7
|
-
*/
|
|
8
3
|
export interface RevenueCatData {
|
|
9
4
|
expirationDate: string | null;
|
|
10
5
|
willRenew: boolean | null;
|
|
11
6
|
originalTransactionId: string | null;
|
|
12
7
|
isPremium: boolean;
|
|
13
|
-
periodType: string | null;
|
|
14
|
-
packageType: PackageType | null;
|
|
8
|
+
periodType: string | null;
|
|
9
|
+
packageType: PackageType | null;
|
|
15
10
|
unsubscribeDetectedAt: string | null;
|
|
16
11
|
billingIssueDetectedAt: string | null;
|
|
17
|
-
store: Store | null;
|
|
18
|
-
ownershipType: OwnershipType | null;
|
|
12
|
+
store: Store | null;
|
|
13
|
+
ownershipType: OwnershipType | null;
|
|
19
14
|
}
|
|
@@ -1,39 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RevenueCat Type Definitions
|
|
3
|
-
* Proper typing for RevenueCat entitlements and errors
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import type { CustomerInfo, PurchasesEntitlementInfo, PurchasesPackage } from "react-native-purchases";
|
|
7
2
|
|
|
8
|
-
/**
|
|
9
|
-
* Default entitlement identifier
|
|
10
|
-
* Can be overridden in RevenueCatConfig
|
|
11
|
-
*/
|
|
12
|
-
const DEFAULT_ENTITLEMENT_ID = "premium";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Store type - Directly from RevenueCat SDK
|
|
16
|
-
* Automatically stays in sync with RevenueCat SDK updates
|
|
17
|
-
*/
|
|
18
3
|
export type Store = PurchasesEntitlementInfo['store'];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* OwnershipType - Directly from RevenueCat SDK
|
|
22
|
-
* Automatically stays in sync with RevenueCat SDK updates
|
|
23
|
-
*/
|
|
24
4
|
export type OwnershipType = PurchasesEntitlementInfo['ownershipType'];
|
|
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
5
|
export type PackageType = PurchasesPackage['packageType'];
|
|
32
6
|
|
|
33
|
-
/**
|
|
34
|
-
* RevenueCat Entitlement Info
|
|
35
|
-
* Represents active entitlement data from CustomerInfo
|
|
36
|
-
*/
|
|
37
7
|
interface RevenueCatEntitlement {
|
|
38
8
|
identifier: string;
|
|
39
9
|
productIdentifier: string;
|
|
@@ -45,11 +15,10 @@ interface RevenueCatEntitlement {
|
|
|
45
15
|
expirationDate: string | null;
|
|
46
16
|
unsubscribeDetectedAt: string | null;
|
|
47
17
|
billingIssueDetectedAt: string | null;
|
|
18
|
+
store: Store | null;
|
|
19
|
+
ownershipType: OwnershipType | null;
|
|
48
20
|
}
|
|
49
21
|
|
|
50
|
-
/**
|
|
51
|
-
* RevenueCat Purchase Error with userCancelled flag
|
|
52
|
-
*/
|
|
53
22
|
interface RevenueCatPurchaseErrorInfo extends Error {
|
|
54
23
|
userCancelled?: boolean;
|
|
55
24
|
code?: string;
|
|
@@ -57,12 +26,9 @@ interface RevenueCatPurchaseErrorInfo extends Error {
|
|
|
57
26
|
underlyingErrorMessage?: string;
|
|
58
27
|
}
|
|
59
28
|
|
|
60
|
-
/**
|
|
61
|
-
* Extract entitlement from CustomerInfo
|
|
62
|
-
*/
|
|
63
29
|
export function getPremiumEntitlement(
|
|
64
30
|
customerInfo: CustomerInfo,
|
|
65
|
-
entitlementIdentifier: string
|
|
31
|
+
entitlementIdentifier: string
|
|
66
32
|
): RevenueCatEntitlement | null {
|
|
67
33
|
const entitlement = customerInfo.entitlements.active[entitlementIdentifier];
|
|
68
34
|
if (!entitlement) {
|
|
@@ -80,19 +46,15 @@ export function getPremiumEntitlement(
|
|
|
80
46
|
expirationDate: entitlement.expirationDate,
|
|
81
47
|
unsubscribeDetectedAt: entitlement.unsubscribeDetectedAt,
|
|
82
48
|
billingIssueDetectedAt: entitlement.billingIssueDetectedAt,
|
|
49
|
+
store: entitlement.store ?? null,
|
|
50
|
+
ownershipType: entitlement.ownershipType ?? null,
|
|
83
51
|
};
|
|
84
52
|
}
|
|
85
53
|
|
|
86
|
-
/**
|
|
87
|
-
* Type guard for RevenueCat purchase error
|
|
88
|
-
*/
|
|
89
54
|
function isRevenueCatPurchaseError(error: unknown): error is RevenueCatPurchaseErrorInfo {
|
|
90
55
|
return error instanceof Error && ("userCancelled" in error || "code" in error);
|
|
91
56
|
}
|
|
92
57
|
|
|
93
|
-
/**
|
|
94
|
-
* Extract error code from RevenueCat error
|
|
95
|
-
*/
|
|
96
58
|
export function getErrorCode(error: unknown): string | null {
|
|
97
59
|
if (!isRevenueCatPurchaseError(error)) {
|
|
98
60
|
return null;
|
|
@@ -100,57 +62,35 @@ export function getErrorCode(error: unknown): string | null {
|
|
|
100
62
|
return error.code || error.readableErrorCode || null;
|
|
101
63
|
}
|
|
102
64
|
|
|
103
|
-
/**
|
|
104
|
-
* Check if error is a user cancellation
|
|
105
|
-
* Checks both userCancelled flag and PURCHASE_CANCELLED_ERROR code
|
|
106
|
-
*/
|
|
107
65
|
export function isUserCancelledError(error: unknown): boolean {
|
|
108
66
|
if (!isRevenueCatPurchaseError(error)) {
|
|
109
67
|
return false;
|
|
110
68
|
}
|
|
111
|
-
|
|
112
|
-
// Check userCancelled flag
|
|
113
69
|
if (error.userCancelled === true) {
|
|
114
70
|
return true;
|
|
115
71
|
}
|
|
116
|
-
|
|
117
|
-
// Check error code
|
|
118
72
|
const code = getErrorCode(error);
|
|
119
73
|
return code === "PURCHASE_CANCELLED_ERROR" || code === "1";
|
|
120
74
|
}
|
|
121
75
|
|
|
122
|
-
/**
|
|
123
|
-
* Check if error is a network error
|
|
124
|
-
*/
|
|
125
76
|
export function isNetworkError(error: unknown): boolean {
|
|
126
77
|
const code = getErrorCode(error);
|
|
127
78
|
return code === "NETWORK_ERROR" || code === "7";
|
|
128
79
|
}
|
|
129
80
|
|
|
130
|
-
/**
|
|
131
|
-
* Check if error is already purchased
|
|
132
|
-
*/
|
|
133
81
|
export function isAlreadyPurchasedError(error: unknown): boolean {
|
|
134
82
|
const code = getErrorCode(error);
|
|
135
83
|
return code === "PRODUCT_ALREADY_PURCHASED_ERROR" || code === "6";
|
|
136
84
|
}
|
|
137
85
|
|
|
138
|
-
/**
|
|
139
|
-
* Check if error is invalid credentials
|
|
140
|
-
*/
|
|
141
86
|
export function isInvalidCredentialsError(error: unknown): boolean {
|
|
142
87
|
const code = getErrorCode(error);
|
|
143
88
|
return code === "INVALID_CREDENTIALS_ERROR" || code === "9";
|
|
144
89
|
}
|
|
145
90
|
|
|
146
|
-
/**
|
|
147
|
-
* Extract raw error message from error object
|
|
148
|
-
* For user-friendly messages, use getErrorMessageForCode from errors module
|
|
149
|
-
*/
|
|
150
91
|
export function getRawErrorMessage(error: unknown, fallback: string): string {
|
|
151
92
|
if (error instanceof Error) {
|
|
152
93
|
return error.message;
|
|
153
94
|
}
|
|
154
95
|
return fallback;
|
|
155
96
|
}
|
|
156
|
-
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UserSwitchMutex
|
|
3
|
-
* Prevents concurrent Purchases.logIn() calls that cause RevenueCat 429 errors
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
class UserSwitchMutexImpl {
|
|
7
2
|
private activeSwitchPromise: Promise<any> | null = null;
|
|
8
3
|
private activeUserId: string | null = null;
|
|
9
4
|
private lastSwitchTime = 0;
|
|
10
|
-
private readonly MIN_SWITCH_INTERVAL_MS = 1000;
|
|
5
|
+
private readonly MIN_SWITCH_INTERVAL_MS = 1000;
|
|
11
6
|
|
|
12
|
-
/**
|
|
13
|
-
* Acquires the lock for user switch operation
|
|
14
|
-
* Returns existing promise if a switch is in progress for the same user
|
|
15
|
-
* Waits if a switch is in progress for a different user
|
|
16
|
-
*/
|
|
17
7
|
async acquire(userId: string): Promise<{ shouldProceed: boolean; existingPromise?: Promise<any> }> {
|
|
18
|
-
// If a switch is in progress for the exact same user, return the existing promise
|
|
19
8
|
if (this.activeSwitchPromise && this.activeUserId === userId) {
|
|
20
9
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
21
10
|
console.log('[UserSwitchMutex] Switch already in progress for this user, returning existing promise');
|
|
@@ -23,7 +12,6 @@ class UserSwitchMutexImpl {
|
|
|
23
12
|
return { shouldProceed: false, existingPromise: this.activeSwitchPromise };
|
|
24
13
|
}
|
|
25
14
|
|
|
26
|
-
// If a switch is in progress for a different user, wait for it to complete
|
|
27
15
|
if (this.activeSwitchPromise) {
|
|
28
16
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
29
17
|
console.log('[UserSwitchMutex] Waiting for active switch to complete...');
|
|
@@ -31,10 +19,8 @@ class UserSwitchMutexImpl {
|
|
|
31
19
|
try {
|
|
32
20
|
await this.activeSwitchPromise;
|
|
33
21
|
} catch {
|
|
34
|
-
// Ignore error, just wait for completion
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
// Add a small delay to avoid rapid-fire requests
|
|
38
24
|
const timeSinceLastSwitch = Date.now() - this.lastSwitchTime;
|
|
39
25
|
if (timeSinceLastSwitch < this.MIN_SWITCH_INTERVAL_MS) {
|
|
40
26
|
const delayNeeded = this.MIN_SWITCH_INTERVAL_MS - timeSinceLastSwitch;
|
|
@@ -44,12 +30,10 @@ class UserSwitchMutexImpl {
|
|
|
44
30
|
await new Promise<void>(resolve => setTimeout(() => resolve(), delayNeeded));
|
|
45
31
|
}
|
|
46
32
|
|
|
47
|
-
// Re-check after delay: another caller may have acquired the lock during our wait
|
|
48
33
|
if (this.activeSwitchPromise) {
|
|
49
34
|
if (this.activeUserId === userId) {
|
|
50
35
|
return { shouldProceed: false, existingPromise: this.activeSwitchPromise };
|
|
51
36
|
}
|
|
52
|
-
// Another switch started for a different user — recurse to wait again
|
|
53
37
|
return this.acquire(userId);
|
|
54
38
|
}
|
|
55
39
|
}
|
|
@@ -58,14 +42,10 @@ class UserSwitchMutexImpl {
|
|
|
58
42
|
return { shouldProceed: true };
|
|
59
43
|
}
|
|
60
44
|
|
|
61
|
-
/**
|
|
62
|
-
* Sets the active promise for the current switch operation
|
|
63
|
-
*/
|
|
64
45
|
setPromise(promise: Promise<any>): void {
|
|
65
46
|
this.activeSwitchPromise = promise;
|
|
66
47
|
this.lastSwitchTime = Date.now();
|
|
67
48
|
|
|
68
|
-
// Clear the lock when the promise completes (success or failure)
|
|
69
49
|
promise
|
|
70
50
|
.finally(() => {
|
|
71
51
|
if (this.activeSwitchPromise === promise) {
|
|
@@ -75,9 +55,6 @@ class UserSwitchMutexImpl {
|
|
|
75
55
|
});
|
|
76
56
|
}
|
|
77
57
|
|
|
78
|
-
/**
|
|
79
|
-
* Resets the mutex state
|
|
80
|
-
*/
|
|
81
58
|
reset(): void {
|
|
82
59
|
this.activeSwitchPromise = null;
|
|
83
60
|
this.activeUserId = null;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { FirebaseAuthLike } from "./SubscriptionInitializerTypes";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Gets the current user ID from Firebase auth.
|
|
5
|
-
* Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
|
|
6
|
-
*/
|
|
7
3
|
export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string | undefined => {
|
|
8
4
|
const auth = getAuth();
|
|
9
5
|
if (!auth) {
|
|
@@ -16,19 +12,12 @@ export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string
|
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
if (user.isAnonymous) {
|
|
19
|
-
|
|
20
|
-
console.log(`[SubscriptionAuthListener] Anonymous user - using Firebase anonymous UID: ${user.uid}`);
|
|
21
|
-
}
|
|
15
|
+
return undefined;
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
return user.uid;
|
|
25
19
|
};
|
|
26
20
|
|
|
27
|
-
/**
|
|
28
|
-
* Sets up auth state listener that will re-initialize subscription
|
|
29
|
-
* when user auth state changes (login/logout).
|
|
30
|
-
* Returns undefined for anonymous users to let RevenueCat generate its own anonymous ID.
|
|
31
|
-
*/
|
|
32
21
|
export const setupAuthStateListener = (
|
|
33
22
|
getAuth: () => FirebaseAuthLike | null,
|
|
34
23
|
onUserChange: (userId: string | undefined) => void
|
|
@@ -39,16 +28,11 @@ export const setupAuthStateListener = (
|
|
|
39
28
|
}
|
|
40
29
|
|
|
41
30
|
return auth.onAuthStateChanged((user) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log('[SubscriptionAuthListener] 🔔 Auth state changed:', {
|
|
46
|
-
hasUser: !!user,
|
|
47
|
-
isAnonymous: user?.isAnonymous,
|
|
48
|
-
userId: userId || '(undefined - no user)',
|
|
49
|
-
});
|
|
31
|
+
if (!user || user.isAnonymous) {
|
|
32
|
+
onUserChange(undefined);
|
|
33
|
+
return;
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
onUserChange(
|
|
36
|
+
onUserChange(user.uid);
|
|
53
37
|
});
|
|
54
38
|
};
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subscription Initializer Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import type { CreditsConfig } from "../../credits/core/Credits";
|
|
6
2
|
import type { UserCreditsDocumentRead } from "../../credits/core/UserCreditsDocument";
|
|
7
3
|
import type { PurchaseSource, PurchaseType } from "../core/SubscriptionConstants";
|
|
@@ -40,7 +36,7 @@ export interface InitializeCreditsMetadata {
|
|
|
40
36
|
willRenew: boolean | null;
|
|
41
37
|
originalTransactionId: string | null;
|
|
42
38
|
isPremium: boolean;
|
|
43
|
-
periodType: string | null;
|
|
39
|
+
periodType: string | null;
|
|
44
40
|
unsubscribeDetectedAt: string | null;
|
|
45
41
|
billingIssueDetectedAt: string | null;
|
|
46
42
|
store: Store | null;
|
|
@@ -69,19 +69,15 @@ export class SubscriptionSyncProcessor {
|
|
|
69
69
|
) {
|
|
70
70
|
const creditsUserId = await this.getCreditsUserId(userId);
|
|
71
71
|
|
|
72
|
-
// Expired subscription case
|
|
73
72
|
if (!isPremium && productId) {
|
|
74
73
|
await handleExpiredSubscription(creditsUserId);
|
|
75
74
|
return;
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
// Free user case - no Firestore document needed
|
|
79
|
-
// Credits absence means no subscription (app handles null gracefully)
|
|
80
77
|
if (!isPremium && !productId) {
|
|
81
78
|
return;
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
// Premium user case - productId is required
|
|
85
81
|
if (!productId) {
|
|
86
82
|
return;
|
|
87
83
|
}
|
|
@@ -22,9 +22,9 @@ export class SubscriptionSyncService {
|
|
|
22
22
|
console.error('[SubscriptionSyncService] Purchase processing failed', {
|
|
23
23
|
userId,
|
|
24
24
|
productId,
|
|
25
|
-
|
|
26
|
-
error
|
|
25
|
+
error: error instanceof Error ? error.message : String(error)
|
|
27
26
|
});
|
|
27
|
+
throw error;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -37,7 +37,7 @@ export class SubscriptionSyncService {
|
|
|
37
37
|
userId,
|
|
38
38
|
productId,
|
|
39
39
|
newExpirationDate,
|
|
40
|
-
error
|
|
40
|
+
error: error instanceof Error ? error.message : String(error)
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -58,12 +58,8 @@ export class SubscriptionSyncService {
|
|
|
58
58
|
userId,
|
|
59
59
|
isPremium,
|
|
60
60
|
productId,
|
|
61
|
-
|
|
62
|
-
willRenew,
|
|
63
|
-
periodType,
|
|
64
|
-
error
|
|
61
|
+
error: error instanceof Error ? error.message : String(error)
|
|
65
62
|
});
|
|
66
63
|
}
|
|
67
64
|
}
|
|
68
65
|
}
|
|
69
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { RevenueCatData } from "../../revenuecat/core/types";
|
|
3
|
-
import { PERIOD_TYPE, type PeriodType } from "../core/
|
|
3
|
+
import { PERIOD_TYPE, type PeriodType } from "../core/SubscriptionConstants";
|
|
4
4
|
|
|
5
5
|
function validatePeriodType(periodType: string | undefined): PeriodType | null {
|
|
6
6
|
if (!periodType) return null;
|