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

[iOS] - purchaseUpdatedListener does not works properly #756

Closed
franj0 opened this issue Oct 4, 2019 · 29 comments
Closed

[iOS] - purchaseUpdatedListener does not works properly #756

franj0 opened this issue Oct 4, 2019 · 29 comments
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS 🕵️‍♂️ need more investigation Need investigation on current issue

Comments

@franj0
Copy link

franj0 commented Oct 4, 2019

Version of react-native-iap

v3.5.8

Version of react-native

react-native: 0.60.4

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

iOS

Expected behavior

  • Call requestPurchase(string) method
  • Update listener triggered with my API to record purchase into my DB
  • myApi throw error and finishTransactionIOS() never called
  • unmount component with this.purchaseUpdateSubscription.remove()
  • mount component again
  • purchaseUpdatedListener() triggered and automatically call myApi and finishTransactionIOS() in callback

Actual behavior

  • Call requestPurchase(string) method

  • Update listener triggered with my API to record purchase into my DB

  • iOS native alert says "You're all set", "Your purchase was successfull" but my API throw error and finishTransaction()
    image

  • Unmount component and call method this.purchaseUpdateSubscription.remove()

  • Go back to component with products and there is no purchaseUpdatedListener logs to retry myApi and call finishTransactionIOS() in callback of myApi

If I kill and launch app again, mount component with products purchaseUpdatedListener is triggered and if myApi don't throw error everything works fine.

On Android I don't have this issue, purchaseUpdatedListener is triggered on component mount and there is no need to kill and launch app again.

Tested environment (Emulator? Real Device?)

real device - iPhone 8 iOS: 13.1.2

Steps to reproduce the behavior

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

hyochan commented Oct 4, 2019

Honestly, I'm not quite sure how to overcome this issue. I hope you could further invest this and bring up workarounds to this problem. Or any future ideas would be helpful.

@franj0
Copy link
Author

franj0 commented Oct 7, 2019

I assume that something is not right with iOS native method, I'll wait for some fix/update for now.

@jeffreyvr
Copy link

jeffreyvr commented Oct 14, 2019

I am running into the same issue. The purchaseUpdatedListener is not triggered on iOS, only after re-launch. Works fine on Android. Did you by chance get any further on this @franj0?

Edit:
I just noticed, on the root component, the componentDidMount was async. After I removed that, I tested again. The listener was called and everything appears to be working.

@hyochan
Copy link
Member

hyochan commented Oct 14, 2019

I've heard that this cause only in sandbox mode which sounds like the problem occurs on ios side. Have you guys also tested this in production?

@jeffreyvr
Copy link

@hyochan So far only tested in sandbox. However, since removing async from componentDidMount things look promising. Will let you know if I get any new insights on this.

@franj0
Copy link
Author

franj0 commented Oct 15, 2019

@hyochan I did not tested on production, maybe you are right, because sandbox UI and production UI for iOS is different.

@jeffreyvr I'm still stuck on listener

@TStupariu
Copy link

I also encountered this issue... It seems like whatever I want to buy, be it a subscription or consumable product the listener only gets triggered on iOS on app relaunch. Mind you I only tested in Sandbox mode so far

@jeffreyvr
Copy link

jeffreyvr commented Nov 18, 2019

@TStupariu What helped me was that I was calling purchaseUpdatedListener inside a async method. After I changed that, it worked as expected. Are you by chance doing that as well?

@TStupariu
Copy link

TStupariu commented Nov 18, 2019

I saw that but unfortunately no, It's called in a regular componentDidMount (no async or other stuff). If I don't find anything the only thing left would be to test in a non-sandbox environment because I have no other ideas.

@TStupariu
Copy link

If anyone is having this issue, at least in the case of one-time purchases here is what I found (I only had time to test those)... I had my listener registered in a componentDidMount and them I removed it on componentWillUnmount... I removed the bit of code that removes the listeners and now it seems to pick up purchases. My guess at least is that on iOS the modal for payment somehow sends the app in a background mode and forces the component to trigger an unmount (on iOS at least) and by doing that it made the listeners disconnect.

@hyochan
Copy link
Member

