Cannot get my one time purchase product from Google Store/Billing in my Avalonia app


I have an avalonia app (11.x) and use this package:

<PackageReference Include="Xamarin.Android.Google.BillingClient" Version="9.1.0.1" />

I created a one time product with product id 'premium? and a buying option (active) in 173 countries. Legacy mode is activated on it.

But I cannot retrieve this product in the app.

My log says this:

2026-08-28 21:14:21.721 +02:00 [WRN] AndroidPremiumService - Premium product premium not found
2026-08-28 21:14:21.719 +02:00 [INF] AndroidPremiumService - QueryProductDetailsAsync: ResponseCode="Ok", DebugMessage=, ProductCount=0, UnfetchedCount=0
2026-08-28 21:14:19.480 +02:00 [INF] AndroidPremiumService - Querying product: premium, ListCount=1, PackageName=com.qooli.zen.cycle, ProductType=inapp
2026-08-28 21:14:19.270 +02:00 [INF] AndroidPremiumService - PRODUCT_DETAILS supported: "Ok", DebugMessage=

It is also not in the 'unfetched' prodcuts.

The applicationId matches the package Id of the store and I tested it in the emulator and using a closed testing track on my device with a beta tester. I even deleted the Play Store data and re-authenticated but it did not help.

Somehow the product is just not retrieved.

Here is the purchase method:

    public async Task RefreshProductInfoAsync()
    {
        try
        {
            await EnsureBillingConnectedAsync();

            var product = QueryProductDetailsParams.Product
                .NewBuilder()
                .SetProductId(PremiumProductId)
                .SetProductType(BillingClient.ProductType.Inapp)
                .Build();

            var packageName =
                _activityProvider.CurrentActivity?.PackageName ?? "unknown";

            var productList = new List<QueryProductDetailsParams.Product>
            {
                product
            };

            Log.Information(
                "AndroidPremiumService - Querying product: {ProductId}, ListCount={Count}, PackageName={PackageName}, ProductType={ProductType}",
                PremiumProductId,
                productList.Count,
                packageName,
                BillingClient.ProductType.Inapp);

            var result = await _billingClient!.QueryProductDetailsAsync(
                QueryProductDetailsParams.NewBuilder()
                    .SetProductList(productList)
                    .Build());

            Log.Information(
                "AndroidPremiumService - QueryProductDetailsAsync: ResponseCode={ResponseCode}, DebugMessage={DebugMessage}, ProductCount={ProductCount}, UnfetchedCount={UnfetchedCount}",
                result.Result.ResponseCode,
                result.Result.DebugMessage,
                result.ProductDetailsList?.Count ?? 0,
                result.UnfetchedProductList?.Count ?? 0);

            if (result.Result.ResponseCode != BillingResponseCode.Ok)
            {
                Log.Warning(
                    "AndroidPremiumService - QueryProductDetailsAsync failed: {ResponseCode} - {DebugMessage}",
                    result.Result.ResponseCode,
                    result.Result.DebugMessage);

                ProductInfo = null;
                _premiumProduct = null;
                _premiumOffer = null;

                return;
            }

            foreach (var storeProduct in result.ProductDetailsList ?? [])
            {
                Log.Information(
                    "AndroidPremiumService - Product returned: {ProductId} - {Title}",
                    storeProduct.ProductId,
                    storeProduct.Title);
            }

            foreach (var unfetched in result.UnfetchedProductList ?? [])
            {
                Log.Warning(
                    "AndroidPremiumService - Unfetched product: {ProductId} - ProductType={ProductType} - StatusCodeValue={StatusCodeValue}",
                    unfetched.ProductId,
                    unfetched.ProductType,
                    unfetched.StatusCodeValue);
            }

            _premiumProduct = result.ProductDetailsList?
                .FirstOrDefault(x => x.ProductId == PremiumProductId);

            if (_premiumProduct is null)
            {
                Log.Warning(
                    "AndroidPremiumService - Premium product {ProductId} not found",
                    PremiumProductId);

                ProductInfo = null;
                _premiumOffer = null;

                return;
            }

            var offers = _premiumProduct.OneTimePurchaseOfferDetailsList;

            Log.Information(
                "AndroidPremiumService - One-time purchase offers: {OfferCount}",
                offers?.Count ?? 0);

            if (offers is null || offers.Count == 0)
            {
                Log.Warning(
                    "AndroidPremiumService - Premium product {ProductId} has no eligible one-time purchase offers",
                    PremiumProductId);

                ProductInfo = null;
                _premiumOffer = null;

                return;
            }

            foreach (var offer in offers)
            {
                Log.Information(
                    "AndroidPremiumService - Offer: Price={FormattedPrice}, Currency={CurrencyCode}, Micros={PriceAmountMicros}, OfferToken={OfferToken}, PurchaseOptionId={PurchaseOptionId}",
                    offer.FormattedPrice,
                    offer.PriceCurrencyCode,
                    offer.PriceAmountMicros,
                    offer.OfferToken,
                    offer.PurchaseOptionId);
            }

            // For now we use the first eligible purchase option.
            // If multiple purchase options or promotional offers are added later,
            // this selection should be replaced by explicit business logic.
            _premiumOffer = offers.First();

            ProductInfo = new StoreProductInfo
            {
                Title = _premiumProduct.Title,
                Description = _premiumProduct.Description,
                FormattedPrice = _premiumOffer.FormattedPrice
            };
        }
        catch (Exception ex)
        {
            Log.Error(
                ex,
                "AndroidPremiumService - RefreshProductInfoAsync failed");

            ProductInfo = null;
            _premiumProduct = null;
            _premiumOffer = null;
        }
    }

And this is how I construct the billing client:

    private async Task EnsureBillingConnectedAsync()
    {
        if (_billingClient?.IsReady == true)
            return;

        var activity = _activityProvider.CurrentActivity;

        if (activity is null)
        {
            throw new InvalidOperationException(
                "EnsureBillingConnectedAsync - No Android Activity available.");
        }

        var tcs = new TaskCompletionSource<BillingResult>();

        _billingClient = BillingClient
            .NewBuilder(activity)
            .SetListener(new PurchaseUpdatedListener(this))
            .EnablePendingPurchases(
                PendingPurchasesParams.NewBuilder()
                    .EnableOneTimeProducts()
                    .Build())
            .EnableAutoServiceReconnection()
            .Build();

        _billingClient.StartConnection(
            new BillingClientStateListener(tcs));

        var result = await tcs.Task;

        if (result.ResponseCode != BillingResponseCode.Ok)
        {
            throw new InvalidOperationException(
                $"Google Play Billing connection failed: " +
                $"{result.ResponseCode} - {result.DebugMessage}");
        }

        var featureResult = _billingClient.IsFeatureSupported(
            BillingClient.FeatureType.ProductDetails);

        Log.Information(
            "AndroidPremiumService - PRODUCT_DETAILS supported: {ResponseCode}, DebugMessage={DebugMessage}",
            featureResult.ResponseCode,
            featureResult.DebugMessage);
    }

I ran out of ideas what it could be... maybe someone else has an idea?

0
Aug 28 at 7:22 PM
User AvatarPatric
#c##android#google-play#android-billing#avalonia

No answer found for this question yet.