Blank white box popup on android device, instead of purchase confirmation

Hi, I’m trying to incorporate the InAppBillingPlugin in my code.

It works perfectly on iOS. However on Android a white box pops up instead of a purchase confirmation screen. I don’t even know what to search here for this problem. I am using the static default android.test.purchased product Id for testing. Chances are this may not be related to the plugin itself but perhaps the way I handle the resolution etc?

Any suggestions to why this is happening?

Gif example:
screen-20230121-171306

Notice how I am still able to exit the popup, and the exception message caught from the InAppBillingPlugin is cancelled by user.

This smells like a threading issue to me. Perhaps you’re trying to run the purchase code on your primary thread, using Task.Wait or some such, thereby blocking the main thread? From your GIF it looks like your animations stop when the purchase process pops up. I believe if your main thread is blocked, your android activity (android’s version of “windowing”) will not run.

We are using Plugin.InAppBilling (v6.7.0) in Party Words, so I can say with certainty that it does work.

This is entirely untested and slapped together, but is an example of how it could be handled:

using System;
using System.Reflection;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;
using Plugin.InAppBilling;


public class DlcManager
{
	// TODO: setup your main thread to handle these vars. If they are not null, handle their value (display error, do stuff about newly purchased DLC, etc) and then set them back to null.
	public static string purchase_dlc_error = null;
	public static purchased_dlc_id = null;


	public async Task<bool> PurchaseDlc(string dlc_id = null)
	{
		var billing = CrossInAppBilling.Current;
		try
		{
			var connected = await billing.ConnectAsync();

			if (!connected)
				throw new Exception("Could not connect!");

			var purchase = await billing.PurchaseAsync(dlc_id, ItemType.InAppPurchase);

			if (purchase == null)
				throw new Exception("null purchase result");

			if (purchase.State == PurchaseState.Deferred || purchase.State == PurchaseState.PaymentPending || purchase.State == PurchaseState.Purchasing)
				throw new Exception("USER_MSG: Purchase pending. Please restart the game after completing your purchase.");
			else if (purchase.State == PurchaseState.Purchased || purchase.State == PurchaseState.Restored)
			{
				purchased_dlc_id = dlc_id;

				if (!purchase.IsAcknowledged.HasValue || !purchase.IsAcknowledged.Value)
					_ = CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.PurchaseToken);

				return true;
			}

			throw new Exception("Unhandled purchase.State");
		}
		catch (InAppBillingPurchaseException e)
		{
			Debug.WriteLine("BILLING_ERROR InAppBillingPurchaseException: " + e);

			switch (e.PurchaseError)
			{
				case PurchaseError.AppStoreUnavailable:
					purchase_dlc_error = "The app store seems to be unavailable. Please try again later.";
					break;
				case PurchaseError.BillingUnavailable:
					purchase_dlc_error = "Billing seems to be unavailable. Please try again later.";
					break;
				case PurchaseError.PaymentInvalid:
					purchase_dlc_error = "Payment seems to be invalid. Please try again.";
					break;
				case PurchaseError.PaymentNotAllowed:
					purchase_dlc_error = "Payment does not seem to be enabled/allowed. Please try again.";
					break;
			}
		}
		catch (Exception e)
		{
			Debug.WriteLine("BILLING_ERROR Exception: " + e);

			if (e.Message.Contains("USER_MSG:"))
				purchase_dlc_error = e.Message.Replace("USER_MSG:", "").Trim();
			else
				purchase_dlc_error = "Failed to complete purchase. Please try again later.";
		}
		finally
		{
			await billing.DisconnectAsync();
			return false;
		}
	}
}

You would then run _ = DlcManager.PurchaseDlc("com.example.myapp"); from your game’s main thread to invoke a purchase process.

Hmm the code I am using is almost identical to yours. In fact I took inspiration from this post.

My Activity1.cs class has:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    Xamarin.Essentials.Platform.Init(this, bundle);

    _game = new Game1();
    _view = _game.Services.GetService(typeof(View)) as View;
    
    SetContentView(_view);
    _game.Run();
}

And my thread method is in my game implementation is (I have omitted most of the functional code to reduce noise in this thread):

private async void MakePurchase(string productId)
{
    var purchaseIsSuccessful = await ExecutePurchase(productId, "");
    if (purchaseIsSuccessful)
    {
        //PURCHASE SUCCESSFUL
    }
    else
    {
        //PURCHASE FAILED
    }
}

private async Task<bool> ExecutePurchase(string productId, string payload)
{
    var billing = CrossInAppBilling.Current;
    try
    {
        await billing.ConnectAsync();
        var purchase = await billing.PurchaseAsync(productId, ItemType.InAppPurchaseConsumable, payload);
        // purchase handling
    }
    catch (InAppBillingPurchaseException purchaseEx)
    {
        // error handling
    }
    finally
    {
        await billing.DisconnectAsync();
    }

    return false;
}

Where I then execute the MakePurchase method with the product id such as:

MakePurchase("android.test.purchased");

I still don’t understand how/why this problem occurs.

Hmm. I’ve never used the “android.test.purchased” ID before, but when I attempt to use that in my app it also gives the white modal in place of an actual purchase process. So, I think you’re using the plugin correctly. When implementing this in our game I tested DLC purchasing with real IDs, having my device registered as a test device within Google Play Console so it didn’t actually charge me.

Thanks TheKelsam, can confirm the plugin works correctly if I upload the .aab file to Google Play Console and download onto the device (instead of building directly from IDE), along with using real Product IDs. The test Product ID “android.test.purchased” still produces the same error, however this can be ignored.

For those wondering:

  • You will need upload .aab file to GPlay Console,
  • You will need to make sure it is published (in a closed testing track - so the public don’t see it)
    • This means you’ll also need to go through the hassle of filling out all of the details about the app/game, I’ve mostly just placed placeholder test and used placeholder.com to create placeholder images with required dimensions
  • You will need to make sure the Product ID you’re testing is published and active
  • You’ll also need to add the tester email address you’re logged into on the device into the close testing track testers list