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 multiple times even after purchase #1172

Closed
marcibk opened this issue Oct 26, 2020 · 64 comments
Closed

purchaseUpdatedListener called multiple times even after purchase #1172

marcibk opened this issue Oct 26, 2020 · 64 comments
Labels
🙏 help wanted Extra attention is needed 📱 iOS Related to iOS 👣 waiting for response Need feedback to continue

Comments

@marcibk
Copy link

marcibk commented Oct 26, 2020

Version of react-native-iap

5.0.1

Version of react-native

0.63.3

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

IOS

Expected behavior

purchaseUpdatedListener not getting called multiple times

Actual behavior

purchaseUpdatedListener getting called on startup multiple times, sometimes even in between

Tested environment (Emulator? Real Device?)

Real device (iOS 14, 13 in Test Flight and in AppStore)

Steps to reproduce the behavior

(Navigator Component)

const itemSkus = ['01', '02'];

const processNewPurchase = async (purchase) => {
  const { productId, transactionReceipt } = purchase;
  if (transactionReceipt !== undefined && transactionReceipt) {
       //backend call with fetch - validating receipt 
        if (data.ack === 'success') {
          console.log('finished');
          await finishTransaction(purchase);
      } else if (data.ack === 'failure') {
          props.setProcessing(false);
          console.log('error');
      }
    }
};


const getProductsIAP = useCallback(async () => {
  await clearProductsIOS();
  await clearTransactionIOS();

 try {
   const result = await initConnection();
   const products = await getProducts(itemSkus);
   props.setProducts(products);
   console.log('result', result);
 } catch (err) {
   console.warn(err.code, err.message);
 }

  purchaseUpdateSubscription = purchaseUpdatedListener(
    async (purchase) => {
      const receipt = purchase.transactionReceipt;
      console.log('purchaseUpdatedListener');
      if (receipt) {
        try {
          await processNewPurchase(purchase);
        } catch (ackErr) {
           console.log('ackErr', ackErr);
        }
      } else {
        console.log('purchaseUpdatedListener error: receipt');
      }
    },
  );

  purchaseErrorSubscription = purchaseErrorListener(
    (error: PurchaseError) => {
      console.log('purchaseErrorListener', error);
      console.log(JSON.stringify(error));
    },
  );

  setLoading(false);
}, []);