hyochan commented Dec 6, 2019

@TStupariu Thanks for sharing this. This is very useful and I'll keep this in mind.

@NMahendroo
Copy link

Hey can you please help me out too.

In my case the purchaseUpdatedListener is getting called in the Sandbox environment and in Production for some In App products it is getting called.

Can you please suggest me what wrong I may be doing?

I didn't implement any checks regarding the Sandbox and Production environment in the Code. The same code is getting executed for both the Environment.

I am using
react-native: 0.59.9
react-native-iap: 4.0.7

@franj0
Copy link
Author

franj0 commented Jan 22, 2020

I did not manage still anything new.
For now I refactored my IAP feature.

Because differents behaviors of purchaseListener between Android and iOS I export IAP listeners from seperated file to the first component(Home) of my app.
Because of that componentDidMount will be called only one time until app is relaunched.
With this I have same behavior for Android and iOS.

@NMahendroo
Copy link

Hi @franj0
I also have implemented the Listeners at Top Level Component and the listeners are working fine in Sandbox, but this is the issue that I am facing in Production on iOS.

The component in which listeners are set up is unmounted only when the app gets closed.

@stale
Copy link

stale bot commented Apr 23, 2020

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 Stale label Apr 23, 2020
@saminadhikari
Copy link

saminadhikari commented Apr 23, 2020

"react-native-iap": "^4.4.6"
"react-native": "0.61.5"
Still facing issue. On iOS, purchaseUpdatedListener only fires on app restart. Since it is on App.js. It has not been unmounted. I tried on sandbox but see no reason for it not to work
Any fixes?

@stale stale bot removed the 🚶🏻 stale Stale label Apr 23, 2020
@TStupariu
Copy link

"react-native-iap": "^4.4.6"
"react-native": "0.61.5"
Still facing issue. On iOS, purchaseUpdatedListener only fires on app restart. Since it is on App.js. It has not been unmounted. I tried on sandbox but see no reason for it not to work
Any fixes?

I am not sure if this would help, but when I faced the issue, even if the listener was on App.js it would still trigger an unmount. Try removing the code that removes the listener either way and see if that is the cause...

@saminadhikari
Copy link

"react-native-iap": "^4.4.6"
"react-native": "0.61.5"
Still facing issue. On iOS, purchaseUpdatedListener only fires on app restart. Since it is on App.js. It has not been unmounted. I tried on sandbox but see no reason for it not to work
Any fixes?

I am not sure if this would help, but when I faced the issue, even if the listener was on App.js it would still trigger an unmount. Try removing the code that removes the listener either way and see if that is the cause...

Did some digging in native wrapper. Forgot to call RNIap.initConnection() which triggers listener flag. Should add this note on readme as others can get confused. Thanks for the reply :)

@tamarr
Copy link

tamarr commented May 1, 2020

Thanks @saminadhikari , none of the examples mentioned you need to call initConnection. Calling it before the request did the trick 👏

wootwoot1234 added a commit to wootwoot1234/react-native-iap that referenced this issue May 12, 2020
Added note about initConnection needed for listeners to work on iOS.  Fixes issue: dooboolab-community#756 (comment)
@wootwoot1234
Copy link
Contributor

I submitted a pull request with a note added to the readme about this. #1002

@creativemind1
Copy link

"react-native-iap": "^4.4.6"
"react-native": "0.61.5"
Still facing issue. On iOS, purchaseUpdatedListener only fires on app restart. Since it is on App.js. It has not been unmounted. I tried on sandbox but see no reason for it not to work
Any fixes?

I am not sure if this would help, but when I faced the issue, even if the listener was on App.js it would still trigger an unmount. Try removing the code that removes the listener either way and see if that is the cause...

Did some digging in native wrapper. Forgot to call RNIap.initConnection() which triggers listener flag. Should add this note on readme as others can get confused. Thanks for the reply :)

I'm calling initConnection() in my componentDidMount() but still it doesn't work. It only calls on App restart on root component.

hyochan pushed a commit that referenced this issue May 19, 2020
Added note about initConnection needed for listeners to work on iOS.  Fixes issue: #756 (comment)
@alexpchin
Copy link

