La pregunta suele llegar tarde. El producto ya funciona, alguien quiere empezar a cobrar y aparece el mensaje en el canal del equipo: "¿cómo montamos las suscripciones?". Respuesta corta, para que puedas seguir con tu día: si publicas en iOS y Android a la vez y no tienes un motivo fuerte para lo contrario, arranca con RevenueCat encima del plugin in_app_purchase. Pásate al billing nativo solo cuando el 1% sobre ingresos deje de salir a cuenta o necesites un control que la capa gestionada no te da. El resto del artículo es el porqué, porque la decisión tiene aristas que no se ven hasta que la primera renovación falla en producción un domingo.

Qué estás decidiendo en realidad

Cobrar una suscripción dentro de una app parece una cosa y son tres. Conviene separarlas antes de elegir herramienta.

El primero es la compra: lanzar la hoja de pago de Apple o Google, gestionar el resultado, reintentos y restauración de compras. El segundo son los derechos (entitlements): saber en cada momento si este usuario tiene acceso premium, aunque cambie de dispositivo, aunque la renovación de este mes haya fallado, aunque haya pedido un reembolso. El tercero es la verificación en servidor: confirmar que una compra es real y no un recibo falsificado, y reaccionar a los eventos que Apple y Google mandan cuando algo cambia sin que la app esté abierta.

La mayoría de los equipos solo ve el primer problema al empezar. Los otros dos son los que te despiertan de madrugada. Elegir bien es, sobre todo, decidir cuánto de los problemas dos y tres construyes tú y cuánto delegas.

Opción 1: in_app_purchase, el plugin de primera parte

in_app_purchase es el plugin oficial del equipo de Flutter. Habla directamente con StoreKit en iOS y con Google Play Billing en Android, sin capas intermedias ni dependencias de terceros. Desde la versión 3.3.0 empaqueta Play Billing Library 8.0.0, así que cumple el requisito de la tienda.

Lo que te da: el flujo de compra y poco más. Lo que no te da, y es la parte importante: no gestiona entitlements ni valida recibos en tu servidor. Eso lo escribes tú. Tienes que montar tu propio backend que reciba las App Store Server Notifications y las Real-time Developer Notifications de Google, guarde el estado de cada suscripción y responda cuando la app pregunte "¿este usuario tiene premium?".

Es la opción con cero coste de licencia y cero dependencia externa. También la que más ingeniería te pide y la que más fácil es de hacer mal. Un receipt validation con casos de borde bien cubiertos (periodos de gracia, reembolsos, upgrades cruzados de plataforma, familias de suscripción) es más trabajo del que parece en la estimación inicial. Tiene sentido cuando publicas en una sola plataforma, cuando ya tienes un backend de facturación propio donde esto encaja sin fricción, o cuando el volumen hace que pagar un porcentaje a un tercero se note en la cuenta de resultados.

Opción 2: RevenueCat sobre in_app_purchase

RevenueCat es una capa gestionada que envuelve StoreKit y Play Billing. Su SDK de Flutter, purchases_flutter, se apoya en la misma maquinaria nativa, pero te resuelve los problemas dos y tres de arriba. Defines los entitlements en su panel y el SDK te devuelve un booleano: este usuario tiene acceso o no. La validación de recibos, el estado de renovación y el historial del cliente viven en su infraestructura, no en la tuya.

El coste es transparente y conviene mirarlo con calma. Es gratis hasta 2.500 dólares de ingresos mensuales rastreados, y a partir de ahí cobra un 1% de los ingresos brutos. "Brutos" es la palabra que conviene no pasar por alto: el 1% se calcula sobre el importe antes de la comisión de la tienda, no sobre lo que te queda. Sobre 100.000 dólares mensuales, tras una comisión de tienda del 30%, te quedan 70.000, pero RevenueCat cobra su 1% sobre los 100.000. Son 1.000 dólares, un 1,43% efectivo sobre lo que de verdad ingresas. No es caro para lo que ahorra, pero mételo en el modelo antes de firmarlo, no después.

A cambio te llevas semanas de backend que no escribes, un dashboard de métricas de suscripción decente y, sobre todo, una sola fuente de verdad para los entitlements en las dos plataformas. Para un equipo que lanza en iOS y Android y quiere estar cobrando este trimestre, es el punto de partida sensato.

Opción 3: billing nativo por plataforma

La tercera vía es hablar con StoreKit 2 y Play Billing Library desde código nativo, con platform channels, sin el plugin de Flutter de por medio. Es la opción de más control y más coste de mantenimiento: escribes y mantienes dos integraciones, una por tienda, y asumes cada cambio de API de Apple y Google en cuanto sale.

