How to implement google play billing in MAUI .NET 8

Ryan Ruffing 0 Reputation points
2024-05-17T16:02:36.84+00:00

I'm trying to implement the google play billing library in my .net maui application (8.0). I tried following instructions on this thread. https://learn.microsoft.com/en-us/answers/questions/1347017/im-curious-about-the-google-play-store-payment-fea
Apparently all you have to do now is initialize a billing client and it will unlock the subscriptions in the google play console, but that has not worked so far for me.
Am I doing something wrong?

using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.BillingClient.Api;

public class SubscriptionManager
{
    private BillingClient billingClient;
    private Context context;
    private Action<bool> subscriptionCallback;

    public SubscriptionManager(Context context)
    {
        this.context = context;
        InitializeBillingClient();
    }

    private void InitializeBillingClient()
    {
        billingClient = BillingClient.NewBuilder(context)
            .EnablePendingPurchases()
            .SetListener(new PurchasesUpdateListener(this))
            .Build();

        billingClient.StartConnection(new SubscriptionConnectionListener(this));
    }

    private class SubscriptionConnectionListener : Java.Lang.Object, IBillingClientStateListener
    {
        private readonly SubscriptionManager subscriptionManager;

        public SubscriptionConnectionListener(SubscriptionManager subscriptionManager)
        {
            this.subscriptionManager = subscriptionManager;
        }

        public void OnBillingServiceDisconnected()
        {
            // Try to restart the connection on the next request to BillingClient
            subscriptionManager.InitializeBillingClient();
        }

        public void OnBillingSetupFinished(BillingResult billingResult)
        {
            if (billingResult.ResponseCode == BillingResponseCode.Ok)
            {
                // BillingClient is ready
            }
            else
            {
                // Handle setup failure
                // Example: Log error or show a message to the user
            }
        }
    }

    public void InitiateSubscriptionPurchase(string skuId, Action<bool> callback)
    {
        subscriptionCallback = callback;
        SkuDetailsParams.Builder paramsBuilder = SkuDetailsParams.NewBuilder();
        paramsBuilder.SetType(BillingClient.SkuType.Subs);

        List<string> skuList = new List<string> { skuId };
        paramsBuilder.SetSkusList(skuList);

        SkuDetailsParams skuDetailsParams = paramsBuilder.Build();

        billingClient.QuerySkuDetailsAsync(skuDetailsParams);
    }

    private void LaunchSubscriptionBillingFlow(SkuDetails skuDetails)
    {
        BillingFlowParams billingFlowParams = BillingFlowParams.NewBuilder()
            .SetSkuDetails(skuDetails)
            .Build();
        BillingResult result = billingClient.LaunchBillingFlow((Activity)context, billingFlowParams);
        if (result.ResponseCode != BillingResponseCode.Ok)
        {
         
            subscriptionCallback?.Invoke(false);
        }
    }

    private class SkuDetailsResponseListener : Java.Lang.Object, ISkuDetailsResponseListener
    {
        private readonly SubscriptionManager subscriptionManager;

        public SkuDetailsResponseListener(SubscriptionManager subscriptionManager)
        {
            this.subscriptionManager = subscriptionManager;
        }

        public void OnSkuDetailsResponse(BillingResult billingResult, IList<SkuDetails> skuDetailsList)
        {
            if (billingResult.ResponseCode == BillingResponseCode.Ok && skuDetailsList != null && skuDetailsList.Count > 0)
            {
                SkuDetails skuDetails = skuDetailsList.FirstOrDefault(); 
                subscriptionManager.LaunchSubscriptionBillingFlow(skuDetails);
            }
            else
            {
               
                subscriptionManager.subscriptionCallback?.Invoke(false);
            }
        }
    }

    internal class PurchasesUpdateListener : Java.Lang.Object, IPurchasesUpdatedListener
    {
        private readonly SubscriptionManager subscriptionManager;

        public PurchasesUpdateListener(SubscriptionManager subscriptionManager)
        {
            this.subscriptionManager = subscriptionManager;
        }

