My app is supposed to perform a simple subscription via Google Play Service once User clicks on the Subscribe button.

Please advice what's wrong?

Entire subscription logic is sitting in a single class.

I've done all the necessarycsteps from the Google Play Console side: single subscription id, single plan id, single offer id. Should I use any other id besides subscription id in the code?

Opening of a Subscription dialog returns 'BILLING_UNAVAILABLE'.

Clicking on a Subscribe button returns 'launchBillingFlow: BillingClient is not ready'.

Same actions on a mobile phone do nothing.

Internet connection is ok as the AdMob testing ad works fine.

package com.something.something;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.MutableLiveData;

import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.QueryProductDetailsParams;
import com.android.billingclient.api.QueryPurchasesParams;
import com.google.common.collect.ImmutableList;

import java.util.List;
public class MenuSubscription extends DialogFragment implements BillingClientStateListener {
    private static final String TAG = "MenuSubscription";
    private static final String SUBSCRIPTION_SKU = "unique_subscription_id_created_via_google_play_console";
    public SingleLiveEvent<List<Purchase>> purchaseUpdateEvent = new SingleLiveEvent<>();
    public MutableLiveData<List<Purchase>> purchases = new MutableLiveData<>();
    public MutableLiveData<ProductDetails> skuDetailsLiveData = new MutableLiveData<>();
    private BillingClient billingClient;

