@umituz/react-native-subscription 2.27.112 → 2.27.114

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/CreditsInitializer.ts +28 -125
  3. package/src/domains/credits/application/credit-strategies/{CreditAllocationContext.ts → CreditAllocationOrchestrator.ts} +4 -9
  4. package/src/domains/credits/application/creditDocumentHelpers.ts +58 -0
  5. package/src/domains/credits/application/creditOperationUtils.ts +154 -0
  6. package/src/domains/credits/core/CreditsMapper.ts +8 -13
  7. package/src/domains/credits/infrastructure/{CreditsRepositoryProvider.ts → CreditsRepositoryManager.ts} +2 -2
  8. package/src/domains/credits/presentation/useCredits.ts +2 -3
  9. package/src/domains/credits/presentation/useDeductCredit.ts +4 -4
  10. package/src/domains/paywall/components/PaywallContainer.types.ts +1 -1
  11. package/src/domains/paywall/components/PaywallModal.tsx +28 -52
  12. package/src/domains/paywall/hooks/usePaywallActions.ts +77 -33
  13. package/src/domains/subscription/application/SubscriptionInitializer.ts +1 -1
  14. package/src/domains/subscription/application/SubscriptionSyncService.ts +17 -21
  15. package/src/domains/subscription/core/RevenueCatError.ts +40 -31
  16. package/src/domains/subscription/infrastructure/handlers/PackageHandler.ts +0 -1
  17. package/src/domains/subscription/infrastructure/hooks/useRevenueCatTrialEligibility.ts +19 -85
  18. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +33 -75
  19. package/src/domains/subscription/infrastructure/managers/subscriptionManagerUtils.ts +57 -0
  20. package/src/domains/subscription/infrastructure/services/CustomerInfoListenerManager.ts +3 -12
  21. package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +0 -2
  22. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +2 -4
  23. package/src/domains/subscription/infrastructure/services/RevenueCatService.ts +1 -5
  24. package/src/domains/subscription/infrastructure/utils/PremiumStatusSyncer.ts +3 -12
  25. package/src/domains/subscription/infrastructure/utils/authPurchaseState.ts +69 -0
  26. package/src/domains/subscription/infrastructure/utils/trialEligibilityUtils.ts +77 -0
  27. package/src/domains/subscription/presentation/components/feedback/FeedbackOption.tsx +139 -0
  28. package/src/domains/subscription/presentation/components/feedback/PaywallFeedbackModal.tsx +15 -70
  29. package/src/domains/subscription/presentation/components/feedback/paywallFeedbackStyles.ts +0 -92
  30. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +1 -1
  31. package/src/domains/subscription/presentation/stores/purchaseLoadingStore.ts +1 -18
  32. package/src/domains/subscription/presentation/useAuthAwarePurchase.ts +19 -69
  33. package/src/domains/subscription/presentation/usePaywallVisibility.ts +1 -1
  34. package/src/domains/subscription/presentation/usePremium.ts +2 -11
  35. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +1 -6
  36. package/src/domains/trial/application/TrialService.ts +4 -8
  37. package/src/domains/wallet/index.ts +0 -6
  38. package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +1 -1
  39. package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +0 -13
  40. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +0 -10
  41. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +0 -8
  42. package/src/domains/wallet/presentation/screens/WalletScreen.tsx +57 -43
  43. package/src/index.ts +1 -1
  44. package/src/init/createSubscriptionInitModule.ts +1 -4
  45. package/src/presentation/hooks/feedback/useFeedbackSubmit.ts +0 -14
  46. package/src/shared/application/ActivationHandler.ts +6 -6
  47. package/src/shared/application/FeedbackService.ts +0 -21
  48. package/src/shared/infrastructure/SubscriptionEventBus.ts +1 -2
  49. package/src/shared/presentation/index.ts +1 -0
  50. package/src/shared/presentation/layouts/ScreenLayout.tsx +79 -0
  51. package/src/shared/types/CommonTypes.ts +65 -0
  52. package/src/shared/utils/BaseError.ts +26 -0
  53. package/src/shared/utils/Logger.ts +15 -46
  54. package/src/shared/utils/Result.ts +16 -0
  55. package/src/shared/utils/SubscriptionConfig.ts +1 -1
  56. package/src/shared/utils/SubscriptionError.ts +20 -30
  57. package/src/utils/appUtils.ts +34 -0
  58. package/src/utils/dateUtils.ts +32 -0
  59. package/src/utils/index.ts +2 -0
  60. package/src/utils/packageTypeDetector.ts +0 -4
  61. package/src/domains/wallet/presentation/screens/WalletScreenContainer.tsx +0 -88
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.27.112",
3
+ "version": "2.27.114",
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,24 +1,16 @@
1
- import { Platform } from "react-native";
2
- import Constants from "expo-constants";
3
- import {
4
- getFirestore,
5
- } from "@umituz/react-native-firebase";
6
- import {
7
- runTransaction,
8
- serverTimestamp,
9
- type Transaction,
10
- type DocumentReference,
11
- } from "firebase/firestore";
12
1
  import type { CreditsConfig } from "../core/Credits";
