Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

purchaseUpdatedListener called only once on app start, and never calls after requestSubscription call #985

Closed
Strate opened this issue Apr 21, 2020 · 25 comments
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS 🕵️‍♂️ need more investigation Need investigation on current issue

Comments

@Strate
Copy link

Strate commented Apr 21, 2020

Version of react-native-iap

4.4.6

Version of react-native

0.61.5

Platforms you faced the error (IOS or Android or both?)

ios

Expected behavior

purchaseUpdatedListener called on each time, after requestSubscription call

Actual behavior

purchaseUpdatedListener called only once on app start, and never calls after requestSubscription call

Tested environment (Emulator? Real Device?)

Real Device

Steps to reproduce the behavior

Just follow readme and try to request subscription

@hyochan hyochan added 🙏 help wanted Extra attention is needed 📱 iOS Related to iOS 🕵️‍♂️ need more investigation Need investigation on current issue labels Apr 21, 2020
@hyochan
Copy link
Member

hyochan commented Apr 21, 2020

Does anyone else having the same problem? @Strate have you called finishTransaction?

@Strate
Copy link
Author

Strate commented Apr 21, 2020

@hyochan yes, but actually this call happens after app restart

@Strate
Copy link
Author

Strate commented Apr 21, 2020

@hyochan I've tried expo-in-app-purchase package, and there is no issue like this - evrything seems to be fine

@hyochan
Copy link
Member

hyochan commented Apr 22, 2020

@Strate I want to see your code. Can't find any error myself :(

@Sophrinix
Copy link

this issue is absolutely infuriating. Is there a prior version where this is not the case?

@Sophrinix
Copy link

Does anyone else having the same problem? @Strate have you called finishTransaction?

How would this be possible as the purchase is an argument in finishTransaction? Thus you need the event listener to get the argument to pass to finishTransaction.

@Strate
Copy link
Author

Strate commented Apr 23, 2020

@Sophrinix finishTransaction expected to be called inside purchaseUpdatedListener

@Strate
Copy link
Author

Strate commented Apr 23, 2020

@hyochan sorry, but looks like I don't have an actual code right now, I've switched to expo-in-app-purchase module. Looks like @Sophrinix experience same issue.

Btw, there was nothing special in my code: register listener as soon as app started, and just follow readme.

@Sophrinix
Copy link

Sophrinix commented Apr 23, 2020

It turns out that this is a regression.

If you use the following versions then everything works:

"react": "16.8.1",
"react-native": "^0.59.10",
"react-native-iap": "4.3.0",

The problem comes when you try to use react-native-iap 4.4.4 and a version of react-native prior to 0.60

@hyochan
Copy link
Member

hyochan commented Apr 24, 2020

@Sophrinix I've just tried requestPurchase in my side and everything seems to be working. Could you please share some of your code?

Also, please check again if you've called initConnection. This is critical changes in a recent update that it is now necessary to be called in iOS as in android.

@developer-appdam
Copy link

I confirm that reverting to 4.3.0 works. I was going nuts with the latest version, I've been debugging for over 1h trying to find the issue.

@LuisRodriguezLD
Copy link

If I may add, 4.3.4 through 4.4.3 work fine, the problem begins in 4.4.4

@sagarv1997
Copy link

If I may add, 4.3.4 through 4.4.3 work fine, the problem begins in 4.4.4

Same here, Downgraded from 4.4.8 to 4.4.3 and it is now working!

@rossbulat
Copy link

rossbulat commented May 3, 2020

Does anyone else having the same problem? @Strate have you called finishTransaction?

I am having the same issue on 4.4.0 - none of the event listeners are being called, including purchaseErrorSubscription.

Just read about downgrading, I am about to try that...

Edit: This is a serious issue because:

  • it prevents purchases being sent for server processing
  • it prevents state rollback when someone cancels the purchase.

@rossbulat
Copy link

