@umituz/react-native-subscription 2.22.1 → 2.22.3
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.22.
|
|
3
|
+
"version": "2.22.3",
|
|
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",
|
|
@@ -31,7 +31,7 @@ export const SubscriptionDetailScreen: React.FC<
|
|
|
31
31
|
SubscriptionDetailScreenProps
|
|
32
32
|
> = ({ config }) => {
|
|
33
33
|
const tokens = useAppDesignTokens();
|
|
34
|
-
const showCredits = config.
|
|
34
|
+
const showCredits = config.credits && config.credits.length > 0;
|
|
35
35
|
const showUpgradePrompt = !config.isPremium && config.upgradePrompt;
|
|
36
36
|
|
|
37
37
|
const styles = useMemo(
|
|
@@ -68,8 +68,8 @@ export const SubscriptionDetailScreen: React.FC<
|
|
|
68
68
|
) : undefined
|
|
69
69
|
}
|
|
70
70
|
>
|
|
71
|
-
{
|
|
72
|
-
|
|
71
|
+
<View style={styles.cardsContainer}>
|
|
72
|
+
{config.isPremium && (
|
|
73
73
|
<SubscriptionHeader
|
|
74
74
|
statusType={config.statusType}
|
|
75
75
|
isPremium={config.isPremium}
|
|
@@ -79,20 +79,20 @@ export const SubscriptionDetailScreen: React.FC<
|
|
|
79
79
|
daysRemaining={config.daysRemaining}
|
|
80
80
|
translations={config.translations}
|
|
81
81
|
/>
|
|
82
|
+
)}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
showUpgradePrompt && (
|
|
84
|
+
{showCredits && (
|
|
85
|
+
<CreditsList
|
|
86
|
+
credits={config.credits!}
|
|
87
|
+
title={
|
|
88
|
+
config.translations.usageTitle || config.translations.creditsTitle
|
|
89
|
+
}
|
|
90
|
+
description={config.translations.creditsResetInfo}
|
|
91
|
+
remainingLabel={config.translations.remainingLabel}
|
|
92
|
+
/>
|
|
93
|
+
)}
|
|
94
|
+
|
|
95
|
+
{showUpgradePrompt && (
|
|
96
96
|
<UpgradePrompt
|
|
97
97
|
title={config.upgradePrompt!.title}
|
|
98
98
|
subtitle={config.upgradePrompt!.subtitle}
|
|
@@ -100,8 +100,8 @@ export const SubscriptionDetailScreen: React.FC<
|
|
|
100
100
|
upgradeButtonLabel={config.translations.upgradeButton}
|
|
101
101
|
onUpgrade={config.onUpgrade}
|
|
102
102
|
/>
|
|
103
|
-
)
|
|
104
|
-
|
|
103
|
+
)}
|
|
104
|
+
</View>
|
|
105
105
|
|
|
106
106
|
<View style={styles.spacer} />
|
|
107
107
|
</ScreenLayout>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Purchase Handler
|
|
3
3
|
* Handles RevenueCat purchase operations for both subscriptions and consumables
|
|
4
|
-
*
|
|
5
|
-
* IMPORTANT: Uses race condition with CustomerInfo listener to handle
|
|
6
|
-
* known RevenueCat bug where purchasePackage can hang indefinitely.
|
|
7
|
-
* @see https://github.com/RevenueCat/react-native-purchases/issues/1082
|
|
8
4
|
*/
|
|
9
5
|
|
|
10
|
-
import Purchases, { type PurchasesPackage
|
|
6
|
+
import Purchases, { type PurchasesPackage } from "react-native-purchases";
|
|
11
7
|
import type { PurchaseResult } from "../../application/ports/IRevenueCatService";
|
|
12
8
|
import {
|
|
13
9
|
RevenueCatPurchaseError,
|
|
@@ -24,50 +20,6 @@ import {
|
|
|
24
20
|
} from "../utils/PremiumStatusSyncer";
|
|
25
21
|
import { getSavedPurchase, clearSavedPurchase } from "../../../presentation/hooks/useAuthAwarePurchase";
|
|
26
22
|
|
|
27
|
-
const PURCHASE_LISTENER_TIMEOUT_MS = 30000; // 30 seconds fallback timeout
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Creates a promise that resolves when CustomerInfo listener detects premium
|
|
31
|
-
* This is a workaround for RevenueCat bug where purchasePackage can hang
|
|
32
|
-
*/
|
|
33
|
-
function createPremiumListenerPromise(
|
|
34
|
-
entitlementId: string
|
|
35
|
-
): { promise: Promise<CustomerInfo>; cleanup: () => void } {
|
|
36
|
-
let cleanup: () => void = () => {};
|
|
37
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
38
|
-
|
|
39
|
-
const promise = new Promise<CustomerInfo>((resolve, reject) => {
|
|
40
|
-
const listener = (info: CustomerInfo) => {
|
|
41
|
-
const isPremium = !!info.entitlements.active[entitlementId];
|
|
42
|
-
if (isPremium) {
|
|
43
|
-
if (__DEV__) {
|
|
44
|
-
console.log('[DEBUG PurchaseHandler] Listener detected premium!');
|
|
45
|
-
}
|
|
46
|
-
cleanup();
|
|
47
|
-
resolve(info);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
Purchases.addCustomerInfoUpdateListener(listener);
|
|
52
|
-
|
|
53
|
-
// Fallback timeout - if neither purchasePackage nor listener resolves
|
|
54
|
-
timeoutId = setTimeout(() => {
|
|
55
|
-
cleanup();
|
|
56
|
-
reject(new Error('Purchase timed out. Please try again.'));
|
|
57
|
-
}, PURCHASE_LISTENER_TIMEOUT_MS);
|
|
58
|
-
|
|
59
|
-
cleanup = () => {
|
|
60
|
-
Purchases.removeCustomerInfoUpdateListener(listener);
|
|
61
|
-
if (timeoutId) {
|
|
62
|
-
clearTimeout(timeoutId);
|
|
63
|
-
timeoutId = null;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
return { promise, cleanup };
|
|
69
|
-
}
|
|
70
|
-
|
|
71
23
|
export interface PurchaseHandlerDeps {
|
|
72
24
|
config: RevenueCatConfig;
|
|
73
25
|
isInitialized: () => boolean;
|
|
@@ -110,48 +62,18 @@ export async function handlePurchase(
|
|
|
110
62
|
|
|
111
63
|
const consumableIds = deps.config.consumableProductIdentifiers || [];
|
|
112
64
|
const isConsumable = isConsumableProduct(pkg, consumableIds);
|
|
113
|
-
|
|
114
|
-
// Set up listener-based detection as fallback for purchasePackage bug
|
|
115
65
|
const entitlementIdentifier = deps.config.entitlementIdentifier;
|
|
116
|
-
const { promise: listenerPromise, cleanup: cleanupListener } = createPremiumListenerPromise(entitlementIdentifier);
|
|
117
66
|
|
|
118
67
|
try {
|
|
119
68
|
if (__DEV__) {
|
|
120
|
-
console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage
|
|
69
|
+
console.log('[DEBUG PurchaseHandler] Calling Purchases.purchasePackage...', {
|
|
121
70
|
productId: pkg.product.identifier,
|
|
122
71
|
packageIdentifier: pkg.identifier,
|
|
123
72
|
offeringIdentifier: pkg.offeringIdentifier,
|
|
124
|
-
timestamp: new Date().toISOString()
|
|
125
73
|
});
|
|
126
74
|
}
|
|
127
75
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
// Race between purchasePackage and listener detection
|
|
131
|
-
// This handles the known RevenueCat bug where purchasePackage can hang
|
|
132
|
-
const purchasePromise = Purchases.purchasePackage(pkg).then(result => ({
|
|
133
|
-
source: 'purchasePackage' as const,
|
|
134
|
-
customerInfo: result.customerInfo
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
const listenerWithSource = listenerPromise.then(info => ({
|
|
138
|
-
source: 'listener' as const,
|
|
139
|
-
customerInfo: info
|
|
140
|
-
}));
|
|
141
|
-
|
|
142
|
-
const result = await Promise.race([purchasePromise, listenerWithSource]);
|
|
143
|
-
cleanupListener();
|
|
144
|
-
|
|
145
|
-
const duration = Date.now() - startTime;
|
|
146
|
-
const customerInfo = result.customerInfo;
|
|
147
|
-
|
|
148
|
-
if (__DEV__) {
|
|
149
|
-
console.log('[DEBUG PurchaseHandler] Purchase resolved via:', {
|
|
150
|
-
source: result.source,
|
|
151
|
-
duration: `${duration}ms`,
|
|
152
|
-
productId: pkg.product.identifier
|
|
153
|
-
});
|
|
154
|
-
}
|
|
76
|
+
const { customerInfo } = await Purchases.purchasePackage(pkg);
|
|
155
77
|
|
|
156
78
|
if (__DEV__) {
|
|
157
79
|
console.log('[DEBUG PurchaseHandler] Purchase completed', {
|
|
@@ -240,9 +162,6 @@ export async function handlePurchase(
|
|
|
240
162
|
pkg.product.identifier
|
|
241
163
|
);
|
|
242
164
|
} catch (error) {
|
|
243
|
-
// Ensure listener cleanup on error
|
|
244
|
-
cleanupListener();
|
|
245
|
-
|
|
246
165
|
if (__DEV__) {
|
|
247
166
|
console.error('[DEBUG PurchaseHandler] Purchase error caught', {
|
|
248
167
|
error,
|