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

Open
traingethermarc opened this issue Oct 26, 2020 · 28 comments
Open

purchaseUpdatedListener called multiple times even after purchase #1172

traingethermarc opened this issue Oct 26, 2020 · 28 comments

Comments

@traingethermarc
Copy link

@traingethermarc traingethermarc 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;
    }
  };
}, []);

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

@hyochan hyochan commented Oct 26, 2020

Does the event fire the same purchase results?

@traingethermarc
Copy link
Author

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

@traingethermarc
Copy link
Author

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

@traingethermarc
Copy link
Author

@traingethermarc traingethermarc 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 hyochan commented Oct 26, 2020

Different account?

@traingethermarc
Copy link
Author

@traingethermarc traingethermarc commented Oct 26, 2020

Yes, same result.

@hyochan
Copy link
Member

@hyochan hyochan commented Oct 27, 2020

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

@edo1493
Copy link

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

@chiporgar7 chiporgar7 commented Nov 24, 2020

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?

@traingethermarc
Copy link
Author

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

@apperside apperside commented Jan 15, 2021

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

@musakhani musakhani commented Jan 20, 2021

any solution?

@RixBai
Copy link

@RixBai RixBai commented Jan 22, 2021

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

@roboman-sil
Copy link

@roboman-sil roboman-sil commented Jan 25, 2021

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

@GleidsonDaniel
Copy link

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

@MrShakes MrShakes commented Jan 28, 2021

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.

@traingethermarc
Copy link
Author

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

@chiporgar7 chiporgar7 commented Jan 30, 2021

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

@chiporgar7
Copy link

@chiporgar7 chiporgar7 commented Jan 30, 2021

¿alguna solución?

Revenuecat

@Desintegrator
Copy link

@Desintegrator Desintegrator commented Mar 3, 2021

same issue

@hyochan
Copy link
Member

@hyochan hyochan commented Mar 3, 2021

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

@hyochan
Copy link
Member

@hyochan 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 Mar 3, 2021
@Desintegrator
Copy link

@Desintegrator Desintegrator commented Mar 3, 2021

@hyochan nope

checked getPendingPurchasesIOS() and try finishing it

How to do this properly? Or better (easier for me i think) just cancel them?

@hyochan
Copy link
Member

@hyochan hyochan commented Mar 4, 2021

@Desintegrator Try that and check if the payment is fine to finishTransaction. Then finish it!

Also as I've mentioned in #1172 (comment), this usually happens in sandbox mode. Did you face this in production?

@Desintegrator
Copy link

@Desintegrator Desintegrator commented Mar 4, 2021

@Desintegrator Try that and check if the payment is fine to finishTransaction. Then finish it!

Also as I've mentioned in #1172 (comment), this usually happens in sandbox mode. Did you face this in production?

@hyochan for now I found in the docs and implemented clearTransactionIOS instead of finishing them. I'm just moving to this library and testing it, so faced this in TestFlight and dev environment only. app is not published yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet