Añadir compras de aplicación (iOS/Android) en React Native

Tiempo de lectura: 6 minutos

Hoy os voy a enseñar cómo agregar compras de aplicacion (APPPurcharses) en tu aplicación con React Native.

Lo primero que vamos a hacer es instalar la dependencia que vamos a utilizar para agregar esta característica: react-native-iap

npm install --save react-native-iap

Una vez instalada, vamos a configurar un componente para compras:

import React, { useEffect, useState } from "react";
import { StyleSheet, Text, Platform, View } from "react-native";
import * as RNIap from 'react-native-iap';

Importamos los componentes y la libreria RNIap de react-native-iap.

const productosSolicitados = Platform.select({
    ios: [
        'codigo.compra',
        'codigo.compra.2'
    ],
    android: [
        'codigo.compra',
        'codigo.compra.2'
    ]
});

Creamos un array que depende de la plataforma (ios/android) y dentro añadimos los códigos de compras que configuramos en Google Play Console o iOS developer.

Ahora vamos al render y creamos la conexión con el API de compras:

const Compras = (props) => {

    const [comprado, setComprado] = useState(false);
    const [productos, setProductos] = useState({});


    useEffect(() => {

        RNIap.initConnection().catch((error) => {
            console.log("Error conectando IAP: " + error);
        }).then(() => {
            console.log("Conectado IAP");

            RNIap.getProducts({ skus: productosSolicitados}).then((products) => {
                console.log("Productos: " + JSON.stringify(products));
                setProductos(products);
            }).catch((error) => {
                console.log("Error buscando productos: " + error);
            }
            );
        });


    }, []);

    return (
        <View>
            <Text>Prueba compras</Text>
        </View>
    );
};

Primero hemos creado dos estados:

const [comprado, setComprado] = useState(false);
const [productos, setProductos] = useState({});

En ellos vamos a añadir lo que se ha comprado y los productos que llegan de la tienda de aplicaciones.

