@umituz/react-native-subscription 2.37.76 → 2.37.78
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/operations/CreditsInitializer.ts +4 -12
- package/src/domains/subscription/application/SubscriptionAuthListener.ts +6 -5
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +25 -6
- package/src/domains/subscription/application/initializer/BackgroundInitializer.ts +4 -4
- package/src/domains/subscription/infrastructure/services/purchase/PurchaseExecutor.ts +14 -5
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.78",
|
|
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",
|
|
@@ -96,22 +96,14 @@ export async function initializeCreditsWithRetry(params: InitializeCreditsParams
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// Throw so the error propagates to callers (PurchaseExecutor, SubscriptionSyncProcessor).
|
|
100
|
+
// Previously this returned { success: false } which was silently ignored by callers,
|
|
101
|
+
// causing purchases to succeed in RevenueCat but credits to never be written to Firestore.
|
|
99
102
|
const errorMessage = lastError instanceof Error
|
|
100
103
|
? lastError.message
|
|
101
104
|
: typeof lastError === 'string'
|
|
102
105
|
? lastError
|
|
103
106
|
: 'Unknown error during credit initialization';
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
? (lastError as Error & { code?: string }).code ?? 'UNKNOWN_ERROR'
|
|
107
|
-
: 'UNKNOWN_ERROR';
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
data: null,
|
|
112
|
-
error: {
|
|
113
|
-
message: errorMessage,
|
|
114
|
-
code: errorCode,
|
|
115
|
-
},
|
|
116
|
-
};
|
|
108
|
+
throw new Error(`[CreditsInitializer] Credit initialization failed after ${maxRetries} retries: ${errorMessage}`);
|
|
117
109
|
}
|
|
@@ -11,10 +11,9 @@ export const getCurrentUserId = (getAuth: () => FirebaseAuthLike | null): string
|
|
|
11
11
|
return undefined;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
14
|
+
// Return UID for ALL users including anonymous.
|
|
15
|
+
// Anonymous users need RevenueCat initialized so they can purchase subscriptions.
|
|
16
|
+
// Their credits are stored at users/{anonymousUID}/credits/balance.
|
|
18
17
|
return user.uid;
|
|
19
18
|
};
|
|
20
19
|
|
|
@@ -28,11 +27,13 @@ export const setupAuthStateListener = (
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
return auth.onAuthStateChanged((user) => {
|
|
31
|
-
if (!user
|
|
30
|
+
if (!user) {
|
|
32
31
|
onUserChange(undefined);
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
34
|
|
|
35
|
+
// Pass UID for ALL users including anonymous.
|
|
36
|
+
// Anonymous users need RevenueCat initialized for purchases.
|
|
36
37
|
onUserChange(user.uid);
|
|
37
38
|
});
|
|
38
39
|
};
|
|
@@ -51,7 +51,7 @@ export class SubscriptionSyncProcessor {
|
|
|
51
51
|
|
|
52
52
|
const creditsUserId = await this.getCreditsUserId(userId);
|
|
53
53
|
|
|
54
|
-
await getCreditsRepository().initializeCredits(
|
|
54
|
+
const result = await getCreditsRepository().initializeCredits(
|
|
55
55
|
creditsUserId,
|
|
56
56
|
purchaseId,
|
|
57
57
|
productId,
|
|
@@ -60,6 +60,10 @@ export class SubscriptionSyncProcessor {
|
|
|
60
60
|
PURCHASE_TYPE.INITIAL
|
|
61
61
|
);
|
|
62
62
|
|
|
63
|
+
if (!result.success) {
|
|
64
|
+
throw new Error(`[SubscriptionSyncProcessor] Credit initialization failed for purchase: ${result.error?.message ?? 'unknown'}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
63
67
|
emitCreditsUpdated(creditsUserId);
|
|
64
68
|
} finally {
|
|
65
69
|
this.purchaseInProgress = false;
|
|
@@ -77,7 +81,7 @@ export class SubscriptionSyncProcessor {
|
|
|
77
81
|
|
|
78
82
|
const creditsUserId = await this.getCreditsUserId(userId);
|
|
79
83
|
|
|
80
|
-
await getCreditsRepository().initializeCredits(
|
|
84
|
+
const result = await getCreditsRepository().initializeCredits(
|
|
81
85
|
creditsUserId,
|
|
82
86
|
purchaseId,
|
|
83
87
|
productId,
|
|
@@ -86,6 +90,10 @@ export class SubscriptionSyncProcessor {
|
|
|
86
90
|
PURCHASE_TYPE.RENEWAL
|
|
87
91
|
);
|
|
88
92
|
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
throw new Error(`[SubscriptionSyncProcessor] Credit initialization failed for renewal: ${result.error?.message ?? 'unknown'}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
89
97
|
emitCreditsUpdated(creditsUserId);
|
|
90
98
|
} finally {
|
|
91
99
|
this.purchaseInProgress = false;
|
|
@@ -100,12 +108,23 @@ export class SubscriptionSyncProcessor {
|
|
|
100
108
|
willRenew?: boolean,
|
|
101
109
|
periodType?: PeriodType
|
|
102
110
|
) {
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
111
|
+
// If a purchase is in progress, skip metadata sync (purchase handler does it)
|
|
112
|
+
// but still allow recovery to run — the purchase handler's credit initialization
|
|
113
|
+
// might have failed, and this is the safety net.
|
|
106
114
|
if (this.purchaseInProgress) {
|
|
107
115
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
108
|
-
console.log("[SubscriptionSyncProcessor]
|
|
116
|
+
console.log("[SubscriptionSyncProcessor] Purchase in progress - running recovery only");
|
|
117
|
+
}
|
|
118
|
+
if (isPremium && productId) {
|
|
119
|
+
const creditsUserId = await this.getCreditsUserId(userId);
|
|
120
|
+
await handlePremiumStatusSync(
|
|
121
|
+
creditsUserId,
|
|
122
|
+
isPremium,
|
|
123
|
+
productId,
|
|
124
|
+
expiresAt ?? null,
|
|
125
|
+
willRenew ?? false,
|
|
126
|
+
periodType ?? null
|
|
127
|
+
);
|
|
109
128
|
}
|
|
110
129
|
return;
|
|
111
130
|
}
|
|
@@ -105,14 +105,14 @@ export async function startBackgroundInitialization(config: SubscriptionInitConf
|
|
|
105
105
|
console.log('[BackgroundInitializer] Initial RevenueCat userId:', initialRevenueCatUserId || '(undefined - anonymous)');
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
108
|
+
// Initialize RevenueCat for all users (including anonymous).
|
|
109
|
+
// Anonymous users get their Firebase UID passed to RevenueCat so they can make purchases.
|
|
110
|
+
// Credits are stored at users/{uid}/credits/balance regardless of auth status.
|
|
111
111
|
if (initialRevenueCatUserId) {
|
|
112
112
|
await initializeInBackground(initialRevenueCatUserId);
|
|
113
113
|
lastInitSucceeded = true;
|
|
114
114
|
} else if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
115
|
-
console.log('[BackgroundInitializer]
|
|
115
|
+
console.log('[BackgroundInitializer] No user available yet, waiting for auth state');
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
const unsubscribe = setupAuthStateListener(() => auth, debouncedInitialize);
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import Purchases, { type PurchasesPackage, type CustomerInfo } from "react-native-purchases";
|
|
2
2
|
import type { PurchaseResult } from "../../../../../shared/application/ports/IRevenueCatService";
|
|
3
3
|
import type { RevenueCatConfig, PackageType } from "../../../../revenuecat/core/types";
|
|
4
|
-
import { notifyPurchaseCompleted } from "../../utils/PremiumStatusSyncer";
|
|
4
|
+
import { notifyPurchaseCompleted, syncPremiumStatus } from "../../utils/PremiumStatusSyncer";
|
|
5
5
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/useAuthAwarePurchase";
|
|
6
6
|
|
|
7
|
+
async function attemptRecovery(config: RevenueCatConfig, userId: string, customerInfo: CustomerInfo): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
console.warn('[PurchaseExecutor] Attempting recovery via syncPremiumStatus...');
|
|
10
|
+
await syncPremiumStatus(config, userId, customerInfo);
|
|
11
|
+
} catch (recoveryError) {
|
|
12
|
+
console.error('[PurchaseExecutor] Recovery also failed:', recoveryError);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
7
16
|
async function executeConsumablePurchase(
|
|
8
17
|
config: RevenueCatConfig,
|
|
9
18
|
userId: string,
|
|
@@ -17,8 +26,8 @@ async function executeConsumablePurchase(
|
|
|
17
26
|
try {
|
|
18
27
|
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
19
28
|
} catch (syncError) {
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
console.error('[PurchaseExecutor] Post-purchase sync failed, attempting recovery:', syncError);
|
|
30
|
+
await attemptRecovery(config, userId, customerInfo);
|
|
22
31
|
} finally {
|
|
23
32
|
if (savedPurchase) {
|
|
24
33
|
clearSavedPurchase();
|
|
@@ -61,8 +70,8 @@ async function executeSubscriptionPurchase(
|
|
|
61
70
|
try {
|
|
62
71
|
await notifyPurchaseCompleted(config, userId, productId, customerInfo, source, packageType);
|
|
63
72
|
} catch (syncError) {
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
console.error('[PurchaseExecutor] Post-purchase sync failed, attempting recovery:', syncError);
|
|
74
|
+
await attemptRecovery(config, userId, customerInfo);
|
|
66
75
|
} finally {
|
|
67
76
|
if (savedPurchase) {
|
|
68
77
|
clearSavedPurchase();
|