13
2
  import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
14
- import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionStatus";
3
+ import { getAppVersion, validatePlatform } from "../../../utils";
4
+ import type { InitializeCreditsMetadata, InitializationResult } from "../../subscription/application/SubscriptionInitializerTypes";
5
+ import { runTransaction, type Transaction, type DocumentReference } from "firebase/firestore";
6
+ import type { Firestore } from "firebase/firestore";
7
+ import { getCreditDocumentOrDefault } from "./creditDocumentHelpers";
8
+ import { calculateNewCredits, buildCreditsData, shouldSkipStatusSyncWrite } from "./creditOperationUtils";
15
9
  import { CreditLimitCalculator } from "./CreditLimitCalculator";
16
10
  import { PurchaseMetadataGenerator } from "./PurchaseMetadataGenerator";
17
- import { creditAllocationContext } from "./credit-strategies/CreditAllocationContext";
18
- import type { InitializeCreditsMetadata, InitializationResult } from "../../subscription/application/SubscriptionInitializerTypes";
19
11
 
20
12
  export async function initializeCreditsTransaction(
21
- db: ReturnType<typeof getFirestore>,
13
+ db: Firestore,
22
14
  creditsRef: DocumentReference,
23
15
  config: CreditsConfig,
24
16
  purchaseId: string,
@@ -30,33 +22,9 @@ export async function initializeCreditsTransaction(
30
22
 
31
23
  return runTransaction(db, async (transaction: Transaction) => {
32
24
  const creditsDoc = await transaction.get(creditsRef);
33
- const now = serverTimestamp();
34
- const existingData = creditsDoc.exists()
35
- ? creditsDoc.data() as UserCreditsDocumentRead
36
- : {
37
- credits: 0,
38
- creditLimit: 0,
39
- isPremium: false,
40
- status: "none",
41
- processedPurchases: [],
42
- purchaseHistory: [],
43
- platform: Platform.OS as any,
44
- lastUpdatedAt: now,
45
- purchasedAt: now,
46
- expirationDate: null,
47
- lastPurchaseAt: null,
48
- willRenew: false,
49
- productId: null,
50
- packageType: null,
51
- originalTransactionId: null,
52
- appVersion: null,
53
- periodType: null,
54
- isTrialing: false,
55
- trialStartDate: null,
56
- trialEndDate: null,
57
- trialCredits: 0,
58
- convertedFromTrial: false,
59
- } as any;
25
+ const platform = validatePlatform();
26
+
27
+ const existingData = getCreditDocumentOrDefault(creditsDoc, platform);
60
28
 
61
29
  if (existingData.processedPurchases.includes(purchaseId)) {
62
30
  return {
@@ -67,16 +35,7 @@ export async function initializeCreditsTransaction(
67
35
  }
68
36
 
69
37
  const creditLimit = CreditLimitCalculator.calculate(metadata.productId, config);
70
-
71
- const platform = Platform.OS;
72
- if (platform !== "ios" && platform !== "android") {
73
- throw new Error(`Invalid platform: ${platform}`);
74
- }
75
-
76
- const appVersion = Constants.expoConfig?.version;
77
- if (!appVersion) {
78
- throw new Error("appVersion is required in expoConfig");
79
- }
38
+ const appVersion = getAppVersion();
80
39
 
81
40
  const { purchaseHistory } = PurchaseMetadataGenerator.generate({
82
41
  productId: metadata.productId,
@@ -87,85 +46,29 @@ export async function initializeCreditsTransaction(
87
46
  appVersion,
88
47
  }, existingData);
89
48
 
90
- const isPremium = metadata.isPremium;
91
-
92
- let isExpired = false;
93
- if (metadata.expirationDate) {
94
- isExpired = new Date(metadata.expirationDate).getTime() < Date.now();
95
- }
96
-
97
- const status = resolveSubscriptionStatus({
98
- isPremium,
99
- willRenew: metadata.willRenew ?? false,
100
- isExpired,
101
- periodType: metadata.periodType ?? undefined,
102
- });
103
-
104
- const isStatusSync = purchaseId.startsWith("status_sync_");
105
- const isSubscriptionActive = isPremium && !isExpired;
106
-
107
- const newCredits = creditAllocationContext.allocate({
108
- status,
109
- isStatusSync,
49
+ const newCredits = calculateNewCredits({
50
+ metadata,
110
51
  existingData,
111
52
  creditLimit,
112
- isSubscriptionActive,
113
- productId: metadata.productId,
53
+ purchaseId,
114
54
  });
115
55
 
116
- const newProcessedPurchases = [...existingData.processedPurchases, purchaseId].slice(-50);
117
-
118
- const creditsData: Record<string, any> = {
119
- isPremium,
120
- status,
121
- credits: newCredits,
56
+ const creditsData = buildCreditsData({
57
+ existingData,
58
+ newCredits,
122
59
  creditLimit,
123
- lastUpdatedAt: now,
124
- processedPurchases: newProcessedPurchases,
125
- };
126
-
127
- if (purchaseHistory.length > 0) {
128
- creditsData.purchaseHistory = purchaseHistory;
129
- }
130
-
131
- const isNewPurchaseOrRenewal = purchaseId.startsWith("purchase_")
132
- || purchaseId.startsWith("renewal_");
133
-
134
- if (isNewPurchaseOrRenewal) {
135
- creditsData.lastPurchaseAt = now;
136
- }
137
-
138
- if (metadata.expirationDate) {
139
- creditsData.expirationDate = serverTimestamp();
140
- }
141
-
142
- if (metadata.willRenew !== undefined) {
143
- creditsData.willRenew = metadata.willRenew;
144
- }
145
-
146
- if (metadata.originalTransactionId) {
147
- creditsData.originalTransactionId = metadata.originalTransactionId;
148
- }
149
-
150
- creditsData.productId = metadata.productId;
151
- creditsData.platform = platform;
152
-
153
- // Skip write if it's a status sync and data hasn't changed to save costs
154
- if (isStatusSync && existingData) {
155
- const hasChanged =
156
- existingData.isPremium !== creditsData.isPremium ||
157
- existingData.status !== creditsData.status ||
158
- existingData.credits !== creditsData.credits ||
159
- existingData.creditLimit !== creditsData.creditLimit ||
160
- existingData.productId !== creditsData.productId;
60
+ purchaseId,
61
+ metadata,
62
+ purchaseHistory,
63
+ platform,
64
+ });
161
65
 
162
- if (!hasChanged) {
163
- return {
164
- credits: existingData.credits,
165
- alreadyProcessed: true,
166
- finalData: existingData
167
- };
168
- }
66
+ if (shouldSkipStatusSyncWrite(purchaseId, existingData, creditsData)) {
67
+ return {
68
+ credits: existingData.credits,
69
+ alreadyProcessed: true,
70
+ finalData: existingData
71
+ };
169
72
  }
170
73
 
171
74
  transaction.set(creditsRef, creditsData, { merge: true });
@@ -4,9 +4,9 @@ import { TrialCreditStrategy } from "./TrialCreditStrategy";
4
4
  import { StandardPurchaseCreditStrategy } from "./StandardPurchaseCreditStrategy";
5
5
 
6
6
  /**
7
- * Strategy Context to coordinate credit allocation logic using the Strategy Pattern.
7
+ * Orchestrator to coordinate credit allocation logic using the Strategy Pattern.
8
8
  */
9
- export class CreditAllocationContext {
9
+ export class CreditAllocationOrchestrator {
10
10
  private strategies: ICreditStrategy[] = [
11
11
  new SyncCreditStrategy(),
12
12
  new TrialCreditStrategy(),
@@ -18,18 +18,13 @@ export class CreditAllocationContext {
18
18
  */
19
19
  allocate(params: CreditAllocationParams): number {
20
20
  const strategy = this.strategies.find(s => s.canHandle(params));
21
-
21
+
22
22
  if (!strategy) {
23
- // Should theoretically never happen due to StandardPurchaseCreditStrategy fallback
24
23
  return params.creditLimit;
25
24
  }
26
25
 
27
- if (__DEV__) {
28
- console.log(`[CreditAllocationContext] Using strategy: ${strategy.constructor.name}`);
29
- }
30
-
31
26
  return strategy.execute(params);
32
27
  }
33
28
  }
34
29
 
35
- export const creditAllocationContext = new CreditAllocationContext();
30
+ export const creditAllocationOrchestrator = new CreditAllocationOrchestrator();
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Credit Document Helpers
3
+ * Utilities for getting and creating credit documents
4
+ */
5
+
6
+ import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
7
+ import { serverTimestamp, type DocumentSnapshot } from "firebase/firestore";
8
+
9
+ /**
10
+ * Get existing credit document or create default
11
+ */
12
+ export function getCreditDocumentOrDefault(
13
+ creditsDoc: DocumentSnapshot,
14
+ platform: "ios" | "android"
15
+ ): UserCreditsDocumentRead {
16
+ if (creditsDoc.exists()) {
17
+ return creditsDoc.data() as UserCreditsDocumentRead;
18
+ }
19
+
20
+ const now = serverTimestamp();
21
+
22
+ return {
23
+ credits: 0,
24
+ creditLimit: 0,
25
+ isPremium: false,
26
+ status: "none",
27
+ processedPurchases: [],
28
+ purchaseHistory: [],
29
+ platform,
30
+ lastUpdatedAt: now,
31
+ purchasedAt: now,
32
+ expirationDate: null,
33
+ lastPurchaseAt: null,
34
+ willRenew: false,
35
+ productId: null,
36
+ packageType: null,
37
+ originalTransactionId: null,
38
+ appVersion: null,
39
+ periodType: null,
40
+ isTrialing: false,
41
+ trialStartDate: null,
42
+ trialEndDate: null,
43
+ trialCredits: 0,
44
+ convertedFromTrial: false,
45
+ } as any;
46
+ }
47
+
48
+ /**
49
+ * Add purchase ID to processed purchases list
50
+ * Maintains last 50 purchases
51
+ */
52
+ export function addProcessedPurchase(
53
+ existing: string[],
54
+ purchaseId: string,
55
+ limit: number = 50
56
+ ): string[] {
57
+ return [...existing, purchaseId].slice(-limit);
58
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Credit Operation Utilities
3
+ * Business logic for credit calculations and data building
4
+ */
5
+
6
+ import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionStatus";
7
+ import { creditAllocationOrchestrator } from "./credit-strategies/CreditAllocationOrchestrator";
8
+ import { isPast } from "../../../utils";
9
+ import type { UserCreditsDocumentRead } from "../core/UserCreditsDocument";
10
+ import type { InitializeCreditsMetadata } from "../../subscription/application/SubscriptionInitializerTypes";
11
+ import { serverTimestamp } from "firebase/firestore";
12
+
13
+ interface CalculateCreditsParams {
14
+ metadata: InitializeCreditsMetadata;
15
+ existingData: UserCreditsDocumentRead;
16
+ creditLimit: number;
17
+ purchaseId: string;
18
+ }
19
+
20
+ interface BuildCreditsDataParams {
21
+ existingData: UserCreditsDocumentRead;
22
+ newCredits: number;
23
+ creditLimit: number;
24
+ purchaseId: string;
25
+ metadata: InitializeCreditsMetadata;
26
+ purchaseHistory: any[];
27
+ platform: "ios" | "android";
28
+ }
29
+
30
+ /**
31
+ * Calculate new credits based on subscription status
32
+ */
33
+ export function calculateNewCredits(params: CalculateCreditsParams): number {
34
+ const { metadata, existingData, creditLimit, purchaseId } = params;
35
+
36
+ const isPremium = metadata.isPremium;
37
+ const isExpired = metadata.expirationDate ? isPast(metadata.expirationDate) : false;
38
+
39
+ const status = resolveSubscriptionStatus({
40
+ isPremium,
41
+ willRenew: metadata.willRenew ?? false,
42
+ isExpired,
43
+ periodType: metadata.periodType ?? undefined,
44
+ });
45
+
46
+ const isStatusSync = purchaseId.startsWith("status_sync_");
47
+ const isSubscriptionActive = isPremium && !isExpired;
48
+
49
+ return creditAllocationOrchestrator.allocate({
50
+ status,
51
+ isStatusSync,
52
+ existingData,
53
+ creditLimit,
54
+ isSubscriptionActive,
55
+ productId: metadata.productId,
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Build credits data object for Firestore update
61
+ */
62
+ export function buildCreditsData(params: BuildCreditsDataParams): Record<string, any> {
63
+ const {
64
+ existingData,
65
+ newCredits,
66
+ creditLimit,
67
+ purchaseId,
68
+ metadata,
69
+ purchaseHistory,
70
+ platform,
71
+ } = params;
72
+
73
+ const isPremium = metadata.isPremium;
74
+ const isExpired = metadata.expirationDate ? isPast(metadata.expirationDate) : false;
75
+
76
+ const status = resolveSubscriptionStatus({
77
+ isPremium,
78
+ willRenew: metadata.willRenew ?? false,
79
+ isExpired,
80
+ periodType: metadata.periodType ?? undefined,
81
+ });
82
+
83
+ const newProcessedPurchases = addProcessedPurchase(existingData.processedPurchases, purchaseId);
84
+
85
+ const creditsData: Record<string, any> = {
86
+ isPremium,
87
+ status,
88
+ credits: newCredits,
89
+ creditLimit,
90
+ lastUpdatedAt: serverTimestamp(),
91
+ processedPurchases: newProcessedPurchases,
92
+ };
93
+
94
+ if (purchaseHistory.length > 0) {
95
+ creditsData.purchaseHistory = purchaseHistory;
96
+ }
97
+
98
+ const isNewPurchaseOrRenewal = purchaseId.startsWith("purchase_") || purchaseId.startsWith("renewal_");
99
+ if (isNewPurchaseOrRenewal) {
100
+ creditsData.lastPurchaseAt = serverTimestamp();
101
+ }
102
+
103
+ if (metadata.expirationDate) {
104
+ creditsData.expirationDate = serverTimestamp();
105
+ }
106
+
107
+ if (metadata.willRenew !== undefined) {
108
+ creditsData.willRenew = metadata.willRenew;
109
+ }
110
+
111
+ if (metadata.originalTransactionId) {
112
+ creditsData.originalTransactionId = metadata.originalTransactionId;
113
+ }
114
+
115
+ creditsData.productId = metadata.productId;
116
+ creditsData.platform = platform;
117
+
118
+ return creditsData;
119
+ }
120
+
121
+ /**
122
+ * Check if status sync write should be skipped (no changes)
123
+ */
124
+ export function shouldSkipStatusSyncWrite(
125
+ purchaseId: string,
126
+ existingData: UserCreditsDocumentRead,
127
+ newCreditsData: Record<string, any>
128
+ ): boolean {
129
+ const isStatusSync = purchaseId.startsWith("status_sync_");
130
+
131
+ if (!isStatusSync) {
132
+ return false;
133
+ }
134
+
135
+ const hasChanged =
136
+ existingData.isPremium !== newCreditsData.isPremium ||
137
+ existingData.status !== newCreditsData.status ||
138
+ existingData.credits !== newCreditsData.credits ||
139
+ existingData.creditLimit !== newCreditsData.creditLimit ||
140
+ existingData.productId !== newCreditsData.productId;
141
+
142
+ return !hasChanged;
143
+ }
144
+
145
+ /**
146
+ * Add purchase ID to processed purchases list
147
+ */
148
+ function addProcessedPurchase(
149
+ existing: string[],
150
+ purchaseId: string,
151
+ limit: number = 50
152
+ ): string[] {
153
+ return [...existing, purchaseId].slice(-limit);
154
+ }
@@ -3,17 +3,12 @@ import { resolveSubscriptionStatus } from "../../subscription/core/SubscriptionS
3
3
  import type { PeriodType, SubscriptionStatusType } from "../../subscription/core/SubscriptionConstants";
4
4
  import type { UserCreditsDocumentRead } from "./UserCreditsDocument";
5
5
 
6
+ import { toSafeDate } from "../../../utils/dateUtils";
7
+
6
8
  /** Maps Firestore document to domain entity with expiration validation */
7
9
  export class CreditsMapper {
8
10
  static toEntity(doc: UserCreditsDocumentRead): UserCredits {
9
- const safeDate = (ts: any): Date | null => {
10
- if (!ts) return null;
11
- if (typeof ts.toDate === "function") return ts.toDate();
12
- if (ts instanceof Date) return ts;
13
- return null;
14
- };
15
-
16
- const expirationDate = safeDate(doc.expirationDate);
11
+ const expirationDate = toSafeDate(doc.expirationDate);
17
12
  const periodType = doc.periodType;
18
13
 
19
14
  // Validate isPremium against expirationDate (real-time check)
@@ -25,10 +20,10 @@ export class CreditsMapper {
25
20
  status,
26
21
 
27
22
  // Dates
28
- purchasedAt: safeDate(doc.purchasedAt) ?? new Date(),
23
+ purchasedAt: toSafeDate(doc.purchasedAt) ?? new Date(),
29
24
  expirationDate,
30
- lastUpdatedAt: safeDate(doc.lastUpdatedAt) ?? new Date(),
31
- lastPurchaseAt: safeDate(doc.lastPurchaseAt),
25
+ lastUpdatedAt: toSafeDate(doc.lastUpdatedAt) ?? new Date(),
26
+ lastPurchaseAt: toSafeDate(doc.lastPurchaseAt),
32
27
 
33
28
  // RevenueCat details
34
29
  willRenew: doc.willRenew,
@@ -39,8 +34,8 @@ export class CreditsMapper {
39
34
  // Trial fields
40
35
  periodType,
41
36
  isTrialing: doc.isTrialing,
42
- trialStartDate: safeDate(doc.trialStartDate),
43
- trialEndDate: safeDate(doc.trialEndDate),
37
+ trialStartDate: toSafeDate(doc.trialStartDate),
38
+ trialEndDate: toSafeDate(doc.trialEndDate),
44
39
  trialCredits: doc.trialCredits,
45
40
  convertedFromTrial: doc.convertedFromTrial,
46
41
 
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Credits Repository Provider
2
+ * Credits Repository Manager
3
3
  * Module-level singleton for credits repository configuration
4
- * Replaces Context API with a simpler, testable approach
4
+ * Provides a clean, testable approach for repository access
5
5
  */
6
6
 
7
7
  import type { CreditsConfig } from "../core/Credits";
@@ -14,7 +14,7 @@ import {
14
14
  getCreditsRepository,
15
15
  getCreditsConfig,
16
16
  isCreditsRepositoryConfigured,
17
- } from "../infrastructure/CreditsRepositoryProvider";
17
+ } from "../infrastructure/CreditsRepositoryManager";
18
18
  import { calculateCreditPercentage, canAffordCost } from "../utils/creditCalculations";
19
19
 
20
20
  export const creditsQueryKeys = {
@@ -81,10 +81,9 @@ export const useCredits = (): UseCreditsResult => {
81
81
  // Observer Pattern: Listen for credit updates
82
82
  useEffect(() => {
83
83
  if (!userId) return;
84
-
84
+
85
85
  const unsubscribe = subscriptionEventBus.on(SUBSCRIPTION_EVENTS.CREDITS_UPDATED, (updatedUserId) => {
86
86
  if (updatedUserId === userId) {
87
- if (__DEV__) console.log("[useCredits] Event received: CREDITS_UPDATED, refetching...");
88
87
  queryClient.invalidateQueries({ queryKey: creditsQueryKeys.user(userId) });
89
88
  }
90
89
  });
@@ -6,7 +6,7 @@
6
6
  import { useCallback } from "react";
7
7
  import { useMutation, useQueryClient } from "@umituz/react-native-design-system";
8
8
  import type { UserCredits } from "../core/Credits";
9
- import { getCreditsRepository } from "../infrastructure/CreditsRepositoryProvider";
9
+ import { getCreditsRepository } from "../infrastructure/CreditsRepositoryManager";
10
10
  import { creditsQueryKeys } from "./useCredits";
11
11
  import { calculateRemainingCredits } from "../utils/creditCalculations";
12
12
 
@@ -65,11 +65,11 @@ export const useDeductCredit = ({
65
65
  wasInsufficient: previousCredits.credits < cost
66
66
  };
67
67
  },
68
- onError: (_err, _cost, context) => {
68
+ onError: (_err, _cost, mutationData) => {
69
69
  // Always restore previous credits on error to prevent UI desync
70
70
  // Use optional chaining to be safe
71
- if (userId && context?.previousCredits && !context.skippedOptimistic) {
72
- queryClient.setQueryData(creditsQueryKeys.user(userId), context.previousCredits);
71
+ if (userId && mutationData?.previousCredits && !mutationData.skippedOptimistic) {
72
+ queryClient.setQueryData(creditsQueryKeys.user(userId), mutationData.previousCredits);
73
73
  }
74
74
  },
75
75
  onSuccess: () => {
@@ -45,7 +45,7 @@ export interface PaywallContainerProps {
45
45
  /** Callback when purchase succeeds */
46
46
  readonly onPurchaseSuccess?: () => void;
47
47
  /** Callback when purchase fails */
48
- readonly onPurchaseError?: (error: string) => void;
48
+ readonly onPurchaseError?: (error: Error | string) => void;
49
49
  /** Callback when auth is required (for anonymous users) */
50
50
  readonly onAuthRequired?: () => void;
51
51
  /** Visibility override */