@umituz/react-native-subscription 2.40.1 → 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 +1 -1
- package/src/domains/credits/application/CreditsInitializer.ts +1 -34
- package/src/domains/credits/core/CreditsConstants.ts +0 -8
- package/src/domains/credits/infrastructure/CreditsRepository.ts +0 -3
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +4 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.40.
|
|
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
|
|
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';
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { DocumentReference, Transaction
|
|
1
|
+
import type { DocumentReference, Transaction } from "@umituz/react-native-firebase";
|
|
2
2
|
import { runTransaction, serverTimestamp } from "@umituz/react-native-firebase";
|
|
3
|
-
import {
|
|
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
|
|
74
|
-
*
|
|
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
|
|