Android In-App Billing. Empezamos con el código (IV)

Tras la presentación inicial, la descripción de los tipos de productos y una vez listo el entorno de trabajo, es hora de ponernos ya con el código.

Es importante, como ya mencioné en el artículo anterior, tener a mano la clave de desarrollador.

NOTA

A modo de tutorial, supondremos que el elemento "Premium" declarado en el artículo anterior es un producto administrado

La clase InAppBillingService

La clase InAppBillingService, la cual viene definida en el archivo 'com.android.vending.billing.InAppBillingService.aidl' nos provee de una capa de abstracción que nos facilitará mucho la comunicación con el servicio de pagos de Google Play.

Como podemos observar en el pack que puse para descargar en el post anterior podemos encontrar una clase llamada IabHelper, la cual contiene la mayor parte del trabajo.
Vamos a ver como implementarla.

Preparando la Activity/Fragment

Cuando declaremos la Activity o el Fragment donde vayamos a utilizar el servicio de facturación de Google Play, deberemos añadir dos interfaces a la declaración, quedando:


import com.android.vending.billing.util.*;

public class PurchaseActivity
 extends SherlockFragmentActivity
 implements IabHelper.OnIabSetupFinishedListener, IabHelper.OnIabPurchaseFinishedListener

Estos dos listeners son los que utilizaremos para definir el comportamiento que deseamos realizar (consulta, compra... lo iremos viendo a lo largo de los tutoriales).

A modo de ejemplo implementaremos un código sencillo que al pulsar un botón nos inicie la compra del artículo anterior.

Primero pondré el código y despues lo iremos desgranando poco a poco

public class PurchaseActivity
 extends SherlockFragmentActivity
 implements IabHelper.OnIabSetupFinishedListener, IabHelper.OnIabPurchaseFinishedListener{
 
     private IabHelper billingHelper;
     private Button boton;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_purchase);
		boton = (Button)findViewById(R.id.boton);
        boton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startBuyProcess();
            }
        });

    }
    
    private void startBuyProcess(){
    	String clave = "NUESTRA-CLAVE-DE-DESARROLLADOR";
        
        billingHelper = new IabHelper(this, clave);
        billingHelper.startSetup(this);
    }
    
    
    
     @Override
    public void onIabSetupFinished(IabResult result) {
        if (result.isSuccess()) {

            try{
                if(billingHelper.queryInventory(true, null).hasPurchase(Premium.SKU)){
                    Toast.makeText(this, "Ya tienes este elemento!", Toast.LENGTH_SHORT).show();
                } else {
                    compraElemento();
                }

            } catch(IabException e){
                e.printStackTrace();
            }

        } else {

            errorAlIniciar();
        }

    }
    
    
    protected void errorAlIniciar() {
        Toast.makeText(PurchaseActivity.this, "Error al intentar iniciar la compra", Toast.LENGTH_SHORT).show();
    }
    
    
    protected void compraElemento() {
        purchaseItem(Premium.SKU);
    }
    
	
    protected void purchaseItem(String sku) {
        billingHelper.launchPurchaseFlow(this, sku, 123, this);
	}
    
    
    @Override
    public void onIabPurchaseFinished(IabResult result, Purchase info) {
        if (result.isFailure()) {
            compraFallida();
        } else if (Premium.SKU.equals(info.getSku())) {
            compraCorrecta(result, info);
        }

    }
    
    /*
     * COSAS QUE QUERAMOS HACER CUANDO SE HAYA
     * ADQUIRIDO EL PRODUCTO CON EXITO
     */
    protected void compraCorrecta(IabResult result, Purchase info){
        // Consumimos los elementos a fin de poder probar varias compras
        billingHelper.consumeAsync(info, null);
        
    }
    
    /*
     * COSAS QUE QUERAMOS HACER CUANDO EL PRODUCTO
     * NO HAYA SIDO ADQUIRIDO
     */
    
    protected void compraFallida(){
    
    }
    
    
    //LIMPIAMOS
    @Override
    protected void onDestroy() {
        disposeBillingHelper();
        super.onDestroy();
    }

    private void disposeBillingHelper() {
        if (billingHelper != null) {
            billingHelper.dispose();
        }
        billingHelper = null;
    }
    
 
 }

ENLACE PARA EVITAR PROBLEMAS DE FORMATO

Comprendiendo el código

Una vez escrita toda la retahila de código, vamos a analizarlo poco a poco comprendiendo qué hace cada función.
(Obviaremos el método onCreate, ya que no es el objetivo de este tutorial explicarlo).

Iniciando la comunicación

private void startBuyProcess(){
    String clave = "NUESTRA-CLAVE-DE-DESARROLLADOR";
        
    billingHelper = new IabHelper(this, clave);
    billingHelper.startSetup(this);
}

