Comprar dentro de la aplicación en Flutter/Android con in_app_purchase - Google Play

Video thumbnail

Te voy a mostrar un poco del código que a mí me funciona y que puedes implementar para realizar un pago mediante Google Play.

En este caso, como ves, el emulador no permite hacer los pagos mediante la Google Play:

Pruebas Internas en la Google Play (Android) y Pasos para el Testing de los pagos

Esto lo estoy haciendo en un dispositivo real. Fíjate que aquí se puede realizar la compra, pero si estás usando un emulador —como en este caso— te muestra un bonito mensaje como el que ves en pantalla, que dice que no puedes acceder a Google Play.

Instalación de la dependencia

Lo primero que tienes que hacer es instalar la dependencia in_app_purchase, que es la que usamos para realizar pagos mediante Google Play. También se puede configurar para Apple Pay —creo que así se llama—, aunque no lo he hecho aún. Eso es otro tema:

pubspec.yaml

in_app_purchase: 

Tengo un array (una lista) con los productos. En algún lugar tienes que tener esos productos definidos, ya sea que los traigas desde un API o —como en mi caso— los tengas fijos aquí. Como mi producto puede tener cupones, descuentos, etc., no quiero estar variando cada rato la configuración en la plataforma:

final List<String> _productsId = [
    '',
    'curso_libro_1_dolar',
    'curso_libro_2_dolar',
    ***
    'curso_libro_29_dolar',
    'curso_libro_30_dolar',
  ];

Aquí tengo un listado de productos que vendo, hasta 30 dólares. Obviamente, deben tener su equivalente creado en Google Play:

https://play.google.com/console/u/<user>/developers/

Recuerda que debes crearlos en el apartado de "Seleccionar aplicación" → "Productos" → "Productos integrados en la aplicación", y ahí empiezas a crear productos como un desquiciado, como si no hubiera mañana.

Proceso para realizar el pago por la Google Play

Primero llamamos a initialize. Aquí lo primero es buscar si el producto existe. Si no lo tengo, no hay nada que hacer y simplemente cancelo la operación:

Future<void> _initialize() async {
  //*** Verificamos que el array no se salga de rango 
  if (_productsId.length <= priceKey) {
    showToastMessage(context, LocaleKeys.productNotAvailable.tr());
    return;
  }
***  
}

Luego verifico si las compras están disponibles. Esto se hace con InAppPurchase.instance.isAvailable. Si no está disponible, no hay nada que hacer.

  //*** Verifica si la Google Play
  /*
  Google Play Store está instalada y funcionando,
  la cuenta tiene acceso a compras, 
  el dispositivo puede conectarse con el servicio de facturación de Google.
    */
  final bool isAvailable = await InAppPurchase.instance.isAvailable();
  if (!isAvailable) {
    showToastMessage(context, LocaleKeys.googlePlayNoAvailable.tr());
    return;
  }

Después de eso, si la tienda está disponible, buscamos el producto. Si no lo encontramos, tampoco podemos continuar. Esto puede deberse a un error mío o algo similar. Para buscar el producto usamos QueryProductDetails. Hay más métodos, pero este es el que necesito. Si no se encuentra, me devuelve un notFoundID, y entonces muestro un error y detengo el proceso:

  //*** Busca el producto a vender
  Set<String> kIds = {_productsId[priceKey]}; // para produccion
  final ProductDetailsResponse response = await InAppPurchase.instance
      .queryProductDetails(kIds);
  // no existe el producto, no lo cree en la Google Play?
  if (response.notFoundIDs.isNotEmpty) {
    showToastMessage(context, LocaleKeys.googlePlayNoProduct.tr());
    return;
  }

Aquí configuro un listener para saber lo que sucede. Suceden muchos eventos, pero normalmente los importantes son el de éxito (done) o el de error.

  //*** Escuchar eventos solo una vez
  // se usa para detectar cambios en la compra, en la app,
  // para cuando sea completada asignar el producto al usuario
  InAppPurchase.instance.purchaseStream.listen(
    _listenToPurchaseUpdated,
    onDone: () {
      // _buyProduct(response.productDetails.first);
    },
    onError: (error) {
      showToastMessage(context, "${LocaleKeys.anErrorOccurred.tr()}: $error");
    },
  );

Por ejemplo, el error se produce cuando intentas comprar un producto que ya tienes, y por eso recibes el mensaje de error. Una vez sorteados todos esos errores y listeners, llamo finalmente al proceso para hacer la compra:

  // vender el producto
  _buyProduct(response.productDetails.first);
  ***
  
//*** Muestra la pantalla de pago
void _buyProduct(ProductDetails productDetails) {
  final purchaseParam = PurchaseParam(productDetails: productDetails);
  // buyNonConsumable es un producto que no expira
  _iap.buyConsumable(purchaseParam: purchaseParam);
  // _iap.buyNonConsumable(purchaseParam: purchaseParam);
}

Tipo de productos: consumibles vs. no consumibles

Para el producto que el usuario quiere comprar. Tienes dos opciones:

  1. Producto no consumible: Si haces algo como lo que yo hago —productos etiquetables—, no funciona bien. Por ejemplo, si tengo dos libros de 10 dólares y el usuario compra uno, al intentar comprar el otro le dirá que ya lo tiene. Eso crea un bloqueo.
  2. Producto consumible: Este no aplica tanto a mi caso, pero es lo que terminé usando. Es más útil para cosas como estrellitas o monedas dentro de juegos. Los productos no consumibles serían cosas reales como libros, zapatos, una casa, etc.
_iap.buyConsumable(purchaseParam: purchaseParam); // buyConsumable es un producto que expira
_iap.buyNonConsumable(purchaseParam: purchaseParam); // buyNonConsumable es un producto que no expira

Acepto recibir anuncios de interes sobre este Blog.

Te explico como puedes crear una aplicación que realice pagos mediante la Google Play, al no poder emplear otras APIs, como las de PayPal y Stripe.

- Andrés Cruz

In english