Casi nadie debería empezar aquí. Tiene sentido cuando necesitas una función muy reciente de StoreKit 2 que el plugin todavía no expone, cuando tienes requisitos de una plataforma que no encajan en la abstracción común, o cuando ya tienes equipo nativo dedicado y la capa Flutter te estorba más que te ayuda. Si estás dudando entre esta opción y las otras dos, la respuesta casi siempre es que aún no la necesitas.

La tabla, para decidir en treinta segundos

Criterioin_app_purchaseRevenueCatBilling nativo
Coste de licenciaCeroGratis hasta 2.500 $/mes, luego 1% brutoCero
Backend de entitlementsLo escribes túIncluidoLo escribes tú
Validación de recibosTu servidorIncluidaTu servidor
Multiplataforma unificadaManualSí, de serieDoble integración
Dependencia externaNingunaRevenueCatNinguna
CuándoUna plataforma, backend propioiOS + Android, time-to-marketNecesidad nativa concreta

La fecha que no puedes ignorar

Al margen de qué opción elijas en Android, hay un plazo de tienda que manda. Desde el 31 de agosto de 2026, Google Play no acepta apps nuevas ni actualizaciones de las existentes si no están compiladas contra Play Billing Library 8 o superior. Se puede pedir una prórroga en Play Console hasta el 1 de noviembre de 2026, y ahí se acaba el margen.

Un matiz que ahorra sustos: es una barrera de publicación, no un interruptor en tiempo de ejecución. Tu app publicada sigue cobrando aunque vaya con una librería antigua. El problema aparece en cuanto necesites subir cualquier actualización, incluido un parche de seguridad de cinco líneas: sin la v8, la tienda te la bloquea. Si usas in_app_purchase 3.3.0 o RevenueCat al día, ya cumples. Si arrastras una versión vieja, esto entra en el sprint actual, no en el backlog.

Lo que casi nadie cablea hasta que se rompe

Elijas la capa que elijas, hay una parte que no es opcional aunque lo parezca: reaccionar a lo que pasa fuera de la app. Una suscripción no vive dentro de tu proceso. Se renueva sola, se cancela desde los ajustes del sistema, entra en periodo de gracia cuando falla el cobro, se reembolsa desde soporte de Apple. Todos esos eventos llegan por notificaciones de servidor a servidor, no por la app.

Si delegas en RevenueCat, esto viene resuelto y solo escuchas sus webhooks. Si vas con in_app_purchase o nativo, es tu servidor el que tiene que recibir las App Store Server Notifications V2 y las Real-time Developer Notifications, procesarlas de forma idempotente y actualizar el estado. Saltarse esta parte es la causa número uno de usuarios que pagaron y no ven su premium, o que cancelaron y siguen teniendo acceso. En una suscripción, el bug no es una pantalla mal alineada: es dinero y confianza.

Cuándo NO usar RevenueCat

Que sea nuestro punto de partida por defecto no lo convierte en respuesta universal. No lo elegiríamos si tu volumen es alto y estable y el 1% bruto supera con holgura lo que costaría un equipo manteniendo la integración propia; a cierta escala, internalizar sale a cuenta. Tampoco si tienes requisitos de residencia de datos o de cumplimiento que chocan con meter el estado de facturación en un tercero, un tema recurrente en fintech y en salud. Ni si publicas en una sola plataforma con un backend de facturación que ya hace este trabajo: ahí la capa extra es dependencia sin contrapartida.

Es la misma lógica que aplicamos con cualquier proveedor que se mete en tu camino crítico, sea un gateway de pago o un gateway de LLM: empieza por lo que te hace ir rápido, y ten claro de antemano cuál es la señal que te dirá que ha llegado el momento de salir.

Cómo lo abordamos nosotros

En los proyectos con suscripción que montamos en Flutter, el patrón por defecto es RevenueCat sobre in_app_purchase, con una regla que no negociamos: el modelo de entitlements de la app se mantiene limpio y propio. La app pregunta "¿este usuario tiene acceso a esta función?" contra tu propia abstracción, no contra identificadores de producto de Apple metidos por medio código. Así, el día que quieras cambiar de capa o internalizar, tocas la implementación por debajo y no reescribes las reglas de negocio repartidas por toda la app. El proveedor te da velocidad hoy; la abstracción limpia te deja la puerta abierta para mañana.

Si estás en ese punto (producto que funciona, toca cobrar, y no quieres descubrir los problemas dos y tres en producción), en Dribba llevamos quince años montando este tipo de infraestructura en Flutter con equipo senior in-house. Hablamos cuando quieras.