En la variable clave deveremos guardar nuestra clave de desarrollador.
Si no la tienes, te recomiendo que leas el post anterior.

El objeto billingHelper tiene un método llamado startSetup, el cual se encargará de iniciar toda la comunicación con el servicio de facturación de Google Play.

Una vez finalizada la ejecución de este método, éste lanzará automáticamente el método onIabSetupFinished, el cual es obligatorio tener, ya que ha sido declarado como interfaz en la declaración de la clase.

@Override
public void onIabSetupFinished(IabResult result) {
  if (result.isSuccess()) {
  
    try{
      if(billingHelper.queryInventory(true, null).hasPurchase(Premium.SKU)){
      
        Toast.makeText(this, "Ya tienes este elemento!", Toast.LENGTH_SHORT).show();
        
      } else {
        compraElemento();
      }
    } catch(IabException e){
      e.printStackTrace();
    }
    
  } else {
    errorAlIniciar();
  }
}

Vamos a ir paso a paso comprendiendo qué hace cada paso de la función.

El método onIabSetupFinished recibe un objeto de tipo IabResult como parámetro, a partir del cual podemos obtener si la comunicación ha sido exitosa o no mediante el método isSuccess()

Si no ha sido exitosa, lanzaremos el método errorAlIniciar, el cuál (en este tutorial) simplemente mostrará un Toast indicando que la comunicación no se pudo llevar a cabo correctamente.

NOTA

Esto puede ser debido, por ejemplo, a que el usuario no disponga de la última versión disponible de la aplicación de Google Play

En cambio, si la inicialización de la comunicación ha sido exitosa, comprobaremos (línea 6) si el usuario tiene en su posesión el elemento que queremos que adquiera (para evitar compras duplicadas).
Eso se hace mediante el método
billingHelper.queryInventory(true, null).hasPurchase("SKU-DEL-ELEMENTO"))
el cual devuelve un booleano.

Este método puede lanzar una excepción de tipo IabException, de modo que la consulta estará hecha dentro de un try-catch.

Finalmente, si la conexion ha sido correcta y el usuario no tiene el elemento, iniciaremos el proceso de compra.

Iniciando la compra

protected void compraElemento() {
    purchaseItem(Premium.SKU);
}

En la función compraElemento, como vemos podemos iniciar la compra del elemento que deseemos, basta con pasar como argumento a la función purchaseItem el SKU del producto que deseamos adquirir.

NOTA

Sería interesante declarar esta función de forma que reciba un String, ya que de ese modo podemos iniciar la compra de cualquier elemento con la misma llamada a esta función, sólo pasándole el SKU en su llamada.

Cuando finalice la transacción, tanto si ésta ha sido exitosa como si no lo ha sido, se llamará automáticamente al método onIabPurchaseFinished, el cual de nuevo habíamos declarado en la declaración inicial de la clase.

Compra finalizada. ¿Todo bien?

@Override
public void onIabPurchaseFinished(IabResult result, Purchase info) {

  if (result.isFailure()) {
    compraFallida();

  } else if (Premium.SKU.equals(info.getSku())) {
    compraCorrecta(result, info);
  }
}

Como vemos, onIabPurchaseFinished recibe un objeto de tipo IabResult, el cual contendrá la información de si la compra ha sido exitosa o no, y además un objeto de tipo Purchase, el cuál contendrá la información de la compra.

Mediante el método isFailure() del objeto IabResult podremos saber si la compra se ha llegado a realizar o no.
Si no se ha realizado, (es decir, isFailure devuelve TRUE) llamaremos al método compraFallida(), en el cual podremos hacer lo que creamos conveniente.

En cambio, si se ha realizado y además el producto adquirido coincide con el que habíamos solocitado adquirir (como pequeña medida extra de seguridad), lanzaremos el método compraCorrecta().

Esta comprobación se hace comparando el SKU del producto a adquirir y el SKU del producto adquirido (obtenido del objeto Purchase recibido por el método).

Compra correcta!

Finalmente, si todo ha ido bien, se llamará al método compraCorrecta()

protected void compraCorrecta(IabResult result, Purchase info){

// Consumimos los elementos a fin de poder probar varias compras
  billingHelper.consumeAsync(info, null);
  
}

Como estamos haciendo pruebas, consumiremos el artículo Premium obtenido, haciendo así que podamos volver a adquirirlo (si no, en posteriores ocasiones en la comprobación de si poseemos ya el artículo, devolvería siempre que ya lo tenemos).

Más adelante hablaré de cómo hacer las pruebas de compra de forma segura y sin dinero real implicado.

RESUMEN

Y hasta aquí la explicación general de cómo funciona la clase InAppBillingService y de cómo implementar sus métodos.

Aquí os dejo un pequeño esquema que, una vez comprendido el código explicado, os ayudará a ilustrar cualquier posible duda que os pueda quedar:

DIAGRAMA