@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.37.
|
|
3
|
+
"version": "2.37.40",
|
|
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,20 +1,14 @@
|
|
|
1
1
|
import type { CreditsConfig } from "../core/Credits";
|
|
2
2
|
import { detectPackageType } from "../../../utils/packageTypeDetector";
|
|
3
3
|
import { getCreditAllocation } from "../../../utils/creditMapper";
|
|
4
|
-
import { NO_SUBSCRIPTION_PRODUCT_ID } from "../../subscription/application/syncConstants";
|
|
5
4
|
|
|
6
5
|
export function calculateCreditLimit(productId: string | undefined, config: CreditsConfig): number {
|
|
7
6
|
if (!productId) {
|
|
8
7
|
throw new Error("[CreditLimitCalculator] Cannot calculate credit limit without productId");
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
// Free tier users (no subscription) get 0 credits - strict paywall
|
|
12
|
-
if (productId === NO_SUBSCRIPTION_PRODUCT_ID) {
|
|
13
|
-
return 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
10
|
const explicitAmount = config.creditPackageAmounts?.[productId];
|
|
17
|
-
if (explicitAmount) return explicitAmount;
|
|
11
|
+
if (explicitAmount !== undefined && explicitAmount !== null) return explicitAmount;
|
|
18
12
|
|
|
19
13
|
const packageType = detectPackageType(productId);
|
|
20
14
|
const dynamicLimit = getCreditAllocation(packageType, config.packageAllocations);
|
|
@@ -25,5 +19,3 @@ export function calculateCreditLimit(productId: string | undefined, config: Cred
|
|
|
25
19
|
|
|
26
20
|
return dynamicLimit;
|
|
27
21
|
}
|
|
28
|
-
|
|
29
|
-
|
|
@@ -4,7 +4,7 @@ import { getAppVersion, validatePlatform } from "../../../utils/appUtils";
|
|
|
4
4
|
import type { InitializeCreditsMetadata, InitializationResult } from "../../subscription/application/SubscriptionInitializerTypes";
|
|
5
5
|
import { runTransaction, type Transaction, type DocumentReference, type Firestore } from "@umituz/react-native-firebase";
|
|
6
6
|
import { getCreditDocumentOrDefault } from "./creditDocumentHelpers";
|
|
7
|
-
import { calculateNewCredits, buildCreditsData
|
|
7
|
+
import { calculateNewCredits, buildCreditsData } from "./creditOperationUtils";
|
|
8
8
|
import { calculateCreditLimit } from "./CreditLimitCalculator";
|
|
9
9
|
import { generatePurchaseMetadata } from "./PurchaseMetadataGenerator";
|
|
10
10
|
import { PURCHASE_ID_PREFIXES } from "../core/CreditsConstants";
|
|
@@ -17,23 +17,16 @@ export async function initializeCreditsTransaction(
|
|
|
17
17
|
metadata: InitializeCreditsMetadata
|
|
18
18
|
): Promise<InitializationResult> {
|
|
19
19
|
|
|
20
|
+
if (!purchaseId.startsWith(PURCHASE_ID_PREFIXES.PURCHASE) && !purchaseId.startsWith(PURCHASE_ID_PREFIXES.RENEWAL)) {
|
|
21
|
+
throw new Error(`[CreditsInitializer] Only purchase and renewal operations can allocate credits. Received: ${purchaseId}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
const platform = validatePlatform();
|
|
21
25
|
const appVersion = getAppVersion();
|
|
22
26
|
|
|
23
27
|
return runTransaction(async (transaction: Transaction) => {
|
|
24
28
|
const creditsDoc = await transaction.get(creditsRef);
|
|
25
29
|
|
|
26
|
-
// Status sync must NEVER create new documents.
|
|
27
|
-
// Credits documents are only created by purchase/renewal flows.
|
|
28
|
-
const isStatusSync = purchaseId.startsWith(PURCHASE_ID_PREFIXES.STATUS_SYNC);
|
|
29
|
-
if (isStatusSync && !creditsDoc.exists()) {
|
|
30
|
-
return {
|
|
31
|
-
credits: 0,
|
|
32
|
-
alreadyProcessed: true,
|
|
33
|
-
finalData: null
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
const existingData = getCreditDocumentOrDefault(creditsDoc, platform);
|
|
38
31
|
|
|
39
32
|
if (existingData.processedPurchases.includes(purchaseId)) {
|
|
@@ -71,14 +64,6 @@ export async function initializeCreditsTransaction(
|
|
|
71
64
|
platform,
|
|
72
65
|
});
|
|
73
66
|
|
|
74
|
-
if (shouldSkipStatusSyncWrite(purchaseId, existingData, creditsData)) {
|
|
75
|
-
return {
|
|
76
|
-
credits: existingData.credits,
|
|
77
|
-
alreadyProcessed: true,
|
|
78
|
-
finalData: existingData
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
67
|
transaction.set(creditsRef, creditsData, { merge: true });
|
|
83
68
|
|
|
84
69
|
return {
|
|
@@ -3,10 +3,6 @@ import type { DeductCreditsResult } from "../core/Credits";
|
|
|
3
3
|
import { CREDIT_ERROR_CODES } from "../core/CreditsConstants";
|
|
4
4
|
import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Deducts credits from a user's balance.
|
|
8
|
-
* Encapsulates the domain rules and transaction logic for credit usage.
|
|
9
|
-
*/
|
|
10
6
|
export async function deductCreditsOperation(
|
|
11
7
|
_db: Firestore,
|
|
12
8
|
creditsRef: DocumentReference,
|
|
@@ -24,6 +20,17 @@ export async function deductCreditsOperation(
|
|
|
24
20
|
};
|
|
25
21
|
}
|
|
26
22
|
|
|
23
|
+
if (cost <= 0 || !Number.isFinite(cost)) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
remainingCredits: null,
|
|
27
|
+
error: {
|
|
28
|
+
message: 'Cost must be a positive finite number',
|
|
29
|
+
code: 'INVALID_AMOUNT'
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
try {
|
|
28
35
|
const remaining = await runTransaction(async (tx: Transaction) => {
|
|
29
36
|
const docSnap = await tx.get(creditsRef);
|
|
@@ -55,8 +62,8 @@ export async function deductCreditsOperation(
|
|
|
55
62
|
};
|
|
56
63
|
} catch (e: unknown) {
|
|
57
64
|
const message = e instanceof Error ? e.message : String(e);
|
|
58
|
-
const code = (message === CREDIT_ERROR_CODES.NO_CREDITS || message === CREDIT_ERROR_CODES.CREDITS_EXHAUSTED)
|
|
59
|
-
? message
|
|
65
|
+
const code = (message === CREDIT_ERROR_CODES.NO_CREDITS || message === CREDIT_ERROR_CODES.CREDITS_EXHAUSTED)
|
|
66
|
+
? message
|
|
60
67
|
: CREDIT_ERROR_CODES.DEDUCT_ERR;
|
|
61
68
|
|
|
62
69
|
return {
|
|
@@ -3,10 +3,6 @@ import type { DeductCreditsResult } from "../core/Credits";
|
|
|
3
3
|
import { CREDIT_ERROR_CODES } from "../core/CreditsConstants";
|
|
4
4
|
import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Refunds credits to a user's balance.
|
|
8
|
-
* Used for optimistic billing rollbacks when generation fails due to transient errors.
|
|
9
|
-
*/
|
|
10
6
|
export async function refundCreditsOperation(
|
|
11
7
|
_db: Firestore,
|
|
12
8
|
creditsRef: DocumentReference,
|
|
@@ -24,7 +20,7 @@ export async function refundCreditsOperation(
|
|
|
24
20
|
};
|
|
25
21
|
}
|
|
26
22
|
|
|
27
|
-
if (amount <= 0) {
|
|
23
|
+
if (amount <= 0 || !Number.isFinite(amount)) {
|
|
28
24
|
return {
|
|
29
25
|
success: false,
|
|
30
26
|
remainingCredits: null,
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import type { ICreditStrategy, CreditAllocationParams } from "./ICreditStrategy";
|
|
2
|
-
import { SyncCreditStrategy } from "./SyncCreditStrategy";
|
|
3
2
|
import { TrialCreditStrategy } from "./TrialCreditStrategy";
|
|
4
3
|
import { StandardPurchaseCreditStrategy } from "./StandardPurchaseCreditStrategy";
|
|
5
4
|
|
|
6
|
-
/**
|
|
7
|
-
* Orchestrator to coordinate credit allocation logic using the Strategy Pattern.
|
|
8
|
-
*/
|
|
9
5
|
class CreditAllocationOrchestrator {
|
|
10
6
|
private strategies: ICreditStrategy[] = [
|
|
11
|
-
new SyncCreditStrategy(),
|
|
12
7
|
new TrialCreditStrategy(),
|
|
13
|
-
new StandardPurchaseCreditStrategy(),
|
|
8
|
+
new StandardPurchaseCreditStrategy(),
|
|
14
9
|
];
|
|
15
10
|
|
|
16
|
-
/**
|
|
17
|
-
* Finds the first applicable strategy and executes its logic.
|
|
18
|
-
*/
|
|
19
11
|
allocate(params: CreditAllocationParams): number {
|
|
20
12
|
const strategy = this.strategies.find(s => s.canHandle(params));
|
|
21
13
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { SubscriptionStatusType } from "../../../subscription/core/SubscriptionStatus";
|
|
2
2
|
import type { UserCreditsDocumentRead } from "../../core/UserCreditsDocument";
|
|
3
3
|
|
|
4
|
-
export interface
|
|
4
|
+
export interface CreditAllocationParams {
|
|
5
5
|
status: SubscriptionStatusType;
|
|
6
|
-
isStatusSync: boolean;
|
|
7
6
|
existingData: UserCreditsDocumentRead | null;
|
|
8
7
|
creditLimit: number;
|
|
9
8
|
isSubscriptionActive: boolean;
|
|
@@ -14,6 +13,3 @@ export interface ICreditStrategy {
|
|
|
14
13
|
canHandle(params: CreditAllocationParams): boolean;
|
|
15
14
|
execute(params: CreditAllocationParams): number;
|
|
16
15
|
}
|
|
17
|
-
|
|
18
|
-
// Renaming the input for clarity
|
|
19
|
-
export type CreditAllocationParams = CreditStrategyParams;
|
|
@@ -2,13 +2,9 @@ import { ICreditStrategy, type CreditAllocationParams } from "./ICreditStrategy"
|
|
|
2
2
|
import { SUBSCRIPTION_STATUS } from "../../../subscription/core/SubscriptionConstants";
|
|
3
3
|
import { TRIAL_CONFIG } from "../../../trial/core/TrialTypes";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Strategy for Trial and Trial Canceled users.
|
|
7
|
-
* Allocates credits based on trial configuration.
|
|
8
|
-
*/
|
|
9
5
|
export class TrialCreditStrategy implements ICreditStrategy {
|
|
10
6
|
canHandle(params: CreditAllocationParams): boolean {
|
|
11
|
-
return params.status === SUBSCRIPTION_STATUS.TRIAL ||
|
|
7
|
+
return params.status === SUBSCRIPTION_STATUS.TRIAL ||
|
|
12
8
|
params.status === SUBSCRIPTION_STATUS.TRIAL_CANCELED;
|
|
13
9
|
}
|
|
14
10
|
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credit Document Helpers
|
|
3
|
-
* Utilities for getting and creating credit documents
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
|
|
7
2
|
import { serverTimestamp, type DocumentSnapshot } from "@umituz/react-native-firebase";
|
|
8
3
|
import { SUBSCRIPTION_STATUS, type Platform } from "../../subscription/core/SubscriptionConstants";
|
|
9
|
-
|
|
10
|
-
* Get existing credit document or create default
|
|
11
|
-
*/
|
|
4
|
+
|
|
12
5
|
export function getCreditDocumentOrDefault(
|
|
13
6
|
creditsDoc: DocumentSnapshot,
|
|
14
7
|
platform: Platform
|
|
@@ -17,7 +10,7 @@ export function getCreditDocumentOrDefault(
|
|
|
17
10
|
return creditsDoc.data() as UserCreditsDocumentRead;
|
|
18
11
|
}
|
|
19
12
|
|
|
20
|
-
const now = serverTimestamp() as any;
|
|
13
|
+
const now = serverTimestamp() as any;
|
|
21
14
|
|
|
22
15
|
const defaultDocument: UserCreditsDocumentRead = {
|
|
23
16
|
credits: 0,
|
|
@@ -23,7 +23,6 @@ export function calculateNewCredits({ metadata, existingData, creditLimit, purch
|
|
|
23
23
|
|
|
24
24
|
return creditAllocationOrchestrator.allocate({
|
|
25
25
|
status,
|
|
26
|
-
isStatusSync: purchaseId.startsWith(PURCHASE_ID_PREFIXES.STATUS_SYNC),
|
|
27
26
|
existingData,
|
|
28
27
|
creditLimit,
|
|
29
28
|
isSubscriptionActive: isPremium && !isExpired,
|
|
@@ -49,7 +48,7 @@ export function buildCreditsData({
|
|
|
49
48
|
periodType: metadata.periodType ?? undefined,
|
|
50
49
|
});
|
|
51
50
|
|
|
52
|
-
const isPurchaseOrRenewal = purchaseId.startsWith(PURCHASE_ID_PREFIXES.PURCHASE) ||
|
|
51
|
+
const isPurchaseOrRenewal = purchaseId.startsWith(PURCHASE_ID_PREFIXES.PURCHASE) ||
|
|
53
52
|
purchaseId.startsWith(PURCHASE_ID_PREFIXES.RENEWAL);
|
|
54
53
|
|
|
55
54
|
const expirationTimestamp = metadata.expirationDate ? toTimestamp(metadata.expirationDate) : null;
|
|
@@ -76,44 +75,3 @@ export function buildCreditsData({
|
|
|
76
75
|
...(metadata.ownershipType && { ownershipType: metadata.ownershipType }),
|
|
77
76
|
};
|
|
78
77
|
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Compare two Firestore Timestamp-like values by their underlying time.
|
|
82
|
-
* Handles Timestamp objects (with toMillis/seconds+nanoseconds), null, and undefined.
|
|
83
|
-
*/
|
|
84
|
-
function timestampsEqual(a: unknown, b: unknown): boolean {
|
|
85
|
-
if (a === b) return true;
|
|
86
|
-
if (a == null || b == null) return a == b;
|
|
87
|
-
if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
|
|
88
|
-
if ("toMillis" in a && "toMillis" in b &&
|
|
89
|
-
typeof (a as { toMillis: unknown }).toMillis === "function" &&
|
|
90
|
-
typeof (b as { toMillis: unknown }).toMillis === "function") {
|
|
91
|
-
return (a as { toMillis: () => number }).toMillis() === (b as { toMillis: () => number }).toMillis();
|
|
92
|
-
}
|
|
93
|
-
if ("seconds" in a && "seconds" in b && "nanoseconds" in a && "nanoseconds" in b) {
|
|
94
|
-
return (a as { seconds: number; nanoseconds: number }).seconds === (b as { seconds: number; nanoseconds: number }).seconds &&
|
|
95
|
-
(a as { seconds: number; nanoseconds: number }).nanoseconds === (b as { seconds: number; nanoseconds: number }).nanoseconds;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function shouldSkipStatusSyncWrite(
|
|
102
|
-
purchaseId: string,
|
|
103
|
-
existingData: any,
|
|
104
|
-
newCreditsData: Record<string, any>
|
|
105
|
-
): boolean {
|
|
106
|
-
if (!purchaseId.startsWith(PURCHASE_ID_PREFIXES.STATUS_SYNC)) return false;
|
|
107
|
-
|
|
108
|
-
if (!existingData || !newCreditsData) return false;
|
|
109
|
-
|
|
110
|
-
return existingData.isPremium === newCreditsData.isPremium &&
|
|
111
|
-
existingData.status === newCreditsData.status &&
|
|
112
|
-
existingData.credits === newCreditsData.credits &&
|
|
113
|
-
existingData.creditLimit === newCreditsData.creditLimit &&
|
|
114
|
-
existingData.productId === newCreditsData.productId &&
|
|
115
|
-
existingData.willRenew === newCreditsData.willRenew &&
|
|
116
|
-
timestampsEqual(existingData.expirationDate, newCreditsData.expirationDate) &&
|
|
117
|
-
timestampsEqual(existingData.canceledAt, newCreditsData.canceledAt) &&
|
|
118
|
-
timestampsEqual(existingData.billingIssueDetectedAt, newCreditsData.billingIssueDetectedAt);
|
|
119
|
-
}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credits Domain Entities
|
|
3
|
-
*
|
|
4
|
-
* Generic credit system types for subscription-based apps.
|
|
5
|
-
* Designed to be used across hundreds of apps with configurable limits.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import type { SubscriptionPackageType } from "../../../utils/packageTypeDetector";
|
|
9
|
-
// Types imported from SubscriptionConstants are used directly in UserCredits interface
|
|
10
2
|
import type {
|
|
11
3
|
SubscriptionStatusType,
|
|
12
4
|
PackageType,
|
|
@@ -17,37 +9,25 @@ import type {
|
|
|
17
9
|
|
|
18
10
|
export type CreditType = "text" | "image";
|
|
19
11
|
|
|
20
|
-
/** Single Source of Truth for user subscription + credits data */
|
|
21
12
|
export interface UserCredits {
|
|
22
|
-
// Core subscription
|
|
23
13
|
isPremium: boolean;
|
|
24
14
|
status: SubscriptionStatusType;
|
|
25
|
-
|
|
26
|
-
// Dates
|
|
27
15
|
purchasedAt: Date | null;
|
|
28
16
|
expirationDate: Date | null;
|
|
29
17
|
lastUpdatedAt: Date | null;
|
|
30
18
|
lastPurchaseAt: Date | null;
|
|
31
|
-
|
|
32
|
-
// RevenueCat subscription details
|
|
33
19
|
willRenew: boolean | null;
|
|
34
20
|
productId: string | null;
|
|
35
21
|
packageType: PackageType | null;
|
|
36
22
|
originalTransactionId: string | null;
|
|
37
|
-
|
|
38
|
-
// Trial fields - periodType comes from RevenueCat SDK
|
|
39
23
|
periodType: string | null;
|
|
40
24
|
isTrialing: boolean | null;
|
|
41
25
|
trialStartDate: Date | null;
|
|
42
26
|
trialEndDate: Date | null;
|
|
43
27
|
trialCredits: number | null;
|
|
44
28
|
convertedFromTrial: boolean | null;
|
|
45
|
-
|
|
46
|
-
// Credits
|
|
47
29
|
credits: number;
|
|
48
30
|
creditLimit: number;
|
|
49
|
-
|
|
50
|
-
// Metadata
|
|
51
31
|
purchaseSource: PurchaseSource | null;
|
|
52
32
|
purchaseType: PurchaseType | null;
|
|
53
33
|
platform: Platform;
|
|
@@ -66,11 +46,8 @@ export type PackageAllocationMap = Partial<Record<
|
|
|
66
46
|
export interface CreditsConfig {
|
|
67
47
|
collectionName: string;
|
|
68
48
|
creditLimit: number;
|
|
69
|
-
/** When true, stores credits at users/{userId}/credits instead of {collectionName}/{userId} */
|
|
70
49
|
useUserSubcollection: boolean;
|
|
71
|
-
/** Credit amounts per product ID for consumable credit packages */
|
|
72
50
|
creditPackageAmounts: Record<string, number>;
|
|
73
|
-
/** Credit allocations for different subscription types (weekly, monthly, yearly) */
|
|
74
51
|
packageAllocations: PackageAllocationMap;
|
|
75
52
|
}
|
|
76
53
|
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credit Error Codes
|
|
3
|
-
*/
|
|
4
1
|
export const CREDIT_ERROR_CODES = {
|
|
5
2
|
NO_CREDITS: 'NO_CREDITS',
|
|
6
3
|
CREDITS_EXHAUSTED: 'CREDITS_EXHAUSTED',
|
|
@@ -8,17 +5,9 @@ export const CREDIT_ERROR_CODES = {
|
|
|
8
5
|
DB_ERROR: 'ERR',
|
|
9
6
|
} as const;
|
|
10
7
|
|
|
11
|
-
/**
|
|
12
|
-
* Purchase ID Prefixes
|
|
13
|
-
*/
|
|
14
8
|
export const PURCHASE_ID_PREFIXES = {
|
|
15
|
-
STATUS_SYNC: 'status_sync_',
|
|
16
9
|
PURCHASE: 'purchase_',
|
|
17
10
|
RENEWAL: 'renewal_',
|
|
18
11
|
} as const;
|
|
19
12
|
|
|
20
|
-
/**
|
|
21
|
-
* Processed Purchases Array Window Size
|
|
22
|
-
* Maintains last N purchases to prevent reprocessing
|
|
23
|
-
*/
|
|
24
13
|
export const PROCESSED_PURCHASES_WINDOW = 50;
|
|
@@ -3,9 +3,6 @@ import { resolveSubscriptionStatus, type SubscriptionStatusType } from "../../su
|
|
|
3
3
|
import type { UserCreditsDocumentRead } from "./UserCreditsDocument";
|
|
4
4
|
import { toSafeDate } from "../../../utils/dateUtils";
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Validate subscription status against expirationDate and periodType
|
|
8
|
-
*/
|
|
9
6
|
function validateSubscription(
|
|
10
7
|
doc: UserCreditsDocumentRead,
|
|
11
8
|
expirationDate: Date | null,
|
|
@@ -28,9 +25,6 @@ function validateSubscription(
|
|
|
28
25
|
};
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
/**
|
|
32
|
-
* Maps Firestore document to domain entity with expiration validation
|
|
33
|
-
*/
|
|
34
28
|
export function mapCreditsDocumentToEntity(doc: UserCreditsDocumentRead): UserCredits {
|
|
35
29
|
const expirationDate = toSafeDate(doc.expirationDate);
|
|
36
30
|
const periodType = doc.periodType;
|
|
@@ -30,41 +30,29 @@ export interface PurchaseMetadata {
|
|
|
30
30
|
timestamp: FirestoreTimestamp;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** Single Source of Truth for user subscription data */
|
|
34
33
|
export interface UserCreditsDocumentRead {
|
|
35
|
-
// Core subscription status
|
|
36
34
|
isPremium: boolean;
|
|
37
35
|
status: SubscriptionStatusType;
|
|
38
|
-
|
|
39
|
-
// Dates (all from RevenueCat)
|
|
40
36
|
purchasedAt: FirestoreTimestamp;
|
|
41
37
|
expirationDate: FirestoreTimestamp | null;
|
|
42
38
|
lastUpdatedAt: FirestoreTimestamp;
|
|
43
39
|
lastPurchaseAt: FirestoreTimestamp | null;
|
|
44
40
|
canceledAt: FirestoreTimestamp | null;
|
|
45
41
|
billingIssueDetectedAt: FirestoreTimestamp | null;
|
|
46
|
-
|
|
47
|
-
// RevenueCat subscription details
|
|
48
42
|
willRenew: boolean | null;
|
|
49
43
|
productId: string | null;
|
|
50
44
|
packageType: PackageType | null;
|
|
51
45
|
originalTransactionId: string | null;
|
|
52
46
|
store: Store | null;
|
|
53
47
|
ownershipType: OwnershipType | null;
|
|
54
|
-
|
|
55
|
-
// Trial fields
|
|
56
48
|
periodType: string | null;
|
|
57
49
|
isTrialing: boolean | null;
|
|
58
50
|
trialStartDate: FirestoreTimestamp | null;
|
|
59
51
|
trialEndDate: FirestoreTimestamp | null;
|
|
60
52
|
trialCredits: number | null;
|
|
61
53
|
convertedFromTrial: boolean | null;
|
|
62
|
-
|
|
63
|
-
// Credits
|
|
64
54
|
credits: number;
|
|
65
55
|
creditLimit: number;
|
|
66
|
-
|
|
67
|
-
// Metadata
|
|
68
56
|
purchaseSource: PurchaseSource | null;
|
|
69
57
|
purchaseType: PurchaseType | null;
|
|
70
58
|
platform: Platform;
|
|
@@ -8,7 +8,7 @@ import { refundCreditsOperation } from "../application/RefundCreditsCommand";
|
|
|
8
8
|
import { PURCHASE_TYPE, type PurchaseType } from "../../subscription/core/SubscriptionConstants";
|
|
9
9
|
import { requireFirestore, buildDocRef, type CollectionConfig } from "../../../shared/infrastructure/firestore";
|
|
10
10
|
import { fetchCredits, checkHasCredits } from "./operations/CreditsFetcher";
|
|
11
|
-
import { syncExpiredStatus } from "./operations/CreditsWriter";
|
|
11
|
+
import { syncExpiredStatus, syncPremiumMetadata, type PremiumMetadata } from "./operations/CreditsWriter";
|
|
12
12
|
import { initializeCreditsWithRetry } from "./operations/CreditsInitializer";
|
|
13
13
|
|
|
14
14
|
export class CreditsRepository extends BaseRepository {
|
|
@@ -74,6 +74,11 @@ export class CreditsRepository extends BaseRepository {
|
|
|
74
74
|
const db = requireFirestore();
|
|
75
75
|
await syncExpiredStatus(this.getRef(db, userId));
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
async syncPremiumMetadata(userId: string, metadata: PremiumMetadata): Promise<void> {
|
|
79
|
+
const db = requireFirestore();
|
|
80
|
+
await syncPremiumMetadata(this.getRef(db, userId), metadata);
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
export function createCreditsRepository(config: CreditsConfig): CreditsRepository {
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credits Repository Manager
|
|
3
|
-
* Module-level singleton for credits repository configuration
|
|
4
|
-
* Provides a clean, testable approach for repository access
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import type { CreditsConfig } from "../core/Credits";
|
|
8
2
|
import type { CreditsRepository } from "./CreditsRepository";
|
|
9
3
|
import { createCreditsRepository } from "./CreditsRepository";
|
|
@@ -11,26 +5,15 @@ import { createCreditsRepository } from "./CreditsRepository";
|
|
|
11
5
|
let globalRepository: CreditsRepository | null = null;
|
|
12
6
|
let globalConfig: CreditsConfig | null = null;
|
|
13
7
|
|
|
14
|
-
/**
|
|
15
|
-
* Configure credits repository for the application
|
|
16
|
-
* Must be called once during app initialization
|
|
17
|
-
*/
|
|
18
8
|
export function configureCreditsRepository(config: CreditsConfig): void {
|
|
19
9
|
globalConfig = config;
|
|
20
10
|
globalRepository = createCreditsRepository(globalConfig);
|
|
21
11
|
}
|
|
22
12
|
|
|
23
|
-
/**
|
|
24
|
-
* Check if credits repository is configured
|
|
25
|
-
*/
|
|
26
13
|
export function isCreditsRepositoryConfigured(): boolean {
|
|
27
14
|
return globalRepository !== null;
|
|
28
15
|
}
|
|
29
16
|
|
|
30
|
-
/**
|
|
31
|
-
* Get the configured credits repository
|
|
32
|
-
* Throws if repository not configured
|
|
33
|
-
*/
|
|
34
17
|
export function getCreditsRepository(): CreditsRepository {
|
|
35
18
|
if (!globalRepository) {
|
|
36
19
|
throw new Error(
|
|
@@ -40,9 +23,6 @@ export function getCreditsRepository(): CreditsRepository {
|
|
|
40
23
|
return globalRepository;
|
|
41
24
|
}
|
|
42
25
|
|
|
43
|
-
/**
|
|
44
|
-
* Get the current credits configuration
|
|
45
|
-
*/
|
|
46
26
|
export function getCreditsConfig(): CreditsConfig {
|
|
47
27
|
if (!globalConfig) {
|
|
48
28
|
throw new Error(
|
|
@@ -51,4 +31,3 @@ export function getCreditsConfig(): CreditsConfig {
|
|
|
51
31
|
}
|
|
52
32
|
return globalConfig;
|
|
53
33
|
}
|
|
54
|
-
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { setDoc } from "firebase/firestore";
|
|
1
|
+
import { getDoc, setDoc } from "firebase/firestore";
|
|
2
2
|
import type { DocumentReference } from "@umituz/react-native-firebase";
|
|
3
3
|
import { serverTimestamp } from "@umituz/react-native-firebase";
|
|
4
4
|
import { SUBSCRIPTION_STATUS } from "../../../subscription/core/SubscriptionConstants";
|
|
5
|
+
import { resolveSubscriptionStatus } from "../../../subscription/core/SubscriptionStatus";
|
|
6
|
+
import { toTimestamp } from "../../../../shared/utils/dateConverter";
|
|
7
|
+
import { isPast } from "../../../../utils/dateUtils";
|
|
5
8
|
|
|
6
9
|
export async function syncExpiredStatus(ref: DocumentReference): Promise<void> {
|
|
10
|
+
const doc = await getDoc(ref);
|
|
11
|
+
if (!doc.exists()) return;
|
|
12
|
+
|
|
7
13
|
await setDoc(ref, {
|
|
8
14
|
isPremium: false,
|
|
9
15
|
status: SUBSCRIPTION_STATUS.EXPIRED,
|
|
@@ -11,3 +17,48 @@ export async function syncExpiredStatus(ref: DocumentReference): Promise<void> {
|
|
|
11
17
|
lastUpdatedAt: serverTimestamp(),
|
|
12
18
|
}, { merge: true });
|
|
13
19
|
}
|
|
20
|
+
|
|
21
|
+
export interface PremiumMetadata {
|
|
22
|
+
isPremium: boolean;
|
|
23
|
+
willRenew: boolean;
|
|
24
|
+
expirationDate: string | null;
|
|
25
|
+
productId: string;
|
|
26
|
+
periodType: string | null;
|
|
27
|
+
unsubscribeDetectedAt: string | null;
|
|
28
|
+
billingIssueDetectedAt: string | null;
|
|
29
|
+
store: string | null;
|
|
30
|
+
ownershipType: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function syncPremiumMetadata(
|
|
34
|
+
ref: DocumentReference,
|
|
35
|
+
metadata: PremiumMetadata
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const doc = await getDoc(ref);
|
|
38
|
+
if (!doc.exists()) return;
|
|
39
|
+
|
|
40
|
+
const isExpired = metadata.expirationDate ? isPast(metadata.expirationDate) : false;
|
|
41
|
+
const status = resolveSubscriptionStatus({
|
|
42
|
+
isPremium: metadata.isPremium,
|
|
43
|
+
willRenew: metadata.willRenew,
|
|
44
|
+
isExpired,
|
|
45
|
+
periodType: metadata.periodType ?? undefined,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const expirationTimestamp = metadata.expirationDate ? toTimestamp(metadata.expirationDate) : null;
|
|
49
|
+
const canceledAtTimestamp = metadata.unsubscribeDetectedAt ? toTimestamp(metadata.unsubscribeDetectedAt) : null;
|
|
50
|
+
const billingIssueTimestamp = metadata.billingIssueDetectedAt ? toTimestamp(metadata.billingIssueDetectedAt) : null;
|
|
51
|
+
|
|
52
|
+
await setDoc(ref, {
|
|
53
|
+
isPremium: metadata.isPremium,
|
|
54
|
+
status,
|
|
55
|
+
willRenew: metadata.willRenew,
|
|
56
|
+
productId: metadata.productId,
|
|
57
|
+
lastUpdatedAt: serverTimestamp(),
|
|
58
|
+
...(expirationTimestamp && { expirationDate: expirationTimestamp }),
|
|
59
|
+
...(canceledAtTimestamp && { canceledAt: canceledAtTimestamp }),
|
|
60
|
+
...(billingIssueTimestamp && { billingIssueDetectedAt: billingIssueTimestamp }),
|
|
61
|
+
...(metadata.store && { store: metadata.store }),
|
|
62
|
+
...(metadata.ownershipType && { ownershipType: metadata.ownershipType }),
|
|
63
|
+
}, { merge: true });
|
|
64
|
+
}
|
|
@@ -35,9 +35,9 @@ export const useDeductCredit = ({
|
|
|
35
35
|
console.error('[useDeductCredit] Unexpected error during credit deduction', {
|
|
36
36
|
cost,
|
|
37
37
|
userId,
|
|
38
|
-
error
|
|
38
|
+
error: error instanceof Error ? error.message : String(error)
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
return false;
|
|
41
41
|
}
|
|
42
42
|
}, [onCreditsExhausted, userId]);
|
|
43
43
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useQuery, useQueryClient } from "@umituz/react-native-design-system";
|
|
2
2
|
import { useCallback, useMemo, useEffect } from "react";
|
|
3
|
-
import { useAuthStore, selectUserId } from "@umituz/react-native-auth";
|
|
3
|
+
import { useAuthStore, selectUserId, selectIsAnonymous } from "@umituz/react-native-auth";
|
|
4
4
|
import { subscriptionEventBus, SUBSCRIPTION_EVENTS } from "../../../shared/infrastructure/SubscriptionEventBus";
|
|
5
5
|
import { NO_CACHE_QUERY_CONFIG } from "../../../shared/infrastructure/react-query/queryConfig";
|
|
6
6
|
import { usePreviousUserCleanup } from "../../../shared/infrastructure/react-query/hooks/usePreviousUserCleanup";
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
isCreditsRepositoryConfigured,
|
|
11
11
|
} from "../infrastructure/CreditsRepositoryManager";
|
|
12
12
|
import { calculateSafePercentage, canAffordAmount } from "../utils/creditValidation";
|
|
13
|
-
import {
|
|
13
|
+
import { isRegisteredUser } from "../../subscription/utils/authGuards";
|
|
14
14
|
import { creditsQueryKeys } from "./creditsQueryKeys";
|
|
15
15
|
import type { UseCreditsResult, CreditsLoadStatus } from "./useCredits.types";
|
|
16
16
|
|
|
@@ -26,15 +26,17 @@ const deriveLoadStatus = (
|
|
|
26
26
|
|
|
27
27
|
export const useCredits = (): UseCreditsResult => {
|
|
28
28
|
const userId = useAuthStore(selectUserId);
|
|
29
|
+
const isAnonymous = useAuthStore(selectIsAnonymous);
|
|
29
30
|
const isConfigured = isCreditsRepositoryConfigured();
|
|
30
31
|
|
|
31
32
|
const config = isConfigured ? getCreditsConfig() : null;
|
|
32
|
-
const
|
|
33
|
+
const isUserRegistered = isRegisteredUser(userId, isAnonymous);
|
|
34
|
+
const queryEnabled = isUserRegistered && isConfigured;
|
|
33
35
|
|
|
34
36
|
const { data, status, error, refetch } = useQuery({
|
|
35
37
|
queryKey: creditsQueryKeys.user(userId),
|
|
36
38
|
queryFn: async () => {
|
|
37
|
-
if (!
|
|
39
|
+
if (!isUserRegistered || !isConfigured) return null;
|
|
38
40
|
|
|
39
41
|
const repository = getCreditsRepository();
|
|
40
42
|
const result = await repository.getCredits(userId);
|
|
@@ -51,11 +53,10 @@ export const useCredits = (): UseCreditsResult => {
|
|
|
51
53
|
|
|
52
54
|
const queryClient = useQueryClient();
|
|
53
55
|
|
|
54
|
-
// Clean up previous user's cache on logout/user switch
|
|
55
56
|
usePreviousUserCleanup(userId, queryClient, creditsQueryKeys.user);
|
|
56
57
|
|
|
57
58
|
useEffect(() => {
|
|
58
|
-
if (!
|
|
59
|
+
if (!isUserRegistered) return undefined;
|
|
59
60
|
|
|
60
61
|
const unsubscribe = subscriptionEventBus.on(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, (updatedUserId) => {
|
|
61
62
|
if (updatedUserId === userId) {
|
|
@@ -64,13 +65,13 @@ export const useCredits = (): UseCreditsResult => {
|
|
|
64
65
|
});
|
|
65
66
|
|
|
66
67
|
return unsubscribe;
|
|
67
|
-
}, [userId, queryClient]);
|
|
68
|
+
}, [userId, isUserRegistered, queryClient]);
|
|
68
69
|
|
|
69
70
|
const credits = data ?? null;
|
|
70
71
|
|
|
71
72
|
const derivedValues = useMemo(() => {
|
|
72
73
|
const has = (credits?.credits ?? 0) > 0;
|
|
73
|
-
const limit = config?.creditLimit ?? 0;
|
|
74
|
+
const limit = credits?.creditLimit ?? config?.creditLimit ?? 0;
|
|
74
75
|
const percent = calculateSafePercentage(credits?.credits, limit);
|
|
75
76
|
return { hasCredits: has, creditsPercent: percent };
|
|
76
77
|
}, [credits, config?.creditLimit]);
|
|
@@ -95,4 +96,4 @@ export const useCredits = (): UseCreditsResult => {
|
|
|
95
96
|
refetch,
|
|
96
97
|
canAfford,
|
|
97
98
|
};
|
|
98
|
-
};
|
|
99
|
+
};
|