@umituz/react-native-subscription 2.37.85 → 2.37.87
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/infrastructure/CreditsRepository.ts +6 -1
- package/src/domains/credits/infrastructure/operations/CreditsFetcher.ts +5 -0
- package/src/domains/credits/infrastructure/operations/CreditsWriter.ts +0 -2
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +7 -3
- package/src/domains/subscription/infrastructure/services/PurchaseHandler.ts +8 -0
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +32 -1
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.87",
|
|
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",
|
|
@@ -7,7 +7,7 @@ import { deductCreditsOperation } from "../application/DeductCreditsCommand";
|
|
|
7
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
|
-
import { fetchCredits, checkHasCredits } from "./operations/CreditsFetcher";
|
|
10
|
+
import { fetchCredits, checkHasCredits, documentExists } from "./operations/CreditsFetcher";
|
|
11
11
|
import { syncExpiredStatus, syncPremiumMetadata, createRecoveryCreditsDocument, type PremiumMetadata } from "./operations/CreditsWriter";
|
|
12
12
|
import { initializeCreditsWithRetry } from "./operations/CreditsInitializer";
|
|
13
13
|
import { calculateCreditLimit } from "../application/CreditLimitCalculator";
|
|
@@ -71,6 +71,11 @@ export class CreditsRepository extends BaseRepository {
|
|
|
71
71
|
return checkHasCredits(this.getRef(db, userId), cost);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
async creditsDocumentExists(userId: string): Promise<boolean> {
|
|
75
|
+
const db = requireFirestore();
|
|
76
|
+
return documentExists(this.getRef(db, userId));
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
async syncExpiredStatus(userId: string): Promise<void> {
|
|
75
80
|
const db = requireFirestore();
|
|
76
81
|
await syncExpiredStatus(this.getRef(db, userId));
|
|
@@ -4,6 +4,11 @@ import type { CreditsResult } from "../../core/Credits";
|
|
|
4
4
|
import type { UserCreditsDocumentRead } from "../../core/UserCreditsDocument";
|
|
5
5
|
import { mapCreditsDocumentToEntity } from "../../core/CreditsMapper";
|
|
6
6
|
|
|
7
|
+
export async function documentExists(ref: DocumentReference): Promise<boolean> {
|
|
8
|
+
const snap = await getDoc(ref);
|
|
9
|
+
return snap.exists();
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
export async function fetchCredits(ref: DocumentReference): Promise<CreditsResult> {
|
|
8
13
|
const snap = await getDoc(ref);
|
|
9
14
|
|
|
@@ -10,7 +10,6 @@ import { getAppVersion, validatePlatform } from "../../../../utils/appUtils";
|
|
|
10
10
|
export async function syncExpiredStatus(ref: DocumentReference): Promise<void> {
|
|
11
11
|
const doc = await getDoc(ref);
|
|
12
12
|
if (!doc.exists()) {
|
|
13
|
-
console.warn("[CreditsWriter] syncExpiredStatus: credits document does not exist, skipping.", ref.path);
|
|
14
13
|
return;
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -40,7 +39,6 @@ export async function syncPremiumMetadata(
|
|
|
40
39
|
): Promise<void> {
|
|
41
40
|
const doc = await getDoc(ref);
|
|
42
41
|
if (!doc.exists()) {
|
|
43
|
-
console.warn("[CreditsWriter] syncPremiumMetadata: credits document does not exist, skipping.", ref.path);
|
|
44
42
|
return;
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -137,9 +137,13 @@ export class SubscriptionSyncProcessor {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
if (!isPremium && !productId) {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
140
|
+
// No entitlement and no productId — could be:
|
|
141
|
+
// 1. Free user who never purchased (no credits doc) → skip
|
|
142
|
+
// 2. Previously premium user whose entitlement was removed → expire
|
|
143
|
+
const hasDoc = await getCreditsRepository().creditsDocumentExists(creditsUserId);
|
|
144
|
+
if (hasDoc) {
|
|
145
|
+
await handleExpiredSubscription(creditsUserId);
|
|
146
|
+
}
|
|
143
147
|
return;
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -21,6 +21,14 @@ export async function handlePurchase(
|
|
|
21
21
|
const consumableIds = deps.config.consumableProductIdentifiers || [];
|
|
22
22
|
const isConsumable = isConsumableProduct(pkg, consumableIds);
|
|
23
23
|
|
|
24
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
25
|
+
console.log("[PurchaseHandler] handlePurchase:", {
|
|
26
|
+
productId: pkg.product.identifier,
|
|
27
|
+
userId,
|
|
28
|
+
isConsumable,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
try {
|
|
25
33
|
const result = await executePurchase(deps.config, userId, pkg, isConsumable);
|
|
26
34
|
return result;
|
|
@@ -86,13 +86,44 @@ async function executeSubscriptionPurchase(
|
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** Default timeout for purchase operations (2 minutes) */
|
|
90
|
+
const PURCHASE_TIMEOUT_MS = 120_000;
|
|
91
|
+
|
|
92
|
+
function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
|
|
93
|
+
return new Promise<T>((resolve, reject) => {
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
reject(new Error(`[PurchaseExecutor] ${label} timed out after ${ms / 1000}s`));
|
|
96
|
+
}, ms);
|
|
97
|
+
|
|
98
|
+
promise
|
|
99
|
+
.then((result) => { clearTimeout(timer); resolve(result); })
|
|
100
|
+
.catch((error) => { clearTimeout(timer); reject(error); });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
export async function executePurchase(
|
|
90
105
|
config: RevenueCatConfig,
|
|
91
106
|
userId: string,
|
|
92
107
|
pkg: PurchasesPackage,
|
|
93
108
|
isConsumable: boolean
|
|
94
109
|
): Promise<PurchaseResult> {
|
|
95
|
-
|
|
110
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
|
+
console.log("[PurchaseExecutor] Starting Purchases.purchasePackage:", {
|
|
112
|
+
productId: pkg.product.identifier,
|
|
113
|
+
userId,
|
|
114
|
+
isConsumable,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { customerInfo } = await withTimeout(
|
|
119
|
+
Purchases.purchasePackage(pkg),
|
|
120
|
+
PURCHASE_TIMEOUT_MS,
|
|
121
|
+
`Purchases.purchasePackage(${pkg.product.identifier})`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
125
|
+
console.log("[PurchaseExecutor] Purchases.purchasePackage completed successfully");
|
|
126
|
+
}
|
|
96
127
|
|
|
97
128
|
const productId = pkg.product.identifier;
|
|
98
129
|
const packageType = pkg.packageType ?? null;
|