expo-iap 3.4.11 → 3.4.13
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/build/useIAP.d.ts +9 -3
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +69 -19
- package/build/useIAP.js.map +1 -1
- package/coverage/clover.xml +2 -2
- package/coverage/coverage-final.json +1 -1
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/src/index.html +1 -1
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov-report/src/modules/android.ts.html +1 -1
- package/coverage/lcov-report/src/modules/index.html +1 -1
- package/coverage/lcov-report/src/modules/ios.ts.html +1 -1
- package/coverage/lcov-report/src/onside/ExpoOnsideMarketplaceAvailabilityModule.ts.html +1 -1
- package/coverage/lcov-report/src/onside/index.html +1 -1
- package/coverage/lcov-report/src/onside/index.ts.html +1 -1
- package/coverage/lcov-report/src/utils/debug.ts.html +1 -1
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1 -1
- package/coverage/lcov-report/src/utils/index.html +1 -1
- package/coverage/lcov.info +1 -1
- package/ios/ExpoIap.podspec +5 -6
- package/package.json +1 -1
- package/plugin/build/withIAP.js +6 -11
- package/plugin/src/withIAP.ts +6 -17
- package/src/useIAP.ts +123 -48
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
117
117
|
Code coverage generated by
|
|
118
118
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
119
|
-
at 2026-03-
|
|
119
|
+
at 2026-03-26T18:03:32.860Z
|
|
120
120
|
</div>
|
|
121
121
|
<script src="../../prettify.js"></script>
|
|
122
122
|
<script>
|
|
@@ -1513,7 +1513,7 @@ export const showExternalPurchaseCustomLinkNoticeIOS = async (
|
|
|
1513
1513
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
1514
1514
|
Code coverage generated by
|
|
1515
1515
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
1516
|
-
at 2026-03-
|
|
1516
|
+
at 2026-03-26T18:03:32.860Z
|
|
1517
1517
|
</div>
|
|
1518
1518
|
<script src="../../prettify.js"></script>
|
|
1519
1519
|
<script>
|
|
@@ -130,7 +130,7 @@ export const ExpoOnsideMarketplaceAvailabilityModule: NativeModuleType =
|
|
|
130
130
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
131
131
|
Code coverage generated by
|
|
132
132
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
133
|
-
at 2026-03-
|
|
133
|
+
at 2026-03-26T18:03:32.860Z
|
|
134
134
|
</div>
|
|
135
135
|
<script src="../../prettify.js"></script>
|
|
136
136
|
<script>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
117
117
|
Code coverage generated by
|
|
118
118
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
119
|
-
at 2026-03-
|
|
119
|
+
at 2026-03-26T18:03:32.860Z
|
|
120
120
|
</div>
|
|
121
121
|
<script src="../../prettify.js"></script>
|
|
122
122
|
<script>
|
|
@@ -241,7 +241,7 @@ export {checkInstallationFromOnside, installedFromOnside, useOnside};
|
|
|
241
241
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
242
242
|
Code coverage generated by
|
|
243
243
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
244
|
-
at 2026-03-
|
|
244
|
+
at 2026-03-26T18:03:32.860Z
|
|
245
245
|
</div>
|
|
246
246
|
<script src="../../prettify.js"></script>
|
|
247
247
|
<script>
|
|
@@ -268,7 +268,7 @@ export const ExpoIapConsole = createConsole();
|
|
|
268
268
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
269
269
|
Code coverage generated by
|
|
270
270
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
271
|
-
at 2026-03-
|
|
271
|
+
at 2026-03-26T18:03:32.860Z
|
|
272
272
|
</div>
|
|
273
273
|
<script src="../../prettify.js"></script>
|
|
274
274
|
<script>
|
|
@@ -1126,7 +1126,7 @@ export function getUserFriendlyErrorMessage(error: ErrorLike): string {
|
|
|
1126
1126
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
1127
1127
|
Code coverage generated by
|
|
1128
1128
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
1129
|
-
at 2026-03-
|
|
1129
|
+
at 2026-03-26T18:03:32.860Z
|
|
1130
1130
|
</div>
|
|
1131
1131
|
<script src="../../prettify.js"></script>
|
|
1132
1132
|
<script>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<div class='footer quiet pad2 space-top1 center small'>
|
|
117
117
|
Code coverage generated by
|
|
118
118
|
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
119
|
-
at 2026-03-
|
|
119
|
+
at 2026-03-26T18:03:32.860Z
|
|
120
120
|
</div>
|
|
121
121
|
<script src="../../prettify.js"></script>
|
|
122
122
|
<script>
|
package/coverage/lcov.info
CHANGED
package/ios/ExpoIap.podspec
CHANGED
|
@@ -26,14 +26,13 @@ Pod::Spec.new do |s|
|
|
|
26
26
|
s.dependency 'ExpoModulesCore'
|
|
27
27
|
s.dependency 'openiap', "#{versions['apple']}"
|
|
28
28
|
|
|
29
|
-
# OnsideKit is
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
# OnsideKit dependency is conditionally included via ENV var set by the Expo plugin.
|
|
30
|
+
# When modules.onside is enabled, the plugin prepends ENV['EXPO_IAP_ONSIDE']='1' to the
|
|
31
|
+
# Podfile, which makes this dependency active and enables #if canImport(OnsideKit) in Swift.
|
|
32
|
+
if ENV['EXPO_IAP_ONSIDE'] == '1'
|
|
33
|
+
s.dependency 'OnsideKit'
|
|
33
34
|
end
|
|
34
35
|
|
|
35
|
-
s.default_subspecs = []
|
|
36
|
-
|
|
37
36
|
# Swift/Objective-C compatibility
|
|
38
37
|
s.pod_target_xcconfig = {
|
|
39
38
|
'DEFINES_MODULE' => 'YES',
|
package/package.json
CHANGED
package/plugin/build/withIAP.js
CHANGED
|
@@ -197,20 +197,15 @@ const withIapAndroid = (config, props) => {
|
|
|
197
197
|
});
|
|
198
198
|
return config;
|
|
199
199
|
};
|
|
200
|
-
const EXPO_IAP_IOS_PATH = '../node_modules/expo-iap/ios';
|
|
201
200
|
const ensureOnsidePodIOS = (content) => {
|
|
202
|
-
|
|
201
|
+
// Set EXPO_IAP_ONSIDE env var at the top of the Podfile so that the ExpoIap podspec
|
|
202
|
+
// conditionally adds OnsideKit as a dependency. This makes #if canImport(OnsideKit)
|
|
203
|
+
// work inside ExpoIap's Swift source files.
|
|
204
|
+
if (content.includes("ENV['EXPO_IAP_ONSIDE'] = '1'")) {
|
|
203
205
|
return content;
|
|
204
206
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
config_plugins_1.WarningAggregator.addWarningIOS('expo-iap', 'Could not find a target block in Podfile when adding ExpoIap/Onside; skipping installation.');
|
|
208
|
-
return content;
|
|
209
|
-
}
|
|
210
|
-
const podLine = ` pod 'ExpoIap/Onside', :path => '${EXPO_IAP_IOS_PATH}'\n`;
|
|
211
|
-
const insertIndex = targetMatch.index + targetMatch[0].length;
|
|
212
|
-
logOnce('📦 expo-iap: Added ExpoIap/Onside subspec to Podfile');
|
|
213
|
-
return content.slice(0, insertIndex) + podLine + content.slice(insertIndex);
|
|
207
|
+
logOnce('📦 expo-iap: Enabled OnsideKit (EXPO_IAP_ONSIDE=1)');
|
|
208
|
+
return `ENV['EXPO_IAP_ONSIDE'] = '1'\n` + content;
|
|
214
209
|
};
|
|
215
210
|
exports.ensureOnsidePodIOS = ensureOnsidePodIOS;
|
|
216
211
|
function computeAutolinkModules(existing, desired) {
|
package/plugin/src/withIAP.ts
CHANGED
|
@@ -262,28 +262,17 @@ const withIapAndroid: ConfigPlugin<
|
|
|
262
262
|
return config;
|
|
263
263
|
};
|
|
264
264
|
|
|
265
|
-
const EXPO_IAP_IOS_PATH = '../node_modules/expo-iap/ios';
|
|
266
|
-
|
|
267
265
|
export const ensureOnsidePodIOS = (content: string): string => {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const targetMatch = content.match(/target\s+'[^']+'\s+do\s*\n/);
|
|
273
|
-
if (!targetMatch) {
|
|
274
|
-
WarningAggregator.addWarningIOS(
|
|
275
|
-
'expo-iap',
|
|
276
|
-
'Could not find a target block in Podfile when adding ExpoIap/Onside; skipping installation.',
|
|
277
|
-
);
|
|
266
|
+
// Set EXPO_IAP_ONSIDE env var at the top of the Podfile so that the ExpoIap podspec
|
|
267
|
+
// conditionally adds OnsideKit as a dependency. This makes #if canImport(OnsideKit)
|
|
268
|
+
// work inside ExpoIap's Swift source files.
|
|
269
|
+
if (content.includes("ENV['EXPO_IAP_ONSIDE'] = '1'")) {
|
|
278
270
|
return content;
|
|
279
271
|
}
|
|
280
272
|
|
|
281
|
-
|
|
282
|
-
const insertIndex = targetMatch.index! + targetMatch[0].length;
|
|
283
|
-
|
|
284
|
-
logOnce('📦 expo-iap: Added ExpoIap/Onside subspec to Podfile');
|
|
273
|
+
logOnce('📦 expo-iap: Enabled OnsideKit (EXPO_IAP_ONSIDE=1)');
|
|
285
274
|
|
|
286
|
-
return
|
|
275
|
+
return `ENV['EXPO_IAP_ONSIDE'] = '1'\n` + content;
|
|
287
276
|
};
|
|
288
277
|
|
|
289
278
|
export type AutolinkState = {expoIap: boolean; onside: boolean};
|
package/src/useIAP.ts
CHANGED
|
@@ -49,6 +49,7 @@ import type {
|
|
|
49
49
|
VerifyPurchaseWithProviderResult,
|
|
50
50
|
ProductAndroid,
|
|
51
51
|
ProductSubscriptionIOS,
|
|
52
|
+
PurchaseOptions,
|
|
52
53
|
} from './types';
|
|
53
54
|
import {ErrorCode} from './types';
|
|
54
55
|
import type {PurchaseError} from './utils/errorMapping';
|
|
@@ -72,7 +73,7 @@ type UseIap = {
|
|
|
72
73
|
purchase: Purchase;
|
|
73
74
|
isConsumable?: boolean;
|
|
74
75
|
}) => Promise<void>;
|
|
75
|
-
getAvailablePurchases: () => Promise<void>;
|
|
76
|
+
getAvailablePurchases: (options?: PurchaseOptions) => Promise<void>;
|
|
76
77
|
fetchProducts: (params: {
|
|
77
78
|
skus: string[];
|
|
78
79
|
type?: ProductTypeInput;
|
|
@@ -89,7 +90,7 @@ type UseIap = {
|
|
|
89
90
|
verifyPurchaseWithProvider: (
|
|
90
91
|
props: VerifyPurchaseWithProviderProps,
|
|
91
92
|
) => Promise<VerifyPurchaseWithProviderResult>;
|
|
92
|
-
restorePurchases: () => Promise<void>;
|
|
93
|
+
restorePurchases: (options?: PurchaseOptions) => Promise<void>;
|
|
93
94
|
getPromotedProductIOS: () => Promise<Product | null>;
|
|
94
95
|
/**
|
|
95
96
|
* @deprecated Use promotedProductListenerIOS to receive the productId,
|
|
@@ -98,6 +99,12 @@ type UseIap = {
|
|
|
98
99
|
requestPurchaseOnPromotedProductIOS: () => Promise<boolean>;
|
|
99
100
|
getActiveSubscriptions: (subscriptionIds?: string[]) => Promise<void>;
|
|
100
101
|
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Manually retry the store connection.
|
|
104
|
+
* Useful when the initial auto-connect fails (e.g., Play Store not ready at mount time).
|
|
105
|
+
* Updates the `connected` state on success.
|
|
106
|
+
*/
|
|
107
|
+
reconnect: () => Promise<boolean>;
|
|
101
108
|
checkAlternativeBillingAvailabilityAndroid: () => Promise<boolean>;
|
|
102
109
|
showAlternativeBillingDialogAndroid: () => Promise<boolean>;
|
|
103
110
|
createAlternativeBillingTokenAndroid: (
|
|
@@ -342,19 +349,24 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
342
349
|
],
|
|
343
350
|
);
|
|
344
351
|
|
|
345
|
-
const getAvailablePurchasesInternal = useCallback(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
const getAvailablePurchasesInternal = useCallback(
|
|
353
|
+
async (options?: PurchaseOptions): Promise<void> => {
|
|
354
|
+
try {
|
|
355
|
+
const result = await getAvailablePurchases({
|
|
356
|
+
alsoPublishToEventListenerIOS:
|
|
357
|
+
options?.alsoPublishToEventListenerIOS ?? false,
|
|
358
|
+
onlyIncludeActiveItemsIOS: options?.onlyIncludeActiveItemsIOS ?? true,
|
|
359
|
+
includeSuspendedAndroid: options?.includeSuspendedAndroid ?? false,
|
|
360
|
+
});
|
|
361
|
+
setAvailablePurchases(result);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
ExpoIapConsole.error('Error fetching available purchases:', error);
|
|
364
|
+
invokeOnError(error);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
[invokeOnError],
|
|
369
|
+
);
|
|
358
370
|
|
|
359
371
|
const getActiveSubscriptionsInternal = useCallback(
|
|
360
372
|
async (subscriptionIds?: string[]): Promise<void> => {
|
|
@@ -411,35 +423,45 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
411
423
|
if (subscriptionsRefState.current.some((sub) => sub.id === productId)) {
|
|
412
424
|
await fetchProductsInternal({skus: [productId], type: 'subs'});
|
|
413
425
|
await getAvailablePurchasesInternal();
|
|
426
|
+
await getActiveSubscriptionsInternal();
|
|
414
427
|
}
|
|
415
428
|
} catch (error) {
|
|
416
429
|
ExpoIapConsole.warn('Failed to refresh subscription status:', error);
|
|
417
430
|
}
|
|
418
431
|
},
|
|
419
|
-
[
|
|
432
|
+
[
|
|
433
|
+
fetchProductsInternal,
|
|
434
|
+
getAvailablePurchasesInternal,
|
|
435
|
+
getActiveSubscriptionsInternal,
|
|
436
|
+
],
|
|
420
437
|
);
|
|
421
438
|
|
|
422
439
|
// Restore completed transactions with cross-platform behavior.
|
|
423
440
|
// iOS: best-effort sync (ignore sync errors) then fetch available purchases.
|
|
424
441
|
// Android: fetch available purchases directly.
|
|
425
|
-
const restorePurchasesInternal = useCallback(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
442
|
+
const restorePurchasesInternal = useCallback(
|
|
443
|
+
async (options?: PurchaseOptions): Promise<void> => {
|
|
444
|
+
try {
|
|
445
|
+
// iOS: Try to sync first, but don't fail if sync errors occur
|
|
446
|
+
if (Platform.OS === 'ios') {
|
|
447
|
+
await syncIOS().catch(() => undefined); // syncIOS returns Promise<boolean>, we don't need the result
|
|
448
|
+
}
|
|
431
449
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
const purchases = await getAvailablePurchases({
|
|
451
|
+
alsoPublishToEventListenerIOS:
|
|
452
|
+
options?.alsoPublishToEventListenerIOS ?? false,
|
|
453
|
+
onlyIncludeActiveItemsIOS: options?.onlyIncludeActiveItemsIOS ?? true,
|
|
454
|
+
includeSuspendedAndroid: options?.includeSuspendedAndroid ?? false,
|
|
455
|
+
});
|
|
456
|
+
setAvailablePurchases(purchases);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
ExpoIapConsole.warn('Failed to restore purchases:', error);
|
|
459
|
+
invokeOnError(error);
|
|
460
|
+
throw error;
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
[invokeOnError],
|
|
464
|
+
);
|
|
443
465
|
|
|
444
466
|
const validateReceipt = useCallback(async (props: VerifyPurchaseProps) => {
|
|
445
467
|
return validateReceiptInternal(props);
|
|
@@ -456,15 +478,29 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
456
478
|
[],
|
|
457
479
|
);
|
|
458
480
|
|
|
481
|
+
// Build config from options (prefer new enableBillingProgramAndroid over deprecated alternativeBillingModeAndroid)
|
|
482
|
+
const buildConnectionConfig = useCallback(() => {
|
|
483
|
+
return optionsRef.current?.enableBillingProgramAndroid ||
|
|
484
|
+
optionsRef.current?.alternativeBillingModeAndroid
|
|
485
|
+
? {
|
|
486
|
+
enableBillingProgramAndroid:
|
|
487
|
+
optionsRef.current.enableBillingProgramAndroid,
|
|
488
|
+
alternativeBillingModeAndroid:
|
|
489
|
+
optionsRef.current.alternativeBillingModeAndroid,
|
|
490
|
+
}
|
|
491
|
+
: undefined;
|
|
492
|
+
}, []);
|
|
493
|
+
|
|
459
494
|
const initIapWithSubscriptions = useCallback(async (): Promise<void> => {
|
|
460
495
|
// CRITICAL: Register listeners BEFORE initConnection to avoid race condition
|
|
461
496
|
// Events might fire immediately after initConnection, so listeners must be ready
|
|
462
497
|
// Register purchase update listener BEFORE initConnection to avoid race conditions.
|
|
463
498
|
subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(
|
|
464
499
|
async (purchase: Purchase) => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
500
|
+
// Refresh subscription status for both iOS and Android subscription purchases.
|
|
501
|
+
// refreshSubscriptionStatus internally checks whether the product is a known
|
|
502
|
+
// subscription, so it is safe to call unconditionally for any purchase event.
|
|
503
|
+
await refreshSubscriptionStatus(purchase.productId);
|
|
468
504
|
|
|
469
505
|
if (optionsRef.current?.onPurchaseSuccess) {
|
|
470
506
|
optionsRef.current.onPurchaseSuccess(purchase);
|
|
@@ -503,17 +539,7 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
503
539
|
}
|
|
504
540
|
|
|
505
541
|
// NOW call initConnection after listeners are ready
|
|
506
|
-
|
|
507
|
-
const config =
|
|
508
|
-
optionsRef.current?.enableBillingProgramAndroid ||
|
|
509
|
-
optionsRef.current?.alternativeBillingModeAndroid
|
|
510
|
-
? {
|
|
511
|
-
enableBillingProgramAndroid:
|
|
512
|
-
optionsRef.current.enableBillingProgramAndroid,
|
|
513
|
-
alternativeBillingModeAndroid:
|
|
514
|
-
optionsRef.current.alternativeBillingModeAndroid,
|
|
515
|
-
}
|
|
516
|
-
: undefined;
|
|
542
|
+
const config = buildConnectionConfig();
|
|
517
543
|
|
|
518
544
|
try {
|
|
519
545
|
const result = await initConnection(config);
|
|
@@ -538,7 +564,54 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
538
564
|
subscriptionsRef.current.purchaseUpdate = undefined;
|
|
539
565
|
subscriptionsRef.current.promotedProductIOS = undefined;
|
|
540
566
|
}
|
|
541
|
-
}, [refreshSubscriptionStatus, invokeOnError]);
|
|
567
|
+
}, [buildConnectionConfig, refreshSubscriptionStatus, invokeOnError]);
|
|
568
|
+
|
|
569
|
+
// Manual reconnect method for when the initial auto-connect fails.
|
|
570
|
+
// Re-runs initConnection and updates the connected state.
|
|
571
|
+
// Re-registers event listeners if they were cleaned up during a previous failure.
|
|
572
|
+
const reconnect = useCallback(async (): Promise<boolean> => {
|
|
573
|
+
const config = buildConnectionConfig();
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
const result = await initConnection(config);
|
|
577
|
+
setConnected(result);
|
|
578
|
+
|
|
579
|
+
if (result) {
|
|
580
|
+
// Re-register listeners if they were cleaned up during a previous failure
|
|
581
|
+
if (!subscriptionsRef.current.purchaseUpdate) {
|
|
582
|
+
subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(
|
|
583
|
+
async (purchase: Purchase) => {
|
|
584
|
+
await refreshSubscriptionStatus(purchase.productId);
|
|
585
|
+
|
|
586
|
+
if (optionsRef.current?.onPurchaseSuccess) {
|
|
587
|
+
optionsRef.current.onPurchaseSuccess(purchase);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (
|
|
594
|
+
Platform.OS === 'ios' &&
|
|
595
|
+
!subscriptionsRef.current.promotedProductIOS
|
|
596
|
+
) {
|
|
597
|
+
subscriptionsRef.current.promotedProductIOS =
|
|
598
|
+
promotedProductListenerIOS((product: Product) => {
|
|
599
|
+
setPromotedProductIOS(product);
|
|
600
|
+
|
|
601
|
+
if (optionsRef.current?.onPromotedProductIOS) {
|
|
602
|
+
optionsRef.current.onPromotedProductIOS(product);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return result;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
ExpoIapConsole.error('[useIAP] reconnect failed:', error);
|
|
611
|
+
invokeOnError(error);
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
}, [buildConnectionConfig, refreshSubscriptionStatus, invokeOnError]);
|
|
542
615
|
|
|
543
616
|
useEffect(() => {
|
|
544
617
|
initIapWithSubscriptions();
|
|
@@ -573,6 +646,8 @@ export function useIAP(options?: UseIAPOptions): UseIap {
|
|
|
573
646
|
requestPurchaseOnPromotedProductIOS,
|
|
574
647
|
getActiveSubscriptions: getActiveSubscriptionsInternal,
|
|
575
648
|
hasActiveSubscriptions: hasActiveSubscriptionsInternal,
|
|
649
|
+
// Reconnect method for manual retry
|
|
650
|
+
reconnect,
|
|
576
651
|
// Alternative billing methods (Android only)
|
|
577
652
|
checkAlternativeBillingAvailabilityAndroid,
|
|
578
653
|
showAlternativeBillingDialogAndroid,
|