    private final PurchasesUpdatedListener purchasesUpdatedListener = (billingResult, purchases) -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
            processPurchases(purchases);
        } else {
            handleAllBillingErrors(billingResult);
}
    };

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        setupBillingClient();
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
        View view = requireActivity().getLayoutInflater().inflate(R.layout.subscription, null);
        Button button_subscribe = view.findViewById(R.id.subscribe_button);
        button_subscribe.setOnClickListener(v -> launchBillingFlow());
        return new AlertDialog.Builder(requireActivity())
                .setView(view)
                .create();
    }

    private void handleAllBillingErrors(BillingResult billingResult){
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
            showMessage("SUCCESS");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("User cancelled the purchase after showing plans to the user");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED) {
            showMessage("Feature not supported on this phone");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.DEVELOPER_ERROR) {
            showMessage("DEVELOPER_ERROR");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.BILLING_UNAVAILABLE) {
            showMessage("BILLING_UNAVAILABLE");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            showMessage("ITEM_ALREADY_OWNED");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_NOT_OWNED) {
            showMessage("ITEM_NOT_OWNED");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_UNAVAILABLE) {
            showMessage("ITEM_UNAVAILABLE");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_DISCONNECTED) {
            showMessage("SERVICE_DISCONNECTED");
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE) {
            showMessage("SERVICE_UNAVAILABLE");
        } else {
            showMessage("Something went wrong while setting up purchase after showing plans to the user");
        }
    }


    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        super.onDismiss(dialog);
        destroy();
    }

    public void destroy() {
        Log.d(TAG, "ON_DESTROY");
        if (billingClient.isReady()) {
            Log.d(TAG, "BillingClient can only be used once -- closing connection");
            showMessage("BillingClient can only be used once -- closing connection");
            billingClient.endConnection();
        }
    }

    @Override
    public void onBillingSetupFinished(BillingResult billingResult) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            queryProductDetails();
            queryPurchases();
        }
    }

    @Override
    public void onBillingServiceDisconnected() {
        Log.w(TAG, "Billing service disconnected. Retrying with exponential backoff...");
        showMessage("Billing service disconnected. Retrying with exponential backoff...");
    }

    public void onSkuDetailsResponse(@NonNull BillingResult billingResult, @Nullable List<ProductDetails> productDetailsList) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            if (productDetailsList != null && !productDetailsList.isEmpty()) {
                ProductDetails productDetails = productDetailsList.get(0);
                skuDetailsLiveData.postValue(productDetails);
            }
        }
    }

    public void queryPurchases() {
        if (!billingClient.isReady()) {
            Log.e(TAG, "queryPurchases: BillingClient is not ready");
            showMessage("queryPurchases: BillingClient is not ready");
        }
        QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.newBuilder()
                .setProductType(BillingClient.ProductType.SUBS)
                .build();
        billingClient.queryPurchasesAsync(queryPurchasesParams, (billingResult, purchasesList) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                processPurchases(purchasesList);
            } else {
                handleAllBillingErrors(billingResult);
}
        });
    }

    private void launchBillingFlow() {
        if (!billingClient.isReady()) {
            Log.e(TAG, "launchBillingFlow: BillingClient is not ready");
            showMessage("launchBillingFlow: BillingClient is not ready");
            return;
        }
        queryProductDetails();
    }


    private void processPurchases(List<Purchase> purchasesList) {
        if (purchasesList != null) {
            Log.d(TAG, "processPurchases: " + purchasesList.size() + " purchase(s)");
            showMessage("processPurchases: " + purchasesList.size() + " purchase(s)");
        } else {
            Log.d(TAG, "processPurchases: with no purchases");
            showMessage("processPurchases: with no purchases");
        }
        purchaseUpdateEvent.postValue(purchasesList);
        purchases.postValue(purchasesList);
        if (purchasesList != null) {
            logAcknowledgementStatus(purchasesList);
        }
    }

    private void logAcknowledgementStatus(@NonNull List<Purchase> purchasesList) {
        int ack_yes = 0;
        int ack_no = 0;
        for (Purchase purchase : purchasesList) {
            if (purchase.isAcknowledged()) {
                ack_yes++;
            } else {
                ack_no++;
            }
        }
        Log.d(TAG, "logAcknowledgementStatus: acknowledged=" + ack_yes + " unacknowledged=" + ack_no);
        showMessage("logAcknowledgementStatus: acknowledged=" + ack_yes + " unacknowledged=" + ack_no);
    }

    public void queryProductDetails() {
        ImmutableList<QueryProductDetailsParams.Product> productList = ImmutableList.of(QueryProductDetailsParams.Product.newBuilder()
                .setProductId(SUBSCRIPTION_SKU)
                .setProductType(BillingClient.ProductType.SUBS)
                .build());
        QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
                .setProductList(productList)
                .build();
        billingClient.queryProductDetailsAsync(params, (billingResult, productDetailsList) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                if (!productDetailsList.isEmpty()) {
                    onSkuDetailsResponse(billingResult, productDetailsList);
                }
            } else {
                handleAllBillingErrors(billingResult);}
        });
    }

    private BillingClient initializeBillingClient() {
        BillingClient billingClient = BillingClient.newBuilder(requireContext())
                .setListener(purchasesUpdatedListener)
                .enablePendingPurchases()
                .build();
        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // BillingClient is ready, you can now launch the billing flow
                    launchBillingFlow();
                } else {
                    handleAllBillingErrors(billingResult);}
            }
            @Override
            public void onBillingServiceDisconnected() {
                // Retry or handle disconnection as needed
                Log.e(TAG, "Billing service disconnected. Retrying with exponential backoff...");
                showMessage("Billing service disconnected. Retrying with exponential backoff...");
            }
        });

        return billingClient;
    }

    public void setupBillingClient() {
        billingClient = initializeBillingClient();
        billingClient = BillingClient.newBuilder(requireContext()).setListener(purchasesUpdatedListener).enablePendingPurchases().setListener(purchasesUpdatedListener).build();
        billingClient.startConnection(new BillingClientStateListener() {
            @SuppressLint("SuspiciousIndentation")
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    handleAllBillingErrors(billingResult);
                    queryProductDetails();
                } else
                    handleAllBillingErrors(billingResult);
            }
            @Override
            public void onBillingServiceDisconnected() {
                Log.e(TAG, "Billing service disconnected. Retrying with exponential backoff...");
                showMessage("Billing service disconnected. Retrying with exponential backoff...");
            }
        });
    }

    private void showMessage(String message) {
        Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
    }
}
0

There are 0 best solutions below