@umituz/react-native-subscription 2.40.0 → 2.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.40.0",
3
+ "version": "2.40.2",
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",
@@ -3,12 +3,11 @@ import { getAppVersion, validatePlatform } from "../../../utils/appUtils";
3
3
 
4
4
  import type { InitializeCreditsMetadata, InitializationResult } from "../../subscription/application/SubscriptionInitializerTypes";
5
5
  import { runTransaction, serverTimestamp, type Transaction, type DocumentReference, type Firestore } from "@umituz/react-native-firebase";
6
- import { doc } from "firebase/firestore";
7
6
  import { getCreditDocumentOrDefault } from "./creditDocumentHelpers";
8
7
  import { calculateNewCredits, buildCreditsData } from "./creditOperationUtils";
9
8
  import { calculateCreditLimit } from "./CreditLimitCalculator";
10
9
  import { generatePurchaseMetadata } from "./PurchaseMetadataGenerator";
11
- import { PURCHASE_ID_PREFIXES, TRANSACTION_SUBCOLLECTION } from "../core/CreditsConstants";
10
+ import { PURCHASE_ID_PREFIXES } from "../core/CreditsConstants";
12
11
 
13
12
  export async function initializeCreditsTransaction(
14
13
  _db: Firestore,
@@ -61,28 +60,6 @@ export async function initializeCreditsTransaction(
61
60
  };
62
61
  }
63
62
 
