Skip to main content
Question

How to test non-consumable purchase multiple times in Google Play?


Forum|alt.badge.img
  • New Member
  • 3 replies

I just made my first test purchase… well, or so I thought. It said something went wrong, but nothing happened in my app, so I guess it didn’t register the purchase correctly.

Now I cannot make another test purchase to debug, because I already own the item (that’s what Google Play says once the purchase bottom sheet appears).

Is there a way to delete the purchase? Or is the only way to do this by making the product consumable (which isn’t really what I’d want in a real production environment for users, they can only buy the product once)?

Thanks!

/edit: I think I may have found the answer:

“Note: To perform multiple test purchases for the same non-consumable product, you can refund and revoke purchases using Google Play Console.”

This is as per the Google Play docs… Should we delete the customer in RevenueCat as well, or keep them and their metadata?

5 replies

jeffrey_bunn
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • 300 replies
  • May 23, 2025

@Andi For testing multiple purchases of non-consumable products, you can go ahead and refund/revoke the purchase in Google Play and delete the user in RevenueCat (which will clear their sandbox transactions).

If you want us to dig into your purchase logs (to learn more about the error you experienced), please open a support ticket. We’re happy to help. Thanks!


Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • May 26, 2025

Thanks, I’ve done that already. It worked the first time, but now I seem to be stuck on my second refund attempt. I have the purchase refunded on Google Play (it’s not yet pending the processing of the refund, it’s already refunded), and deleted the user on revenuecat, but I keep on getting the error message:

 

Error

You already own this item.

 

The exact message that I get from my logger is:
 

ERROR : This product is already active for the user.

 

I already tried restarting my phone, but doesn’t seem to be resolved. Is this a cache issue, or are you familiar with this problem? Thanks!

 

The rough code that I have, first block up top (before purchasePackage) can be ignored, it’s app-specific to find the right package on my product page to make the purchase.

 

    try {
      // Find the package across all offerings
      let rcPackage = null;

      // Search through all available offerings
      if (allOfferings) {
        for (const offeringKey in allOfferings) {
          const offering = allOfferings[offeringKey];

          if (offering.availablePackages) {
            // Find matching package in this offering
            const foundPackage = offering.availablePackages.find(
              (pkg) => pkg.product.identifier === packageItem.purchaseProductId
            );

            if (foundPackage) {
              rcPackage = foundPackage;
              break;
            }
          }
        }
      }

      // Make the purchase
      const { customerInfo } = await Purchases.purchasePackage(rcPackage);

      logger.info(customerInfo);
      if (customerInfo.entitlements.active[packageItem.purchaseProductId]) {
        await registerForRaceFree({
          variables: {
            input: {
              raceId: id,
              packageId: packageItem.id,
              distanceId: selectedDistance,
              email: user?.primaryEmailAddress?.emailAddress || "",
            },
          },
        });

        toast.success(t("toastMessageSuccessfullyRegistered"), {
          duration: 3000,
        });
      }
    } catch (error) {
      logger.error(error);

      if (!error.userCancelled) {
        // toast.error(`Purchase failed: ${error.message || "Unknown error"}`, {
        //   duration: 3000,
        // });
        toast.error(
          t("toastMessagePurchaseFailed", { message: error.message }),
          {
            duration: 3000,
          }
        );
      }

 


Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • May 26, 2025

 

By the way, I don’t use entitlements. So I modified my code to check for transactions, rather than an active entitlement, but keep it future-proof to use entitlements. Though for now, I think they only complicate my app/purchasing flow:

 

    try {
      // Find the package across all offerings
      let rcPackage = null;

      // Search through all available offerings
      if (allOfferings) {
        for (const offeringKey in allOfferings) {
          const offering = allOfferings[offeringKey];

          if (offering.availablePackages) {
            // Find matching package in this offering
            const foundPackage = offering.availablePackages.find(
              (pkg) => pkg.product.identifier === packageItem.purchaseProductId
            );

            if (foundPackage) {
              rcPackage = foundPackage;
              break;
            }
          }
        }
      }

      // Make the purchase
      const { customerInfo } = await Purchases.purchasePackage(rcPackage);
      logger.info("customerInfo");
      logger.info(customerInfo);

      // Check for entitlement (future compatibility)
      const hasEntitlement =
        customerInfo.entitlements.active[packageItem.purchaseProductId];

      // Check for direct product purchase (current logic)
      const hasPurchasedProduct =
        customerInfo.nonSubscriptionTransactions?.some(
          (tx) =>
            tx.productId === packageItem.purchaseProductId ||
            tx.productIdentifier === packageItem.purchaseProductId
        ) ||
        customerInfo.allPurchasedProductIdentifiers?.includes(
          packageItem.purchaseProductId
        );

      if (hasEntitlement || hasPurchasedProduct) {
        await registerForRaceFree({
          variables: {
            input: {
              raceId: id,
              packageId: packageItem.id,
              distanceId: selectedDistance,
              email: user?.primaryEmailAddress?.emailAddress || "",
            },
          },
        });

        toast.success(t("toastMessageSuccessfullyRegistered"), {
          duration: 3000,
        });
      }
    } catch (error) {
      logger.error(error);

      if (!error.userCancelled) {
        // toast.error(`Purchase failed: ${error.message || "Unknown error"}`, {
        //   duration: 3000,
        // });
        toast.error(
          t("toastMessagePurchaseFailed", { message: error.message }),
          {
            duration: 3000,
          }
        );
      }
    }


I tried to check purchases before making the purchase, and got this error:

 

      logger.info("restoredPurchases");
      const restoredPurchases = await Purchases.restorePurchases();
      logger.info(restoredPurchases);

 

 

[{"nativeStackAndroid":[],"userInfo":{"underlyingErrorMessage":"Backend Code: 7651 - The payment for this non-subscription product is not complete.","readableErrorCode":"UnknownBackendError","readable_error_code":"UnknownBackendError","message":"There was an unknown backend error.","code":16},"code":"16"}]

 

This sounds as if the product isn’t properly purchased, or still stuck in a purchasing flow? I’m not sure. The product was purchased correctly before, imo. All purchases that I can see in google play have been successfully charged AND successfully refunded.

 

Btw, thanks, I have opened a support request to dig further into my logs.


Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • May 26, 2025

I believe I found the “solution”/problem.

https://stackoverflow.com/questions/36821357/you-already-own-this-item-google-play-inapp-error

https://stackoverflow.com/questions/68839798/how-to-remove-entitlement-after-refund-a-test-order-in-app-purchase-in-play-co

If you forget to remove the entitlement during refund, then you are screwed. There’s no way to remove the entitlement for the user, so they’ve been refunded, but they are still entitled to the product.

The second stackoverflow shows how to “consume” the non-consumable and make it available again for purchase, but I don’t think this is possible with RevenueCat, so the only viable option seems to be to create a new user (very sub-optimal for testing), or create a new product… and then be really careful when issuing refunds. It’s not perfect.

If you know another way to get rid of the entitlement for RevenueCat workflow, I’d be really grateful!


jeffrey_bunn
RevenueCat Staff
Forum|alt.badge.img+6
  • RevenueCat Staff
  • 300 replies
  • May 28, 2025

Hi ​@Andi! Thanks so much for your code samples and sharing the results of your investigation. I just replied to your support ticket, but for the benefit of others, I wanted to share that refunding Google payments via the RevenueCat dashboard or API (and not via Google Play) also revokes the entitlement. I suspect you refunded via Google Play Console and didn’t remove the entitlement, but please let me know if I’m mistaken. Thanks!


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings