question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

purchaseUpdateListener getting called 100+ times, but not every app run

See original GitHub issue

Please use Discussion board if you want get some help out of it.

purchaseUpdateListener, gets called 100+ times - sometimes. The behavior is really strange. On one app start it will get called 0 times after the initial setup. I’ll kill the app and restart it, I’ll get 65 calls to the purchaseUpdateListener. I’ll kill the app a few more times, 0 calls to purchaseUpdateListener. Then the next run 140 calls to purchaseUpdateListener. I do call finishTransaction… But I don’t understand the intermittant nature of this problem. Sandobox env. Confirmed using two different sandbox users.

The issue is that I am not sure when I should validate my receipt. I thought purchaseUpdateListener is the place, but now when It gets called hundreds of times…

I looked at these:

https://github.com/dooboolab/react-native-iap/issues/1104

https://github.com/dooboolab/react-native-iap/issues/1172

But those did not help me.

Version of react-native-iap

7.3.0

Version of react-native

0.63.4

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

iOS (not tested on Android)

Expected behavior

If no new store events happen (the test users have not renewed or purchased anything in the last few days), I should not be seeing any calls to purchase UpdateListener.

Actual behavior

I see a lot of calls to purchaseUpdateListener, but not consistently

Tested environment (Emulator? Real Device?)

Real device

Steps to reproduce the behavior

import { DatePickerIOSBase, Platform } from "react-native";
import { debug } from "react-native-reanimated";

import storeAuth from "../config/storeAuth";
import { debugLog, errorType } from "../utils/LoggingUtils";

const {
  initConnection,
  getSubscriptions,
  validateReceiptIos,
  getPurchaseHistory,
  flushFailedPurchasesCachedAsPendingAndroid,
  clearTransactionIOS,
  endConnection,
  purchaseUpdatedListener,
  purchaseErrorListener,
  finishTransaction,
  requestSubscription,
  presentCodeRedemptionSheetIOS,
} = require("react-native-iap");

const androidValidationURL =
  "https://xxxx.cloudfunctions.net/validate";

let purchaseUpdatedListenerHandler = undefined;
let purchaseErrorListenerHandler = undefined;

const subscriptionStatus = {
  EXPIRED: "expired",
  VALID: "valid",
  NONE: "none",
};

const subcriptionSKUs = Platform.select({
  ios: ["xxx1", "xxx2"],
  android: ["xxx1", "xxx2"],
});

const initIAP = (onPurchaseUpdate, onInitComplete, onError) => {
  initConnection()
    .then(() => {

      purchaseUpdatedListenerHandler = purchaseUpdatedListener(
        async (purchase) => {

          if (purchase.transactionReceipt) {
            const result = await finishTransaction(purchase);
            onPurchaseUpdate(purchase);
          } else {
            onError(
              "RNIap.purchaseUpdatedListener. start: purchase.transactionReceipt: false",
              "",
              errorType.WARNING
            );
          }
        }
      );

      purchaseErrorListenerHandler = purchaseErrorListener(async (err) => {
        onError("RNIap.purchaseErrorListener. start ", err, type);
      });

      onInitComplete();
    })
    .catch((err) => {
      //initConnection
      onError("RNIap.initConnection.catch: ", err, errorType.WARNING);
    });
};

/**
 * Cleans up all subscriptions
 */
export const releaseIAP = async () => {
  if (purchaseUpdatedListenerHandler) {
    purchaseUpdatedListenerHandler.remove();
    purchaseUpdatedListenerHandler = undefined;
  }
  if (purchaseErrorListenerHandler) {
    purchaseErrorListenerHandler.remove();
    purchaseErrorListenerHandler = undefined;
  }
  endConnection()
    .then(() => {
    })
    .catch((err) => {
    });
};

const fetchActiveSubscriptions = (
  onSubscriptionsFetched,
  onValidationDone,
  onError
) => {
  flushTransactions()
    .then(() => {
      /*** GETS AVAILABLE SUBSCRIPTIONS ***/
      getSubscriptions(subcriptionSKUs)
        .then((res) => {
          onSubscriptionsFetched(res);
          /*** GETS PURCHASE HISTORY ***/
          //lets get the entire purchase history, get the latest receipt, and validate it
          getPurchaseHistory()
            .then((res) => {
              const lastPurchase =
                res.length === 0
                  ? undefined
                  : res.reduce((a, b) => {
                      return a.transactionDate > b.transactionDate ? a : b;
                    });

              /*** VALIDATE PURCHASE ***/
              validatePurchase(lastPurchase, onValidationDone, onError);
            })
            .catch((err) => {
              //getPurchaseHistory
              onError(
                "RNIap.getPurchaseHistory.catch: ",
                err,
                errorType.WARNING
              );
            });
        })
        .catch((err) => {
          //getSubscriptions
          onError("RNIap.getSubscriptions.catch: ", err, errorType.WARNING);
        });
    })
    .catch((err) => {
      //flushTransactions
      onError("RNIap.getPurchaseHistory.catch: ", err, errorType.WARNING);
    });
};