I can also confirm that 4.3.0 is working.

@stonesong
Copy link

stonesong commented May 7, 2020

I had the same issue for a few days with RN 0.62.2 and react-native-iap 4.4.8
I somehow missed the initConnection() probably because it is missing from the example in the README

@maxencehenneron
Copy link
Contributor

Had the same issue on iOS when upgrading to 4.4.8 which made some subscriptions not complete. Downgrading to 4.4.3 resolved the issue, for now. Is initConnection() mandatory? The doc makes it seems like it isn't

@kidroca
Copy link

kidroca commented May 19, 2020

Had the same issue as I didn't add the initConnection() call.
The README seems to be just updated with the bold part here:

initConnection() On iOS, it will simply call canMakePayments method and return value which is required for the listeners to work properly.

I think this part "On iOS, it will simply call canMakePayments" should be removed, also initConnection() should be added to the basic example

I was really convinced it was optional because it wasn't in the example

Adding the initConnection() before registering the purchase listener/s fixed this for me, so this is probably related to #1002 and #756

@mrozanski
Copy link

I'm on 4.4.9 and the issue described by this ticket is happening to me as well.
The listener is only invoked when started, but if I set it and then perform the purchase, it is not called.
I solved it by subscribing to the native event directly, after looking at react-native-iap/index.ts and figuring out that's what happens internally.
This can be done at any time and will persist. (Just make sure it's done only once)

import { NativeModules, NativeEventEmitter } from 'react-native';
import { Observable } from 'rxjs/Observable';

const purchaseEvent = Observable.fromEvent(
  new NativeEventEmitter(NativeModules.RNIapIos),
  'purchase-updated'
);
const purchaseSubscription = purchaseEvent.subscribe((transactionData) => {
  // Trigger server receipt validation here...
});
const errorEvent = Observable.fromEvent(
  new NativeEventEmitter(NativeModules.RNIapIos),
  'purchase-error'
);
const errorSubscription = errorEvent.subscribe((errorData) => {
  // Handle errors here...
});

@danleveille
Copy link

A lot of people are running into errors because initConnection() is not in the README's example code. I've added it here: #1088, but if anyone could double check my code, it'd be appreciated. (I still don't have my code fully functioning so I'm not too confident about it...)

@lordkiz
Copy link

lordkiz commented Dec 9, 2020

I was also encountering this issue on v5.1.1. initConnection did not solve it. The listener callbacks are never triggered on purchaseUpdate. Works fine on android though. My workaround was to use Observables like @mrozanski did.

import React, { useContext, useEffect, useState } from 'react';
import { Alert, EmitterSubscription, Platform, NativeEventEmitter,
    NativeModules } from 'react-native';
import { connect } from 'react-redux';
import RNIap, {
    InAppPurchase,
    PurchaseError,
    SubscriptionPurchase,
    purchaseErrorListener,
    purchaseUpdatedListener,
} from 'react-native-iap';
import { Observable, Subscription } from 'rxjs';

const RNIAPEvent = new NativeEventEmitter(NativeModules.RNIapIos);
const purchaseObservable = new Observable((subscriber) => {
    RNIAPEvent.addListener('purchase-updated', (event) => {
        subscriber.next(event);
    })
})

const RNIAPErrorEvent = new NativeEventEmitter(NativeModules.RNIapIos);
const purchaseErrorObservable = new Observable((subscriber) => {
    RNIAPErrorEvent.addListener('purchase-error', (event) => {
        subscriber.next(event);
    })
})


const Wrapper = ({ ...props }) => {

    let purchaseUpdateSubscription: EmitterSubscription | Subscription | null = null;
    let purchaseErrorSubscription: EmitterSubscription | Subscription | null = null;

    const validateTransaction = async (purchase: SubscriptionPurchase) => {    
        return new Promise(async (resolve) => {
              //resolve(validationResponseFromYourBackend)
        }
    }

    const handleSubEvent = async (purchase: InAppPurchase | SubscriptionPurchase) => {
        const receipt = purchase.transactionReceipt;
        const purchaseToken = purchase.purchaseToken
        if (receipt) {
            try {
                const result = await validateTransaction(purchase);
                if (result.status === 200) {
                    RNIap.acknowledgePurchaseAndroid(purchaseToken).then(() => {
                        RNIap.finishTransaction(purchase, false)
                        .then(() => giveYourUserValue())
                        .catch((e) => {
                            //
                        })
                    })
                }
            } catch (err) {
                //
            }
        }
    }


    useEffect(() => {
        RNIap.initConnection()
            .then(() => {
                RNIap.flushFailedPurchasesCachedAsPendingAndroid().catch(() => {
                    // exception
                    })
                .then(() => {
                    if (Platform.OS === 'ios') {
                        //Documented implementation has issues purchaseUpdatedListener callback
                        purchaseUpdateSubscription = purchaseObservable.subscribe((purchase) => {
                            console.log('purchase observable', purchase)
                            handleSubEvent(purchase);
                        });

                        purchaseErrorSubscription = purchaseErrorObservable.subscribe((error) => {
                            console.log('purchaseErrorListener', error);
                        })
                        
                    } else {
                        //for android use the documented method. Callbacks work.
                        purchaseUpdateSubscription = purchaseUpdatedListener(
                            (purchase: InAppPurchase | SubscriptionPurchase) => {
                                handleSubEvent(purchase);
                            }
                                
                        );
                        purchaseErrorSubscription = purchaseErrorListener(
                            (error: PurchaseError) => {
                               console.log('purchaseErrorListener', error);
                            },
                        );
                    }
            
                })
            })
        return () => {
              // clear your listeners
             //eg if Subscription purchaseErrorSubscription.unsubscribe() 
            //eg if EmitterSubscription purchaseErrorSubscription.remove() 
        }
    }, []);

    return (
        <InAppSubContext.Provider
            value={{
                someValueYouWantPassedDown: 'theValue'
            }}
        >
            {props.children}
        </InAppSubContext.Provider>
    )
}

const mapState = (state) => ({ someProps: 'yeah' });

const InAppSubscriptionManager = connect(mapState)(Wrapper);

export default InAppSubscriptionManager;

You can now use this to wrap your app in your App.tsx:


import InAppSubscriptionManager from './path/to/inAppSubscriptionManager';
const App = () => {

    return (
        <Provider store={store}>
                <InAppSubscriptionManager>

                    <AppNavigator />
                        
                </InAppSubscriptionManager>
        </Provider>
    );
}

export default App;

@mrozanski
Copy link

mrozanski commented Dec 9, 2020

Glad it helped. We have this in production since June.
It did change from Observable to this at some point (we're not using typescript)

      const purchaseEvent = new NativeEventEmitter(NativeModules.RNIapIos);
      const subscription = purchaseEvent.addListener(
        'purchase-updated',
        transactionData => {
          console.log('IAP-LOG purchase-updated');
          dispatch(validateRecepit(transactionData));
        }
      );
      const errorSubscription = purchaseEvent.addListener(
        'purchase-error',
        data => {
          crashlytics().log(`Purchase error ${JSON.stringify(data)}`);
          console.log('IAP-LOG purchase-error', data);
        }
      );
    };

@stale
Copy link

stale bot commented Jun 9, 2021

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

@stale stale bot added the Stale label Jun 9, 2021
@Strate
Copy link
Author

Strate commented Jun 9, 2021

Hey, I've just migrated from expo-in-app-purchase to this library, and faced almost no error.

@stale stale bot removed the Stale label Jun 9, 2021
@stale
Copy link

stale bot commented Apr 18, 2022

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS 🕵️‍♂️ need more investigation Need investigation on current issue
Projects
None yet
Development

No branches or pull requests