Dentro del primer método que se ejecuta (useEffect) vamos a añadir la conexión con RNIap

    RNIap.initConnection().catch((error) => {
        console.log("Error conectando IAP: " + error);
    }).then(() => {
        console.log("Conectado IAP");

Después añadimos el código para obtener los productos configurados:

        RNIap.getProducts({ skus: productosSolicitados}).then((products) => {
            console.log("Productos: " + JSON.stringify(products));
            setProductos(products);
        }).catch((error) => {
            console.log("Error buscando productos: " + error);
        }
        );

Si queremos obtener suscripciones tendremos que añadir este código:

            RNIap.getSubscriptions({ skus: productosSolicitados}).then((products) => {
                console.log("Productos: " + JSON.stringify(products));
                setProductos(products);
            }).catch((error) => {
                console.log("Error buscando productos: " + error);
            }
            );

Antes de ejecutar la APP, tenemos que ir a la carpeta de Android>app>src>main>manifest.xml y añadir este permiso (si no tienes la carpeta app generada puedes generarla siguiendo este tutorial https://devcodelight.com/generar-carpetas-de-codigo-nativo-android-ios-en-react-native-con-expo/) :

  <uses-permission android:name="com.android.vending.BILLING" />

Si ejecutamos la APP ahora mismo, nos devolverá el error: Error: E_IAP_NOT_AVAILABLE (https://devcodelight.com/solucionar-error-e_iap_not_available-con-la-libreria-react-native-iap-en-react-native/(abre en una nueva pestaña), ya que esta librería es nativa y necesita ser instalada.

PARA EVITAR HACERLO A MANO:

Creamos un archivo llamado ComprasInAPPConfigBuild.js

Con este contenido:

const { withAppBuildGradle, withAndroidManifest } = require('@expo/config-plugins');

const withCustomAndroidConfig = (config) => {
  // Modificar build.gradle para agregar missingDimensionStrategy
  config = withAppBuildGradle(config, (config) => {
    if (config.modResults.contents) {
      if (!config.modResults.contents.includes("missingDimensionStrategy 'store', 'play'")) {
        config.modResults.contents = config.modResults.contents.replace(
          /defaultConfig\s*{([\s\S]*?)}/,
          `defaultConfig { $1
        missingDimensionStrategy 'store', 'play'
          }`
        );
      }
    }
    return config;
  });

  // Modificar AndroidManifest.xml para agregar permiso de facturación
  config = withAndroidManifest(config, (config) => {
    const billingPermission = {
      $: {
        'android:name': 'com.android.vending.BILLING',
      },
    };

    const androidManifest = config.modResults;
    const manifest = androidManifest.manifest;

    // Asegúrate de que 'uses-permission' esté definido
    if (!manifest['uses-permission']) {
      manifest['uses-permission'] = [];
    }

    // Comprobar si el permiso ya existe
    const existingPermissionIndex = manifest['uses-permission'].findIndex(
      (permission) => permission.$['android:name'] === 'com.android.vending.BILLING'
    );

    // Agregar el permiso si no existe
    if (existingPermissionIndex === -1) {
      manifest['uses-permission'].push(billingPermission);
    }

    return config;
  });

  return config;
};

module.exports = withCustomAndroidConfig;

Ahora para que se aplique, añadimos este plugin en app.json:

{
  "expo": {
    "plugins": [
      "./ComprasInAPPConfigBuild"
    ]
  }
}

Y usamos el comando:

npx expo prebuild

Ahora tenemos que generar el build para más información sobre como generar un build de desarrollo usa este artículo: Como crear un build development usando Expo EAS con React Native

eas build --profile development --platform android

Si da error al generar el build, tendremos que hacer esto: https://devcodelight.com/solucionar-error-could-not-resolve-project-react-native-iap-al-generar-build-dev-con-la-libreria-react-native-iap-en-react-native/(abre en una nueva pestaña)

Una vez generado el build, podremos ejecutarlo con este comando:

npx expo start --dev-client

Y ya nos devuleve las compras configuradas:

Ahora vamos a crear botones con la información de la compra:
 return (
        <View>
            <Text>Prueba Premium</Text>
            {
                productos.map((pkg) => {
                    return   <Button style={styles.boton} mode="contained" onPress={() => comprar(pkg.productId)}> <Text>Compra 1 {pkg.oneTimePurchaseOfferDetails.formattedPrice}</Text> </Button>
                })
            }
        </View>
    );

Recorremos el array de compras disponibles y además asignamos una función que vamos a crear, llamada comprar();. Para obtener el id de producto accedemos pkg.productId y el para el precio pkg.oneTimePurchaseOfferDetails.formattedPrice

Creamos la función de comprar:

function comprar(product_id) {
        console.log("Comprar: " + product_id);
        RNIap.requestPurchase({ skus: [product_id] }).then((purchase) => {
            console.log("Comprado: " + JSON.stringify(purchase));
            setComprado(true);
        }).catch((error) => {
            console.log("Error: " + error);
        });
    }

Si queremos comprar suscripciones tendremos que poner:

function comprar(product_id) {
        console.log("Comprar: " + product_id);
        RNIap.requestSubscription({ skus: [product_id] }).then((purchase) => {
            console.log("Comprado: " + JSON.stringify(purchase));
            setComprado(true);
        }).catch((error) => {
            console.log("Error: " + error);
        });
    }

Este código funciona para Android, si queremos que funcione en iOS, tenemos que modificarlo de esta forma ({ sku: product_id }):

function comprar(product_id) {
        console.log("Comprar: " + product_id);
        RNIap.requestSubscription({ sku: product_id }).then((purchase) => {
            console.log("Comprado: " + JSON.stringify(purchase));
            setComprado(true);
        }).catch((error) => {
            console.log("Error: " + error);
        });
    }

Para poder utilizar los dos códigos, podemos poner un if dependiendo de la plataforma:

function comprar(product_id) { 

        if (Platform.OS === 'android') {
          console.log("Comprar: " + product_id);
        RNIap.requestSubscription({ skus: [product_id] }).then((purchase) => {
            console.log("Comprado: " + JSON.stringify(purchase));
            setComprado(true);
        }).catch((error) => {
            console.log("Error: " + error);
        });
        } else if (Platform.OS === 'ios') {    
        console.log("Comprar: " + product_id);
        RNIap.requestSubscription({ sku: product_id }).then((purchase) => {
            console.log("Comprado: " + JSON.stringify(purchase));
            setComprado(true);
        }).catch((error) => {
            console.log("Error: " + error);
        });
      }

}

Ahora nos falta obtener la validación de la compra. Para ello creamos dos variables nuevas debajo de nuestro array de productos que hemos llamado productosSolicitados:

let retornoCompraUpdate;
let retornoCompraError;

Y lo asignamos dentro de usseffect justo al final:

 useEffect(() => {
....

          retornoCompraError = RNIap.purchaseErrorListener((error) => {
                console.log("Error compra: " + JSON.stringify(error));
                if (error.code == "E_USER_CANCELLED") {
                    console.log("Compra cancelada");
                }
                if (error.code == "E_UNKNOWN") {
                    console.log("Error desconocido");
                }
            });

            retornoCompraUpdate = RNIap.purchaseUpdatedListener((purchase) => {
                console.log("Compra actualizada: " + JSON.stringify(purchase));
                setComprado(true); //Aquí deberíamos validar con un servidor si la compra es válida
            }
            );
        });

Quedando todo junto de esta forma:

 useEffect(() => {

        RNIap.initConnection().catch((error) => {
            console.log("Error conectando IAP: " + error);
        }).then(() => {
            console.log("Conectado IAP");

            RNIap.getProducts({ skus: productosSolicitados}).then((products) => {
                console.log("Productos: " + JSON.stringify(products));
                setProductos(products);
            }).catch((error) => {
                console.log("Error buscando productos: " + error);
            }
            );
        });

         retornoCompraError = RNIap.purchaseErrorListener((error) => {
                console.log("Error compra: " + JSON.stringify(error));
                if (error.code == "E_USER_CANCELLED") {
                    console.log("Compra cancelada");
                }
                if (error.code == "E_UNKNOWN") {
                    console.log("Error desconocido");
                }
            });

            retornoCompraUpdate = RNIap.purchaseUpdatedListener((purchase) => {
                console.log("Compra actualizada: " + JSON.stringify(purchase));
                setComprado(true); //Aquí deberíamos validar con un servidor si la compra es válida
            }
            );

    }, []);

Una vez realizada la compra, podemos validarla. Aunque recomiendo validarla con un servidor dedicado o firebase, aquí os pongo un ejemplo de cómo validarla de forma local.

Para ello añadimos este código en el purchaseUpdatedListener:

 RNIap.acknowledgePurchaseAndroid({ token: purchase.purchaseToken }).then(() => {
                console.log('acknowledgePurchaseAndroid success');
            }).catch(err => {
                console.log('acknowledgePurchaseAndroid error', err);
            });

De esta forma primero aceptamos la compra y finalmente finaliza la compra. Si nuestra compra es consumible, es decir que permite comprar de nuevo, añadiremos esta línea de código para que permita de nuevo volver a comprar:

RNIap.finishTransaction({ purchase: purchase, isConsumable: true });

Quedando el código así:

 RNIap.acknowledgePurchaseAndroid({ token: purchase.purchaseToken }).then(() => {
                console.log('acknowledgePurchaseAndroid success');
                RNIap.finishTransaction({ purchase: purchase, isConsumable: true });
            }).catch(err => {
                console.log('acknowledgePurchaseAndroid error', err);
            });

Este código consumirá las compras en Android, si queremos consumir las compras de iOS, tendremos que poner solo esta línea:

                RNIap.finishTransaction({ purchase: purchase, isConsumable: true });

Si estamos añadiendo compras a una app iOS, tendremos que activar la capacidad de compra en nuestra APP dentro de Xcode. Para eso hacemos lo siguiente

  • Generamos los ficheros de APP Build:
    expo prebuild
  • Abrimos con Xcode nuestro proyecto (abrimos la carpeta iOS de nuestro proyecto)
  • Seleccionamos nuestro proyecto y pulsamos en Signin & Capabilities.
  • Ahora pulsamos en + Capability
  • Elegimos de la lista la de In-App Purcharse
  • Y se añade a nuestro proyecto.
  • Añadir las compras de aplicación en la web de APPLE DEVELOPER (abajo del todo al abrir nuestra APP):

Es importante que rellenes todos los campos de la compra en aplicación y que el estado sea Lista para enviar. Para que aparezcan en la APP de pruebas.

  • Firmar los acuerdos, seleccionar impuestos y banca para poder recibir los pagos.

Para que las compras funcionen, hay que crear una cuenta Sandbox.

1 comentario en «Añadir compras de aplicación (iOS/Android) en React Native»

Deja un comentario