/**
 * Validates the receipt from the app/play store. Validation done using the app store api (no own validation for now)
 * @param {receipt} receipt as received from the app/play store
 */
const validatePurchase = async (purchase, onValidationDone, onError) => {
  if (!purchase) {
    //no transactions
    onValidationDone(subscriptionStatus.NONE, purchase);
  } else {
    const receipt = purchase.transactionReceipt;

    if (Platform.OS === "ios") {
      const receiptBodyiOS = {
        "receipt-data": receipt,
        password: storeAuth.iosAppStore.appAccessPassword,
      };

      const result = await validateReceiptIos(receiptBodyiOS, true)
        .then((res) => {
          //latest_receipt_info holds the entire purchase history
          const renewalHistory = res.latest_receipt_info;

          //we just need the renewal with the youngest expiration date
          const latestRenewal =
            renewalHistory.length === 0
              ? undefined
              : renewalHistory.reduce((a, b) => {
                  test.push(new Date(parseInt(a.expires_date_ms)));
                  return a.expires_date_ms > b.expires_date_ms ? a : b;
                });



          const expired = latestRenewal
            ? parseInt(res.receipt.request_date_ms) >
              parseInt(latestRenewal.expires_date_ms)
            : false;
          if (!expired) {
            onValidationDone(subscriptionStatus.VALID, purchase);
          } else {
            onValidationDone(subscriptionStatus.EXPIRED, purchase);
          }
        })
        .catch((err) => {
          onError("RNIap.validateReceiptIos.catch: ", err, errorType.WARNING);
        });
    } else if (Platform.OS === "android") {

      fetch(androidValidationURL, {
        headers: { "Content-Type": "application/json" },
        method: "POST",
        body: JSON.stringify({ data: receipt }),
      })
        .then((res) => {

          res.json().then((r) => {
            debugLog(
              "validatePurchase(): android: then: r: " + JSON.stringify(r)
            );
            if (r.result.error == -1) {
              onError(
                "Subscription Error. There has been an error with your subscription.",
                r.result.error,
                errorType.WARNING
              );
            } else if (r.result.isActiveSubscription == 1) {
              onValidationDone(subscriptionStatus.VALID, purchase);
            } else {
              onValidationDone(subscriptionStatus.EXPIRED, purchase);
            }
          });
        })
        .catch((err) => {
          onError(
            "validatePurchase(): android: catch: ",
            err,
            errorType.WARNING
          );
        });
    }
  }
};

/**
 * Cleans up all transactions. Meant to be called first, to flush out cancelled/stale transactions.
 */
const flushTransactions = async () => {
  if (Platform.OS === "android") {
    await flushFailedPurchasesCachedAsPendingAndroid();
  } else {
    await clearTransactionIOS();
  }
};

const purchaseSubscription = async (
  subscription,
  onSubscriptionRequest,
  onError
) => {
  onSubscriptionRequest();

  if (Platform.OS === "ios") {
    await clearTransactionIOS();
  }

  requestSubscription(subscription)
    .then(() => {
      debugLog("RNIap.requestSubscription success");
    })
    .catch((err) => {
      onError("RNIap.requestSubscription failed: ", err, errorType.WARNING);
    });
};

const useOfferCode = async () => {
  presentCodeRedemptionSheetIOS();
};

module.exports = {
  subscriptionStatus,
  initIAP,
  releaseIAP,
  fetchActiveSubscriptions,
  purchaseSubscription,
  useOfferCode,
};

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:29

github_iconTop GitHub Comments

4reactions
eneffworkcommented, Mar 10, 2022

Still facing this issue

3reactions
adambeercommented, Dec 2, 2021

Im getting this behavior without calling getPurchaseHistory() at all.

Read more comments on GitHub >

github_iconTop Results From Across the Web

In App Purchase seems to be called multiple times
The library does not filter out the Action values and so all of these broadcasts result in your purchasesUpdatedListener being called.
Read more >
react-native-iap - npm
This react-native module will help you access the In-app purchases capabilities of your phone on the Android , iOS platforms and the Amazon ......
Read more >
modules - React Native IAP - dooboolab
Add IAP purchase event Register a callback that gets called when the store has any updates to purchases that have not yet been...
Read more >
How to use the react-native-iap.purchaseUpdatedListener ...
To help you get started, we've selected a few react-native-iap examples, based on popular ways it is used in public projects. Secure your...
Read more >
React Native : Subscriptions, In-App Purchases & Service ...
If any of these are not filled out, the contract with Apple is not completed, so In-App Purchases will not work. Now that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found