@@ -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