        public void OnPurchasesUpdated(BillingResult billingResult, IList<Purchase> purchases)
        {
            if (billingResult.ResponseCode == BillingResponseCode.Ok && purchases != null)
            {
                foreach (var purchase in purchases)
                {
                    // Handle each purchase
                    if (purchase.PurchaseState == PurchaseState.Purchased)
                    {
                        // Purchase was successful
                        subscriptionManager.subscriptionCallback?.Invoke(true);
                    }
                    else if (purchase.PurchaseState == PurchaseState.Pending)
                    {
                        // Purchase is pending, handle accordingly
                        // Example: Show a message to the user
                    }
                    else
                    {
                        // Purchase failed or was canceled
                        subscriptionManager.subscriptionCallback?.Invoke(false);
                    }
                }
            }
            else
            {
                // Handle purchase update failure
                subscriptionManager.subscriptionCallback?.Invoke(false);
            }
        }
    }
}
Developer technologies .NET .NET MAUI
{count} votes

2 answers

Sort by: Most helpful
  1. Ricardo de Jesús Veloz Cleto 0 Reputation points
    2024-08-14T02:34:57.8466667+00:00

    //My solution

    //Class:

    #if ANDROID

    using Android.BillingClient.Api;

    using System.Collections.Generic;

    using static Android.BillingClient.Api.BillingFlowParams;

    using static Android.BillingClient.Api.QueryProductDetailsParams;

    public class BillingService

    {

    private static BillingClient billingClient;
    
    private static PurchasesUpdatedListener purchasesUpdatedListener;
    
    public BillingService()
    
    {
    
        try
    
        {
    
            purchasesUpdatedListener = new PurchasesUpdatedListener();
    
            billingClient = BillingClient.NewBuilder(Microsoft.Maui.MauiApplication.Context)
    
                .SetListener(purchasesUpdatedListener)
    
                .EnablePendingPurchases(PendingPurchasesParams.NewBuilder()
    
                    .EnableOneTimeProducts()
    
                    .Build())
    
                .Build();
    
            StartConnection();
    
        }
    
        catch (Exception e)
    
        {
    
            Console.WriteLine(e.Message);
    
        }
    
    }
    
    private async void StartConnection()
    
    {
    
        billingClient.StartConnection(new BillingClientStateListener());
    
        await WaitForConnectionAsync();
    
    }
    
    private async Task WaitForConnectionAsync()
    
    {
    
        // Verificar el estado de conexión hasta que sea Connected
    
        while (billingClient.ConnectionState != ConnectionState.Connected)
    
        {
    
            Console.WriteLine("Esperando la conexión...");
    
            await Task.Delay(500); // Esperar 500 ms antes de volver a verificar
    
        }
    
        Console.WriteLine("Conexión establecida.");
    
        // Aquí puedes continuar con la lógica que requiere que la conexión esté establecida
    
    }
    
    public async Task<ProductDetails> QueryProductDetailsAsync(string productId)
    
    {
    
        var productList = new List<Product>
    
        {
    
            Product.NewBuilder()
    
                .SetProductId(productId)
    
                .SetProductType(BillingClient.SkuType.Subs) // O BillingClient.SkuType.InApp para productos no suscripción
    
                .Build()
    
        };
    
        var queryProductDetailsParams = QueryProductDetailsParams.NewBuilder()
    
            .SetProductList(productList)
    
            .Build();
    
        var result = await billingClient.QueryProductDetailsAsync(queryProductDetailsParams);
    
        if (result != null)
    
        {
    
            foreach (var item in result.ProductDetails)
    
            {
    
                Console.WriteLine("->->->->->->->->->");
    
                Console.WriteLine(item.ProductId);
    
                Console.WriteLine(item.Name);
    
                Console.WriteLine(item.ProductType);
    
                Console.WriteLine(item.Title);
    
                Console.WriteLine(item.Description);
    
                Console.WriteLine(item.GetSubscriptionOfferDetails());
    
                Console.WriteLine("<-<-<-<-<-<-<-<-<-");
    
                return item;
    
            }
    
        }
    
        return null;
    
    }
    
    public async Task<bool> CheckSubscriptionStatusAsync()
    
    {
    
        // Crear los parámetros de consulta para las compras
    
        var queryPurchasesParams = QueryPurchasesParams.NewBuilder()
    
            .SetProductType(BillingClient.SkuType.Subs) // Establecer el tipo de producto como suscripción
    
            .Build();
    
        // Consultar las suscripciones
    
        var purchasesResult = await billingClient.QueryPurchasesAsync(queryPurchasesParams);
    
        if (purchasesResult.Result.ResponseCode  ==  BillingResponseCode.Ok )
    
        {
    
            var purchases = purchasesResult.Purchases;
    
            // Verificar si el usuario tiene una suscripción activa
    
            foreach (var purchase in purchases)
    
            {
    
                Console.WriteLine("PurchaseState {0}", purchase.PurchaseState);
    
                if (purchase.PurchaseState == PurchaseState.Purchased)
    
                {
    
                    // La suscripción está activa
    
                    Console.WriteLine("El usuario ya está suscrito.");
    
                    return true;
    
                }
    
            }
    
            // No hay suscripción activa
    
            Console.WriteLine("El usuario no está suscrito.");
    
        }
    
        else
    
        {
    
            // Manejar el error al consultar las compras
    
            Console.WriteLine($"Error al consultar las compras: {purchasesResult.Result.ResponseCode}");
    
        }
    
        return false;
    
    }
    
    public async Task<bool> LaunchBillingFlowAsync(string productId, string offerToken)
    
    {
    
        // Obtener los detalles del producto
    
        var productDetails = await QueryProductDetailsAsync(productId);
    
        // Construir los parámetros de flujo de facturación
    
        var productDetailsParamsList = new List<ProductDetailsParams>
    
        {
    
            ProductDetailsParams.NewBuilder()
    
                .SetProductDetails(productDetails)
    
                .SetOfferToken(offerToken)
    
                .Build()
    
        };
    
        var billingFlowParams = BillingFlowParams.NewBuilder()
    
            .SetProductDetailsParamsList(productDetailsParamsList)
    
            .Build();
    
        // Obtener la actividad actual
    
        var activity = Microsoft.Maui.ApplicationModel.Platform.CurrentActivity;
    
        // Iniciar el flujo de facturación
    
        var billingResult = billingClient.LaunchBillingFlow(activity, billingFlowParams);
    
        // Manejar el resultado del flujo de facturación
    
        if (billingResult.ResponseCode == BillingResponseCode.Ok)
    
        {
    
            Console.WriteLine("Flujo de facturación iniciado correctamente.");
    
            return true;
    
        }
    
        else
    
        {
    
            Console.WriteLine($"Error al iniciar el flujo de facturación: {billingResult.ResponseCode}");
    
            return false;
    
        }
    
    }
    
    private class BillingClientStateListener : Java.Lang.Object, IBillingClientStateListener
    
    {
    
        public void OnBillingSetupFinished(BillingResult billingResult)
    
        {
    
            if (billingResult.ResponseCode == BillingResponseCode.Ok)
    
            {
    
                // El BillingClient está listo. Puedes consultar compras aquí.
    
            }
    
        }
    
        public void OnBillingServiceDisconnected()
    
        {
    
            // Intentar reiniciar la conexión en la próxima solicitud
    
            // Llama a StartConnection() nuevamente si es necesario
    
        }
    
    }
    
    private class PurchasesUpdatedListener : Java.Lang.Object, IPurchasesUpdatedListener
    
    {
    
        public void OnPurchasesUpdated(BillingResult billingResult, IList<Purchase> purchases)
    
        {
    
            // Implementar la lógica para manejar las compras actualizadas
    
        }
    
    }
    

    }

    #endif

    //in public static class MauiProgram:

    #if ANDROID

        public static BillingService bservice;
    

    #endif

    //invocation:

        //subscriptionType is your subscription ID   
        private async Task OriginalAndroidBilling(string subscriptionType)
    
        {
    

    #if ANDROID

            //BillingService bservice = new BillingService();
    
            var IsSuscribed = await MauiProgram.bservice.CheckSubscriptionStatusAsync();
    
            if (!IsSuscribed)
    
            {
    
                var SubsDetails = await MauiProgram.bservice.QueryProductDetailsAsync(subscriptionType);
    
                var BillFlow = await MauiProgram.bservice.LaunchBillingFlowAsync(subscriptionType, "NA");
    
            }
    
            
    

    #endif

        }
    

  2. Adin 6 Reputation points
    2025-02-18T09:00:01.63+00:00

    I've created a sample .Net 9.0 Maui app with Google Pay. Perhaps it will help you https://github.com/theadin/MauiGooglePay

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.