Skip to content
This repository was archived by the owner on Aug 4, 2026. It is now read-only.

Commit 5ac0028

Browse files
committed
feat(useIAP): add reconnect method for manual connection retry
When the initial auto-connect fails (e.g., Play Store not ready at mount time on Android), there was no way to retry the connection through the hook — connected would permanently stay false. This adds a reconnect() method that re-runs initConnection, updates the connected state, and re-registers event listeners if they were cleaned up during a previous failure. Related to hyochan/expo-iap#328
1 parent 8caf60f commit 5ac0028

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

src/hooks/useIAP.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ type UseIap = {
9090
subscriptionIds?: string[],
9191
) => Promise<ActiveSubscription[]>;
9292
hasActiveSubscriptions: (subscriptionIds?: string[]) => Promise<boolean>;
93+
/**
94+
* Manually retry the store connection.
95+
* Useful when the initial auto-connect fails (e.g., Play Store not ready at mount time).
96+
* Updates the `connected` state on success.
97+
*/
98+
reconnect: () => Promise<boolean>;
9399
// Alternative billing (Android)
94100
checkAlternativeBillingAvailabilityAndroid?: () => Promise<boolean>;
95101
showAlternativeBillingDialogAndroid?: () => Promise<boolean>;
@@ -476,6 +482,91 @@ export function useIAP(options?: UseIapOptions): UseIap {
476482
invokeOnError,
477483
]);
478484

485+
const reconnect = useCallback(async (): Promise<boolean> => {
486+
let config:
487+
| {
488+
enableBillingProgramAndroid?: BillingProgramAndroid;
489+
alternativeBillingModeAndroid?: AlternativeBillingModeAndroid;
490+
}
491+
| undefined;
492+
493+
if (Platform.OS === 'android') {
494+
if (optionsRef.current?.enableBillingProgramAndroid) {
495+
config = {
496+
enableBillingProgramAndroid:
497+
optionsRef.current.enableBillingProgramAndroid,
498+
};
499+
} else if (optionsRef.current?.alternativeBillingModeAndroid) {
500+
config = {
501+
alternativeBillingModeAndroid:
502+
optionsRef.current.alternativeBillingModeAndroid,
503+
};
504+
}
505+
}
506+
507+
try {
508+
const result = await initConnection(config);
509+
setConnected(result);
510+
511+
if (result) {
512+
// Re-register purchase listener if not already active
513+
if (!subscriptionsRef.current.purchaseUpdate) {
514+
subscriptionsRef.current.purchaseUpdate = purchaseUpdatedListener(
515+
async (purchase: Purchase) => {
516+
try {
517+
await getActiveSubscriptionsInternal();
518+
await getAvailablePurchasesInternal();
519+
} catch (e) {
520+
RnIapConsole.warn('[useIAP] post-purchase refresh failed:', e);
521+
}
522+
if (optionsRef.current?.onPurchaseSuccess) {
523+
optionsRef.current.onPurchaseSuccess(purchase);
524+
}
525+
},
526+
);
527+
}
528+
529+
// Re-register error listener if not already active
530+
if (!subscriptionsRef.current.purchaseError) {
531+
subscriptionsRef.current.purchaseError = purchaseErrorListener(
532+
(error) => {
533+
if (
534+
error.code === ErrorCode.InitConnection &&
535+
!connectedRef.current
536+
) {
537+
return;
538+
}
539+
if (optionsRef.current?.onPurchaseError) {
540+
optionsRef.current.onPurchaseError(error);
541+
}
542+
},
543+
);
544+
}
545+
546+
// iOS promoted products listener
547+
if (isStandardIOS() && !subscriptionsRef.current.promotedProductIOS) {
548+
subscriptionsRef.current.promotedProductIOS =
549+
promotedProductListenerIOS((product: Product) => {
550+
setPromotedProductIOS(product);
551+
if (optionsRef.current?.onPromotedProductIOS) {
552+
optionsRef.current.onPromotedProductIOS(product);
553+
}
554+
});
555+
}
556+
}
557+
558+
return result;
559+
} catch (error) {
560+
RnIapConsole.error('[useIAP] reconnect failed:', error);
561+
invokeOnError(error);
562+
return false;
563+
}
564+
}, [
565+
getActiveSubscriptionsInternal,
566+
getAvailablePurchasesInternal,
567+
invokeOnError,
568+
]);
569+
479570
useEffect(() => {
480571
isMountedRef.current = true;
481572
initIapWithSubscriptions();
@@ -519,6 +610,7 @@ export function useIAP(options?: UseIapOptions): UseIap {
519610
requestPurchaseOnPromotedProductIOS,
520611
getActiveSubscriptions: getActiveSubscriptionsInternal,
521612
hasActiveSubscriptions: hasActiveSubscriptionsInternal,
613+
reconnect,
522614
// Alternative billing (Android only)
523615
...(Platform.OS === 'android'
524616
? {

0 commit comments

Comments
 (0)