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?