Does this happen if you don't call finishTransaction?

@cchalfan1
Copy link

I'm still having this same issue.

"react-native-iap": "^4.5.0",
"react-native": "^0.62.2",

this.purchaseUpdateSubscription = purchaseUpdatedListener(async (purchase: ProductPurchase ) is called on app start.

This is not called on RNIap.requestSubscription(product.id, false);

@saminadhikari
Copy link

I'm still having this same issue.

"react-native-iap": "^4.5.0",
"react-native": "^0.62.2",

this.purchaseUpdateSubscription = purchaseUpdatedListener(async (purchase: ProductPurchase ) is called on app start.

This is not called on RNIap.requestSubscription(product.id, false);

You'll need to call RNIap.initConnection() during app initialization to be able to trigger update listener

@chiporgar7
Copy link

strange things are happening to me, I have added INIT.CONNECTION

but now to the event IAP.PURCHASEUPDATELISTENER

come in many times. What's going on?

`useEffect(() => {

    IAP.initConnection().then(()=>{

        console.log('connected store')

        IAP.getProducts(productIds).then(res => {
            setProducts(res)
        });

        IAP.purchaseUpdatedListener(
            (purchase) => {
                const receipt = purchase.transactionReceipt;
            
                if(receipt) {
                   
                    console.log('test')
               
                    IAP.finishTransaction(purchase)

                }
            },
        );


    })

 



},[])`

@hyochan
Copy link
Member

hyochan commented Feb 13, 2021

I have reproduced this issue recently. However, strangely this only happened in simulator. The real device works as expected!

Closes for now. Feel free to update if there is anything new.

@hyochan hyochan closed this as completed Feb 13, 2021
@JeroenVanSteijn
Copy link

JeroenVanSteijn commented Apr 13, 2021

I have just experienced this on a real device through testflight with sandbox. purchaseUpdatedListener only fires on app restart for me. I am using the latest version with hooks. Anyone else had this, and how can it be resolved?

purchaseUpdatedListener is called in the .then() from initConnection. Is this okay, or should I do an await/do it on a later component or whatever?

the useEffect looks like this in App.tsx:

    useEffect(() => {
        RNIap.initConnection()
            .then(() => {
                getSubscriptions(subSkus);
                RNIap.purchaseUpdatedListener(
                    (
                        purchase:
                            | RNIap.InAppPurchase
                            | RNIap.SubscriptionPurchase
                            | RNIap.ProductPurchase
                    ) => {
                        const receipt = purchase.transactionReceipt;
                        if (Platform.OS === "ios" && purchase.transactionId) {
                            verifyReceipt(
                                receipt,
                                purchase.productId,
                                navigateToSucces
                            );
                            RNIap.finishTransactionIOS(purchase.transactionId);
                        } else if (
                            Platform.OS === "android" &&
                            purchase.purchaseToken
                        ) {
                            verifyReceipt(
                                purchase.purchaseToken,
                                purchase.productId,
                                navigateToSucces
                            );
                            RNIap.acknowledgePurchaseAndroid(
                                purchase.purchaseToken
                            );
                        } else {
                            Sentry.captureException(
                                `PurchaseListener could not recognize platform: ${Platform.OS}`
                            );
                        }
                    }
                );
            })
            .catch(Sentry.captureException);
        return () => {
            RNIap.endConnection();
        };
    }, []);

@sakshya73
Copy link

I have just experienced this on a real device through testflight with sandbox. purchaseUpdatedListener only fires on app restart for me. I am using the latest version with hooks. Anyone else had this, and how can it be resolved?

purchaseUpdatedListener is called in the .then() from initConnection. Is this okay, or should I do an await/do it on a later component or whatever?

the useEffect looks like this in App.tsx:

    useEffect(() => {
        RNIap.initConnection()
            .then(() => {
                getSubscriptions(subSkus);
                RNIap.purchaseUpdatedListener(
                    (
                        purchase:
                            | RNIap.InAppPurchase
                            | RNIap.SubscriptionPurchase
                            | RNIap.ProductPurchase
                    ) => {
                        const receipt = purchase.transactionReceipt;
                        if (Platform.OS === "ios" && purchase.transactionId) {
                            verifyReceipt(
                                receipt,
                                purchase.productId,
                                navigateToSucces
                            );
                            RNIap.finishTransactionIOS(purchase.transactionId);
                        } else if (
                            Platform.OS === "android" &&
                            purchase.purchaseToken
                        ) {
                            verifyReceipt(
                                purchase.purchaseToken,
                                purchase.productId,
                                navigateToSucces
                            );
                            RNIap.acknowledgePurchaseAndroid(
                                purchase.purchaseToken
                            );
                        } else {
                            Sentry.captureException(
                                `PurchaseListener could not recognize platform: ${Platform.OS}`
                            );
                        }
                    }
                );
            })
            .catch(Sentry.captureException);
        return () => {
            RNIap.endConnection();
        };
    }, []);

The same is happening for me, any update on this one?

marzuq-adebayo-dev added a commit to marzuq-adebayo-dev/React-Native-iap that referenced this issue May 20, 2023
Added note about initConnection needed for listeners to work on iOS.  Fixes issue: dooboolab-community/react-native-iap#756 (comment)
@aashankhan2981
Copy link

aashankhan2981 commented Dec 27, 2023

I faced the same issue, and updating react-native-iap to the latest version didn't resolve it. Eventually, I refactored my useEffect, where I initialize RNIAP, by lifting it to a higher-level context (possibly a parent component). I stripped away all dependencies to ensure it triggers only once, and then I implemented proper cleanup for all listeners. This modification successfully resolved the issue for me.

Here's the modified code:


// RNIap listeners
let purchaseUpdateSubscription;
let purchaseErrorSubscription;
let lastTransactionRecipe = null;

// IAP Section
useEffect(() => {
  // Init IAP function
  const timeout = setTimeout(() => {
    const initializeRNIap = async () => {
      try {
        const initialized = await RNIap.initConnection();

        if (Platform.OS === 'android') {
          await RNIap.flushFailedPurchasesCachedAsPendingAndroid();
        } else {
          await RNIap.clearTransactionIOS();
        }
      } catch (err) {
        console.warn(err.code, err.message);
      }
    };

    // Init IAP
    initializeRNIap()
      .then(() => {
        purchaseUpdateSubscription = purchaseUpdatedListener(async (purchase) => {
          const receipt = purchase.transactionReceipt;
          if (receipt) {
            if (lastTransactionRecipe === purchase.transactionReceipt) return;
            lastTransactionRecipe = receipt;
            try {
              if (Platform.OS === 'android' && purchase.purchaseToken) {
                RNIap.acknowledgePurchaseAndroid({
                  token: purchase.purchaseToken,
                  developerPayload: purchase.developerPayloadAndroid,
                })
                  .then(() => {
                    finishTransaction({
                      purchase: purchase,
                      isConsumable: true,
                      developerPayloadAndroid: purchase.developerPayloadAndroid,
                    }).catch((err) => {
                      console.log(err.code + ' finish', err.message);
                    });
                  })
                  .catch((err) => {
                    console.log(err.code + ' acknowledge', err.message);
                  });
              } else {
                await finishTransaction({
                  purchase: purchase,
                  isConsumable: true,
                });
              }
              // Additional logic here...

            } catch (ackErr) {
              console.warn('This is the error from context');
              console.warn('ackErr', ackErr);
            }
          }
        });

        purchaseErrorSubscription = purchaseErrorListener((error) => {
          if (error.code === 'E_USER_CANCELLED') {
            // 'Upgrade Cancelled', 'You cancelled the upgrade'
          } else {
            // Error while upgrading
          }
        });
      })
      .catch((err) => {
        console.error(err, '------------------Error-------------');
      });
  }, 200);

  return () => {
    clearTimeout(timeout);
    if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
      purchaseUpdateSubscription = null;
    }
    if (purchaseErrorSubscription) {
      purchaseErrorSubscription.remove();
      purchaseErrorSubscription = null;
    }
    RNIap.endConnection();
  };
}, []);

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