useEffect(() => {
  getProductsIAP();

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

@marcibk marcibk changed the title purchaseUpdatedListener called even after purchase purchaseUpdatedListener called multiple times even after purchase Oct 26, 2020
@hyochan
Copy link
Member

hyochan commented Oct 26, 2020

Does the event fire the same purchase results?

@hyochan hyochan added 🙏 help wanted Extra attention is needed 📱 iOS Related to iOS labels Oct 26, 2020
@marcibk
Copy link
Author

marcibk commented Oct 26, 2020

Yes, it fires with the successful results/receipt. If there is no current receipt it fires too, but fails on the validation server.

@hyochan
Copy link
Member

hyochan commented Oct 26, 2020

Could you please check if your component is not rerendered? There were lots of issue when the component rerendered and listeners started several times occationally. Well that should be released tough but we need to reproduce your case.

@marcibk
Copy link
Author

marcibk commented Oct 26, 2020

Well, it's the "NavigatorContainer" means I am setting different states, updating stuff and so on. But only renders one and then "rerenders" for the update of different states, props...

But that's what is for - to unsubscribe to the listeners/to avoid this.

useEffect(() => {
  getProductsIAP();

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

@hyochan
Copy link
Member

hyochan commented Oct 26, 2020

Well, it's the "NavigatorContainer" means I am setting different states, updating stuff and so on. But only renders one and then "rerenders" for the update of different states, props...

But that's what is for - to unsubscribe to the listeners/to avoid this.

useEffect(() => {
  getProductsIAP();

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

Yes I have also mentioned this. Just shared the previous issue I remember. We need the reproduction.

Also, cleariing transaction or trying with different testflight account might help.

@marcibk
Copy link
Author

marcibk commented Oct 26, 2020

const getProductsIAP = useCallback(async () => {
  await clearProductsIOS();
  await clearTransactionIOS();

.....

Tested in in TestFlight, production and different devices.

@hyochan
Copy link
Member

hyochan commented Oct 26, 2020

Different account?

@marcibk
Copy link
Author

marcibk commented Oct 26, 2020

Yes, same result.

@hyochan
Copy link
Member

hyochan commented Oct 27, 2020

Strange. Could you also try the code snippet in our IapExample project?

@edo1493
Copy link

edo1493 commented Nov 4, 2020

I have the following behaviour.

  1. User buys IAP.
  2. purchaseUpdatedListener gets called.
  3. User uninstalls the app.
  4. User comes back to the app.
  5. purchaseUpdatedListener gets triggered and goes in a loop.

I am using classes, but I don't see any re-render.

I am on "react-native-iap": "4.4.1" and "react-native": "0.63.3".

I have tried to upgrade to 5.0.0, but I am getting the same behaviour.

@chiporgar7
Copy link

Tengo el siguiente comportamiento.

  1. El usuario compra IAP.
  2. Se llama a purchaseUpdatedListener.
  3. El usuario desinstala la aplicación.
  4. El usuario vuelve a la aplicación.
  5. purchaseUpdatedListener se activa y forma un bucle.

Estoy usando clases, pero no veo ninguna repetición.

Estoy en "react-native-iap": "4.4.1" y "react-native": "0.63.3".

Intenté actualizar a 5.0.0, pero obtengo el mismo comportamiento.

Hello one question. did you find the solution to this?

@marcibk
Copy link
Author

marcibk commented Nov 25, 2020

Still same problem. Looks like it´s purchaseUpdatedListener is broken. Please fix this asap. No solution found.

@kenljv
Copy link

kenljv commented Jan 7, 2021

Having same issue with this. How is it possible that it's not fixed for months? Is this not a critical issue or has someone found a workaround for this? Also if it helps, I think I only encountered this issue when I upgraded my device from iOS 13 to iOS 14. As far as I could recall it was working fine on iOS 13 but I could be mistaken.

@apperside
Copy link

Same issue here.
I noticed that it happens every time I save the code and the app is hot reloaded.
In this scenario the root component is unmounted, and then remounted.

This is my useIap() hook

const useIap=()=>{

const purchaseUpdateSubscription = useRef<EmitterSubscription[]>([]);
    const purchaseErrorSubscription = useRef<EmitterSubscription[]>([]);

const init = useCallback(async () => {
        console.taggedLog(logTag, 'call iap init');

        await RNIap.initConnection();

        await RNIap.flushFailedPurchasesCachedAsPendingAndroid();

        purchaseUpdateSubscription.current.push(RNIap.purchaseUpdatedListener(async purchase => {
            console.taggedLog(logTag, 'purchaseUpdatedListener', purchase);

            try {
                await finalizePurchase(purchase);
            } catch (err) {
                handleFailedPurchase();
            }
        }));

        // use array to be sure to not accidentally overwrite subscriptions and lose access to them.
        // being an array I can always iterate and call `remove` on them
        purchaseErrorSubscription.current.push(RNIap.purchaseErrorListener(error => {
            handleFailedPurchase(error);
        }));

        updateProducts();
        return clear;
    }, []);

  const clear = useCallback(() => {
        console.taggedLog(logTag, 'clearing all');
        clearPurchaseEventListeners();
    }, [clearPurchaseEventListeners]);


 const clearPurchaseEventListeners = useCallback(() => {
        console.taggedLog(logTag, 'clearing purchase event listener', purchaseUpdateSubscription.current.length, purchaseErrorSubscription.current.length);
        purchaseUpdateSubscription.current.forEach(sub => sub.remove());
        purchaseErrorSubscription.current.forEach(sub => sub.remove());
        purchaseUpdateSubscription.current = [];
        purchaseErrorSubscription.current = [];
    }, []);

return {init,clear}
}

And then i use this code in the root component

const iap=useIap();
 useEffect(() => {
        console.log("root component mounted");
        iap.init();

        return () => {
            console.log("root component unmounted");
            iap.clear();
        };
    }, []);

The root component can happen to get unmounted and remounted, the clear method is called though, and I also see the log 'clearing purchase event listener',1,1, which means it's going to clear them.

Even in this situation, the purchase purchaseUpdatedListener is still called many times, es

@musakhani
Copy link

any solution?

@ruby0888
Copy link

ruby0888 commented Jan 22, 2021

Have the same issue.
It even called in different screens.
Any updates on this?

@roboman-sil
Copy link

Same issue for me. It calls the past receipts when the iap initialises the next time.

@GleidsonDaniel
Copy link

GleidsonDaniel commented Jan 27, 2021

I had the same problem and I realized that the same was happening because I was not assembling and disassembling the component correctly, thus making several listeners open at the same time.

 componentWillUnmount() {
    if (this.purchaseUpdateSubscription) {
      this.purchaseUpdateSubscription.remove();
      this.purchaseUpdateSubscription = null;
    }
  }

Every time a purchase is made and the component is disassembled it is necessary to remove the listener/subscription and add a new one when assembling the component.
This error does not occur on android, why? I don't know, but because it works on Android it makes you think that the error is in iOS and not in the code .

@MrShakes
Copy link

A possible workaround, testing I noticed that it sometimes shows the past receipts(sent to the server) so what I did is immediately store the transactionId of each notification received, so you always check the database first for if this transactionId has been processed and if so then ignore(it's a duplicate) if not then you can proceed. If the notification comes first to the App(rather than the server) then you can put some logic to control this i.e you should only receive 1 after a certain button has been clicked etc.
After testing extensively it stopped duplicating after a while, something's going on there.

@marcibk
Copy link
Author

marcibk commented Jan 29, 2021

Yes, there are some sketchy workarounds, but they are all pretty ....
Please just fix this issue. This is definitely a critical and project breaking bug since October 2020....

@chiporgar7
Copy link

There is only one solution, is to use : Revenuecat ;)

@chiporgar7
Copy link

¿alguna solución?

Revenuecat

@Desintegrator
Copy link

same issue

@hyochan
Copy link
Member

hyochan commented Mar 3, 2021

Have you guys checked getPendingPurchasesIOS() and try finishing it after checking the status?

@hyochan
Copy link
Member

hyochan commented Mar 3, 2021

Please look into below information and see if these helps.

"What you're seeing with multiple process purchase calls is actually normal in the case of auto-renewing subscriptions. When you test in the sandbox those subscriptions renew very quickly (how fast depends on sub period) and you can often see several of those appear in the queue after an app restart. Also, if a purchase hasn't been successfully completed (which is likely given those exceptions) then they can remain in the queue and result in multiple calls to your ProcessPurchase on every app restart until the problem is resolved."

from https://forum.unity.com/threads/solved-processpurchase-event-get-called-multiple-times.506574.

Also, The stackoverflow.

@hyochan hyochan closed this as completed Mar 3, 2021
@dprajapati1179
Copy link

Any solution I am facing the same issue of calling both purchaseUpdatedListener purchaseErrorListener multiple times (more than 15 times)

I am using the latest released version "react-native-iap": "10.0.1"
@andresesfm respond me if there is any specific fixes for the same.

@andresesfm andresesfm reopened this Aug 30, 2022
@andresesfm
Copy link
Collaborator

Can you please share what kind of events are you getting?

Could it be previous transaction that were not finished?

@dprajapati1179
Copy link

Can you please share what kind of events are you getting?

Could it be previous transaction that were not finished?

Yes it gets called imiidatly for multiple times with the same purchase,

I want to call My API to store the recipt for the subscription and due to multiple call backend will be confused

Any solution to stop I am also finishing transaction after calling my API but before finishing it get called again and again.

@dprajapati1179
Copy link

@andresesfm also note that the same is happening for purchaseErrorListener.

I am attaching the screenshot of the already having subscription error after pressing the ok button from the popup.

image

image

@andresesfm
Copy link
Collaborator

Please try 10.0.3 that adds clean up on disconnect. Please open a new ticket if this is still an issue. Even if the symptoms are the same, the code has changed too much to trace back. Thank you for understanding

@dprajapati1179
Copy link

Please try 10.0.3 that adds clean up on disconnect. Please open a new ticket if this is still an issue. Even if the symptoms are the same, the code has changed too much to trace back. Thank you for understanding

Okay, I will try with the 10.0.3 and update you here.
Thank you for the replay @andresesfm

@dprajapati1179
Copy link

Hello @andresesfm,

I used the latest version 10.0.5 but still, the listeners are called 3-4 times.
Am I making any mistake?

@dprajapati1179
Copy link

Hi @andresesfm

I tried with the latest version but still the same is happening for purchaseErrorListener and purchaseUpdatedListener.
Now the listeners are called 3-4 times not more than that.

Anything else I can try?

@andresesfm
Copy link
Collaborator

@dprajapati1179 the listeners will get every purchase transaction

@ToniNikolaev23
Copy link

@andresesfm Hello i have some questions about transactions and updateListener.I start to use react-native-iap and for testing i used sandbox like everybody i think..when i make purchase for any product everything is good i get receipt, sending to my server to validate, if server send me status okey i finishTransaction (this method always return undefined). And sandbox make multiple payments every 3-5 minutes.I saw that we have 2 statuses of payments(Available and Pending) i console.log to see what happen everytime with them.When statuses are in Available -> updateListener work correctly, but sometimes a lot statuses come to Pending and crash my server.I find out one flow that make my purchases to Pending -> I pay 1 subscription for example, my server validate and i finish transaction..after 15 minutes i goes to paymentscreen and click again to this subscription i receive status that already own this product, click okey and after return again to this page i saw a lot purchases goes to Pending.So i try to understand how that work.Also why after finish any trasanction they again goes to AvailableProducts, and how to understand that i finishTransaction works correctly when return me undefined.And what if for example i finishTransaction only in one screen..and if i make first purchase and leave page, after 30 minutes i log again, sandbox will make auto 5-6 payments, should i make 1 loop before updateListener to finish all new upcomming Pending transactions?

@andresesfm
Copy link
Collaborator

@ToniNikolaev23 You might get many duplicate transactions on sandbox as you are making many purchases with the same account. This is not typical in production

@krushalikevadiya
Copy link

Same issue for me also.purchaseUpdatedListener calling when user enter into the screen as well.

@c-info
Copy link

c-info commented Dec 15, 2022

const processNewPurchase = async (purchase) => {
const { productId, transactionReceipt } = purchase;
if (transactionReceipt !== undefined && transactionReceipt) {
//backend call with fetch - validating receipt
if (data.ack === 'success') {
console.log('finished');
await finishTransaction(purchase);
} else if (data.ack === 'failure') {
props.setProcessing(false);
console.log('error');
}
}
};

What if transactionReceipt is get empty ?

@andresesfm
Copy link
Collaborator

It means that the purchase didn't go through

@Haseeba393
Copy link

I'm using latest version 12.10.2 and still getting this problem, Please help me out in this

@tmoubarak
Copy link

tmoubarak commented Apr 23, 2023

same here! It seems the more i test, the more the listener gets called

@saeedtkh
Copy link

saeedtkh commented May 7, 2023

the same problem? any updates?

@karuzo17
Copy link

I currently have the same issue on Android. I get at least two listeners, which is quite problematic. On iOS everything seems to be fine for me. I am using

"react-native-iap": "^12.10.4"

Any ideas how to fix it?

@mohak54e
Copy link

I currently have the same issue on Android. I get at least two listeners, which is quite problematic. On iOS everything seems to be fine for me. I am using

"react-native-iap": "^12.10.4"

Any ideas how to fix it?

Any fix for this issue?

@BLOCKMATERIAL
Copy link

I currently have the same issue on Android. I get at least two listeners, which is quite problematic. On iOS everything seems to be fine for me. I am using
"react-native-iap": "^12.10.4"
Any ideas how to fix it?

Any fix for this issue?

use expo-in-app-purchases

Development of expo-in-app-purchases is currently paused to focus on other projects. Alternative libraries include react-native-iap and react-native-purchases from RevenueCat.

@Haider-Ali-7
Copy link

I had the same issue. Purchase listener was running two times. I made the function which will do nothing when listener run more than once. You can set executeOnce value to false after calling the finishTransaction function. Leaving it here as many folks experiencing this issue. Hope it helps somebody.

let executeOnce = false;
let purchaseUpdateSubscription;
let purchaseErrorSubscription;

const IAPScreen = () => {
const getProductsIAP = useCallback(async () => {
If (ios) {
  await clearProductsIOS();
  await clearTransactionIOS();
  }
  
if(android){
  await flushFailedPurchasesCachedAsPendingAndroid();
}

 try {
   const result = await initConnection();
   const products = await getProducts(itemSkus);
   props.setProducts(products);
   console.log('result', result);
 } catch (err) {
   console.warn(err.code, err.message);
 }

  purchaseUpdateSubscription = purchaseUpdatedListener(
    async (purchase) => {
    
      var runOnce = (() => {
        return () => {
          if (!executeOnce) {
            executeOnce = true;
            
            console.log('[RUN ONCE] Function ran once!');
            const receipt = purchase.transactionReceipt;
            
            if (receipt) {
               // validation receipt api
             } 
             
          }
        };
      })();
      runOnce();
    },
  );

  purchaseErrorSubscription = purchaseErrorListener(
    (error: PurchaseError) => {
      console.log('purchaseErrorListener', error);
      console.log(JSON.stringify(error));
    },
  );

}, []);


useEffect(() => {
  getProductsIAP();

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

export default IAPScreen;

@Saphirah
Copy link

Saphirah commented Aug 5, 2023

Isn't this expected behavior? You are calling "await finishTransaction(purchase);" in the purchaseUpdatedListener.
This will update your subscription, calling another "purchaseUpdatedListener" to fire because your subscription got updated.
This will happen in an infinite loop. Just had the same issue. Workaround is store the last processed transaction in a variable. Similar to the executeOnce of @Haider-Ali-7

//Global Scope
let lastTransactionRecipe = null;

purchaseUpdateSubscription = purchaseUpdatedListener(
            (purchase) => {
                if (purchase.transactionReceipt) {
                    if(lastTransactionRecipe === purchase.transactionReceipt) return;
                    //Do recipe validation check
                    lastTransactionRecipe = purchase.transactionReceipt;
                    finishTransaction({purchase, isConsumable: false});
                }
            }
        );

@daibing1976
Copy link

any new update? we have the same problem.

@GemsGame
Copy link

GemsGame commented Nov 22, 2023

there is we have only a crutch as a solution -_-

@congduong97
Copy link

congduong97 commented Dec 25, 2023

I found the workaround. You can use a flag to mark payment finished and use debounce like below.
Screenshot 2023-12-25 at 11 25 25
Screenshot 2023-12-25 at 11 25 57

@mrtawil
Copy link

mrtawil commented Feb 14, 2024

+1

@nguoingulanh
Copy link

My version: "react-native-iap": "^12.13.0"
I follow this https://github.com/dooboolab-community/react-native-iap/blob/main/IapExample/src/screens/ClassSetup.tsx
Everything ok with me

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 👣 waiting for response Need feedback to continue
Projects
None yet
Development

No branches or pull requests