Google published a brand new version to handle the payments in Android but after searching quite a while I cannot find a single example or tutorial from someone who succeeded im
I'm begginer on Android Studio and i'm implementing the billing library 2.1.0. After a week of reading the android studio documentation and many tutorial about billing library I have made this java class, but i feel is not good enough, at least it do what it has to do. If you find any way to improve it, comment it. Thanks:
1.- Class Pago.java:
package com.example.billing;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.example.R;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.SERVICE_TIMEOUT;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.OK;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.USER_CANCELED;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.BILLING_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ITEM_UNAVAILABLE;
import static com.android.billingclient.api.BillingClient.BillingResponseCode.ERROR;
import static com.android.billingclient.api.BillingClient.SkuType.INAPP;
import java.util.ArrayList;
import java.util.List;
public class Pagos implements PurchasesUpdatedListener, BillingClientStateListener, SkuDetailsResponseListener, ConsumeResponseListener {
private BillingClient billingClient;
private Context contextPago;
private String skuId;
private List misProductos;
// Constructor de la clase Pagos
public Pagos(Context context) {
contextPago = context;
}
// Asigna el sku del producto que se quiere comprar
public void comprar(String skuId) {
this.skuId = skuId;
configurarBillingClient();
}
// Configura el Billing Client para iniciar la conexión con Google Play Console
private void configurarBillingClient() {
// 1. Configura el Billing Client
billingClient = BillingClient.newBuilder(contextPago)
.enablePendingPurchases()
.setListener(this)
.build();
// 2. Inicia la conexión y asigna los Listener
billingClient.startConnection(this);
}
@Override
// Evento salta al llamar billingClient.startConnection()
public void onBillingSetupFinished(BillingResult billingResult) {
// Busca compras en el Servidor de Google y las marca como consumidas
consumeCompras();
// Verifica que la versión de Play Store sea compatible con INAPP
if (!billingClient.isReady()) {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return;
}
// Verifica que la versión de Play Store sea compatible con Suscripciones
// if (billingClient.isFeatureSupported(SUBSCRIPTIONS).getResponseCode() != OK) {
// String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_VERSIÓN_NO_COMPATIBLE);
// Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
// return; //GooglePlayNoSoportaComprasDeSuscripciones
// }
// Verifica que la Configuración se haya hecho bien, sino muestra mensaje de error
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
consultaProductos();
}
}
// Asigna los elemento que se consultarán a Google y los envía con querySkuDetailsAsync
private void consultaProductos() {
// Inicializa constantes
String ITEM_SKU_1 = "android.test.item_unavailable";
String ITEM_SKU_2 = "android.test.canceled";
String ITEM_SKU_3 = "android.test.purchased";
String ITEM_SKU_4 = "donar";
String ITEM_SKU_5 = "prueba.1";
// Agrega los productos que se consultarán a Google
List skuList = new ArrayList<>();
skuList.add(ITEM_SKU_1);
skuList.add(ITEM_SKU_2);
skuList.add(ITEM_SKU_3);
skuList.add(ITEM_SKU_4);
skuList.add(ITEM_SKU_5);
// TODO Cambiar el ingreso manual de items por una consulta a servidor propio de backend seguro.
SkuDetailsParams.Builder skuDetailsParams = SkuDetailsParams
.newBuilder()
.setSkusList(skuList)
.setType(INAPP);
// Envía consulta a Google y devuelve el listado de productos mediante onSkuDetailsResponse
billingClient.querySkuDetailsAsync(skuDetailsParams.build(), this);
}
@Override
// Evento salta cuando Google envía los detalles de los Productos en Venta
public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
if (skuDetailsList != null) {
misProductos = skuDetailsList;
muestraDialogoCompra();
} else {
String mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_NO_SKUDETAILSLIST);
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
}
}
}
// Lanza el dialogo de compra de Google
private void muestraDialogoCompra() {
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(getSkuIdDetails())
.build();
billingClient.launchBillingFlow((Activity) contextPago, flowParams);
}
// Obtiene el Producto que se comprará según el Sku ingresado mediante comprar(sku);
private SkuDetails getSkuIdDetails() {
if (misProductos == null) return null;
for (SkuDetails skuProducto : misProductos) {
if (skuId.equals(skuProducto.getSku())) return skuProducto;
}
return null;
}
@Override
// Evento salta cuando se finaliza el Proceso de compra
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List list) {
if (verificaResponseCode(billingResult.getResponseCode()) == OK) {
// Validar compra con consulta a Google para evitar ingeniería inversa de hackers
if (validaCompra()) {
// Compra confirmada
Log.i("Pagos", "Compra encontrada en servidor");
} else {
// Compra no encontrada: Mensaje de error - Revocar privilegios
Log.i("Pagos", "Compra no encontrada posible hacker");
}
consumeCompras();
}
}
// Valida la compra y Devuelve True si encuentra la compra del usuario en el Servidor de Google
private boolean validaCompra() {
List purchasesList = billingClient.queryPurchases(INAPP).getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
if (purchase.getSku().equals(skuId)) {
return true;
}
}
}
return false;
}
// Busca compras en el Servidor de Google y las marca como consumidas
private void consumeCompras() {
Purchase.PurchasesResult queryPurchases = billingClient.queryPurchases(INAPP);
if (queryPurchases.getResponseCode() == OK) {
List purchasesList = queryPurchases.getPurchasesList();
if (purchasesList != null && !purchasesList.isEmpty()) {
for (Purchase purchase : purchasesList) {
ConsumeParams params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.consumeAsync(params, this);
}
}
}
}
@Override
// Evento salta cuando se ha consumido un producto, Si responseCode = 0, ya se puede volver a comprar
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() == OK) {
Log.i("Pagos", "Token de Compra: " + purchaseToken + " consumida");
} else {
Log.i("Pagos", "Error al consumir compra, responseCode: " + billingResult.getResponseCode());
}
}
@Override
// Evento salta cuando se pierde la conexión durante una compra
public void onBillingServiceDisconnected() {
billingClient.startConnection(this);
}
// Verifica que el estado del responseCode sea OK, si no muestra mensaje de Error
private int verificaResponseCode(int responseCode) {
if (responseCode == OK) return OK;
if (responseCode == USER_CANCELED) return USER_CANCELED;
String mensaje = "";
switch (responseCode) {
case SERVICE_TIMEOUT:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_SERVICE_TIMEOUT);
break;
case BILLING_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_BILLING_UNAVAILABLE);
break;
case ITEM_UNAVAILABLE:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ITEM_UNAVAILABLE);
break;
case ERROR:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR);
break;
default:
mensaje = contextPago.getString(R.string.PAGOS_MENSAJE_ERROR) + " código: " + responseCode;
break;
}
Toast.makeText(contextPago, mensaje, Toast.LENGTH_LONG).show();
return responseCode;
}
}
3.- Manifest
4.- build.gradle
// Google Play Billing Library
implementation 'com.android.billingclient:billing:2.1.0'
5.- Usage, place this code wherever you want to show the billing component:
private final String SKU_UNAVAILABLE = "android.test.item_unavailable";
private final String SKU_CANCELED = "android.test.canceled";
private final String SKU_PURCHASED = "android.test.purchased";
private final String SKU_DONAR = "donar";
private void donar() {
Pagos pagos = new Pagos(this);
pagos.comprar(SKU_DONAR);
cargandoDialogoCompra(true);
}
You can change SKU_DONAR, to SKU_UNAVAILABLE, SKU_CANCELED, SKU_PURCHASED because these are items for testing purposes and as i read its not neccesary to add them to play console
6.- Google Play console
Presencia en Google Play Store -> Productos integrados en la aplicación -> Productos administrados:
Donación (donar) PEN 9.99
That's all, please improve my code, thanks to you all.