64
- // User-specific transaction deduplication: prevent the same Apple/Google transaction
65
- // from allocating credits multiple times for the same user.
66
- // Path: users/{userId}/credits/processedTransactions/{transactionId}
67
- if (metadata.storeTransactionId) {
68
- const transactionRef = doc(_db, creditsRef.path, TRANSACTION_SUBCOLLECTION, metadata.storeTransactionId);
69
- const transactionDoc = await transaction.get(transactionRef);
70
- if (transactionDoc.exists()) {
71
- if (__DEV__) {
72
- console.log('[CreditsInitializer] 🟡 Store transaction already processed, skipping', {
73
- userId,
74
- storeTransactionId: metadata.storeTransactionId,
75
- existingCredits: existingData.credits,
76
- });
77
- }
78
- return {
79
- credits: existingData.credits,
80
- alreadyProcessed: true,
81
- finalData: existingData
82
- };
83
- }
84
- }
85
-
86
63
  if (__DEV__) {
87
64
  console.log('[CreditsInitializer] 🔵 Processing credit allocation', {
88
65
  userId,
@@ -121,16 +98,6 @@ export async function initializeCreditsTransaction(
121
98
 
122
99
  transaction.set(creditsRef, creditsData, { merge: true });
123
100
 
124
- // Register transaction in user-specific subcollection to prevent duplicate processing.
125
- if (metadata.storeTransactionId) {
126
- const transactionRef = doc(_db, creditsRef.path, TRANSACTION_SUBCOLLECTION, metadata.storeTransactionId);
127
- transaction.set(transactionRef, {
128
- purchaseId,
129
- productId: metadata.productId,
130
- createdAt: serverTimestamp(),
131
- });
132
- }
133
-
134
101
  if (__DEV__) {
135
102
  console.log('[CreditsInitializer] 🟢 Credit allocation successful', {
136
103
  userId,
@@ -14,11 +14,3 @@ export const PROCESSED_PURCHASES_WINDOW = 50;
14
14
 
15
15
  /** Maximum credits that can be deducted in a single operation. */
16
16
  export const MAX_SINGLE_DEDUCTION = 10000;
17
-
18
- /**
19
- * User-specific Firestore sub-collection for transaction deduplication.
20
- * Changed from global root-level collection to user-scoped collection
21
- * to match vivoim_app pattern and avoid Firestore permission issues.
22
- * Path: users/{userId}/credits/processedTransactions/{transactionId}
23
- */
24
- export const TRANSACTION_SUBCOLLECTION = 'processedTransactions';
@@ -104,9 +104,6 @@ export class CreditsRepository extends BaseRepository {
104
104
  willRenew,
105
105
  expirationDate,
106
106
  periodType,
107
- db,
108
- userId,
109
- storeTransactionId,
110
107
  );
111
108
  }
112
109
  }
@@ -1,13 +1,12 @@
1
- import type { DocumentReference, Transaction, Firestore } from "@umituz/react-native-firebase";
1
+ import type { DocumentReference, Transaction } from "@umituz/react-native-firebase";
2
2
  import { runTransaction, serverTimestamp } from "@umituz/react-native-firebase";
3
- import { doc, getDoc, setDoc } from "firebase/firestore";
3
+ import { getDoc, setDoc } from "firebase/firestore";
4
4
  import { SUBSCRIPTION_STATUS } from "../../../subscription/core/SubscriptionConstants";
5
5
  import { resolveSubscriptionStatus } from "../../../subscription/core/SubscriptionStatus";
6
6
  import type { SubscriptionMetadata } from "../../../subscription/core/types/SubscriptionMetadata";
7
7
  import { toTimestamp } from "../../../../shared/utils/dateConverter";
8
8
  import { isPast } from "../../../../utils/dateUtils";
9
9
  import { getAppVersion, validatePlatform } from "../../../../utils/appUtils";
10
- import { TRANSACTION_SUBCOLLECTION } from "../../core/CreditsConstants";
11
10
 
12
11
  // Fix: was getDoc+setDoc (non-atomic) — now uses runTransaction so concurrent
13
12
  // initializeCreditsTransaction and deductCreditsOperation no longer see stale
@@ -70,11 +69,8 @@ export async function syncPremiumMetadata(
70
69
  * NOTE: This uses non-atomic check-then-act (getDoc + setDoc). In theory, two concurrent
71
70
  * calls could both see no document and create duplicates. However, this is extremely rare
72
71
  * in practice because: (1) createRecoveryCreditsDocument is called after a successful
73
- * purchase which is already serialized, (2) the user-specific transaction check (below) prevents
74
- * duplicates, (3) even if two recovery docs are created, the credits document
75
- * logic is idempotent (same purchaseId processed twice is no-op). Making this atomic
76
- * would require a transaction spanning both the credits doc and transaction subcollection,
77
- * which adds complexity without meaningful benefit given the safeguards above.
72
+ * purchase which is already serialized, (2) the credits document logic is idempotent
73
+ * (same purchaseId processed twice is no-op).
78
74
  */
79
75
  export async function createRecoveryCreditsDocument(
80
76
  ref: DocumentReference,
@@ -83,35 +79,10 @@ export async function createRecoveryCreditsDocument(
83
79
  willRenew: boolean,
84
80
  expirationDate: string | null,
85
81
  periodType: string | null,
86
- db?: Firestore,
87
- userId?: string,
88
- storeTransactionId?: string | null,
89
82
  ): Promise<boolean> {
90
83
  const existingDoc = await getDoc(ref);
91
84
  if (existingDoc.exists()) return false;
92
85
 
93
- // User-specific deduplication: if this transaction was already processed
94
- // for this user, skip recovery to prevent duplicate credit allocation.
95
- if (db && userId && storeTransactionId) {
96
- try {
97
- const transactionRef = doc(db, ref.path, TRANSACTION_SUBCOLLECTION, storeTransactionId);
98
- const transactionDoc = await getDoc(transactionRef);
99
- if (transactionDoc.exists()) {
100
- if (__DEV__) {
101
- console.log(
102
- `[CreditsWriter] Recovery skipped: transaction ${storeTransactionId} already processed for user ${userId}`
103
- );
104
- }
105
- return false;
106
- }
107
- } catch (error) {
108
- // Non-fatal: if transaction check fails, still create recovery doc as safety net
109
- if (__DEV__) {
110
- console.warn('[CreditsWriter] Transaction check failed during recovery:', error);
111
- }
112
- }
113
- }
114
-
115
86
  const platform = validatePlatform();
116
87
  const appVersion = getAppVersion();
117
88
 
@@ -74,14 +74,33 @@ export function usePaywallActions({
74
74
  return;
75
75
  }
76
76
 
77
+ if (__DEV__) {
78
+ console.log('[usePaywallActions] Starting purchase', {
79
+ productId: pkg.product.identifier,
80
+ hasOnClose: !!onCloseRef.current,
81
+ hasOnSuccess: !!onPurchaseSuccessRef.current,
82
+ });
83
+ }
84
+
77
85
  setIsLocalProcessing(true);
78
86
  startPurchase(currentSelectedId, "manual");
79
87
 
80
88
  try {
81
89
  const success = await onPurchaseRef.current(pkg);
90
+ if (__DEV__) {
91
+ console.log('[usePaywallActions] Purchase completed', { success });
92
+ }
82
93
  if (success === true) {
94
+ if (__DEV__) {
95
+ console.log('[usePaywallActions] Purchase successful, calling onPurchaseSuccess and onClose');
96
+ }
83
97
  onPurchaseSuccessRef.current?.();
98
+ // Always close paywall on successful purchase
84
99
  onCloseRef.current?.();
100
+ } else {
101
+ if (__DEV__) {
102
+ console.warn('[usePaywallActions] Purchase returned false, not closing');
103
+ }
85
104
  }
86
105
  } catch (error) {
87
106
  onPurchaseErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));
@@ -99,12 +118,29 @@ export function usePaywallActions({
99
118
 
100
119
  if (isProcessingRef.current) return;
101
120
 
121
+ if (__DEV__) {
122
+ console.log('[usePaywallActions] Starting restore', {
123
+ hasOnClose: !!onCloseRef.current,
124
+ hasOnSuccess: !!onPurchaseSuccessRef.current,
125
+ });
126
+ }
127
+
102
128
  setIsLocalProcessing(true);
103
129
  try {
104
130
  const success = await onRestoreRef.current();
131
+ if (__DEV__) {
132
+ console.log('[usePaywallActions] Restore completed', { success });
133
+ }
105
134
  if (success === true) {
135
+ if (__DEV__) {
136
+ console.log('[usePaywallActions] Restore successful, calling onPurchaseSuccess and onClose');
137
+ }
106
138
  onPurchaseSuccessRef.current?.();
107
139
  onCloseRef.current?.();
140
+ } else {
141
+ if (__DEV__) {
142
+ console.warn('[usePaywallActions] Restore returned false, not closing');
143
+ }
108
144
  }
109
145
  } catch (error) {
110
146
  onPurchaseErrorRef.current?.(error instanceof Error ? error : new Error(String(error)));