Compras in-app para una app web de Claude Code: RevenueCat dentro de WebViewGold
Dentro de las apps de iOS y Android, las suscripciones digitales pasan por las tiendas; en tu sitio web, Stripe puede quedarse. WebViewGold inicia la compra en la tienda a través de RevenueCat con un solo esquema de URL, así que el trabajo en tu código se reduce a una bifurcación limpia por plataforma, un webhook y una comprobación del entitlement. Esta guía muestra cómo hacer que Claude Code construya exactamente eso y cómo demostrar que funciona antes de la revisión.
Desde $109 de pago único por plataforma, o deja que nuestro equipo lo haga todo desde $698.
Garantía de aprobación – o te devolvemos el dinero en 14 días
Sin programar
Pago único, actualizaciones gratuitas
Soporte humano 24/7
Hecho en Alemania desde 2014 · ISO 27001
9:41
Tu app
Tu appYa en tu bolsillo
App Store · Google Play Aprobada
La regla y cómo encajan las piezas
La directriz 3.1.1 de Apple dice que el contenido digital, las funciones y las suscripciones que se desbloquean dentro de una app de iOS deben usar la compra in-app; la política de pagos de Google Play dice lo mismo para Play Billing. Los bienes físicos y los servicios que se usan fuera de la app están exentos. Las excepciones regionales han cambiado mucho desde 2025: las directrices actuales de Apple permiten botones y enlaces a otros métodos de pago en las apps del storefront de Estados Unidos, y Google cambió sus normas para EE. UU. tras la sentencia del caso Epic contra Google. Conviene leer ambas en su versión actual antes de apoyarte en ellas; el enfoque más sencillo que funciona en todas partes sigue siendo la compra in-app dentro de la app y Stripe en la web. La guía Stripe frente a compra in-app te ayuda a decidir.
La parte de WebViewGold es pequeña. RevenueCat es la integración predeterminada: tu botón navega a revenuecat://purchase?external_id=USER_ID&product=PRODUCT_ID, aparece la hoja nativa de la tienda, RevenueCat registra la transacción y envía un webhook a tu servidor, y tu servidor escribe el entitlement que comprueba tu app web. restoreinapppurchases:// restaura las compras y cancelinapppurchase:// abre los ajustes de suscripción de la tienda. En iOS 15 y posteriores, revenuecat://launchPaywall?external_id=USER_ID&offering=OFFERING_ID muestra el paywall de RevenueCat que diseñaste en su panel; la documentación indica ese esquema solo para iOS, así que en Android se usa revenuecat://purchase por producto. Ten en cuenta que usar compras in-app en un producto final requiere la Extended License de WebViewGold (consulta la documentación de compras in-app).
Qué hace Claude Code y qué haces tú
Área
Claude Code
Tú
Consolas de las tiendas
Redacta la lista de productos y los textos del paywall para que los pegues
Acuerdo de apps de pago (Paid Apps), datos bancarios y fiscales, productos de suscripción en App Store Connect y en la Play Console
RevenueCat
Explica qué campos rellenar, nada más
Proyecto con ambas apps, productos, entitlement, offering, URL del webhook y el valor de su cabecera Authorization
WebViewGold
Puede definir las claves de RevenueCat en Config.swift / Config.java si se lo pides
Bundle ID y nombre de paquete que coincidan con RevenueCat, compilaciones, testers de sandbox
App web
Bifurcación del paywall, enlaces para restaurar y gestionar, ruta del webhook, tabla de entitlements, consulta periódica tras la compra, un comando de control contra Stripe dentro de la app
Aprobar el plan, definir las variables de entorno, probar la compra en dispositivos
Configuración, en el orden que evita esperas
Los acuerdos de las tiendas y la revisión de productos son lo que más tarda; empieza por ahí.
Productos en ambas tiendas
En App Store Connect, firma el acuerdo Paid Apps y luego crea un grupo de suscripciones con productos como pro_monthly y pro_yearly. En la Play Console, crea los mismos IDs de producto como suscripciones con un plan base; Play necesita la app en al menos un canal de pruebas. La guía de configuración de RevenueCat cubre todos los campos.
Proyecto de RevenueCat
Añade la app de App Store y la de Play Store, importa los productos, asígnalos a un entitlement llamado pro y ponlos en un offering. En Integrations → Webhooks, introduce https://app.example.com/api/webhooks/revenuecat y un valor para la cabecera Authorization que generes tú mismo.
Claves en WebViewGold
iOS: en Config.swift pon kRevenueCatEnable en true y rellena revenueCatAPIKey y revenueCatProjectID; deja kRevenueCatShowDefaultOfferingPaywall en false salvo que de verdad quieras un paywall en cada inicio. Android: rellena REVENUECAT_API_KEY y REVENUECAT_PROJECT_ID en Config.java. Usa la clave pública del SDK que RevenueCat muestra para la app de cada plataforma.
Ejecuta el prompt en modo plan
Aquí lo valioso es el plan: debe enumerar cada lugar donde tu app vende algo o menciona un precio. Apruébalo, deja que el agente lo implemente, añade en tu hosting las variables de entorno REVENUECAT_WEBHOOK_AUTH y las credenciales de tu base de datos, y despliega.
Compila y prueba en el sandbox
TestFlight en iOS y pruebas internas en Android, con las comprobaciones de abajo. Si prefieres dejar en otras manos las compilaciones nativas y la configuración de las tiendas, nuestro equipo de Full Service configura y compila ambas apps.
Mi WebView de iOS finalmente está aprobada. ¡Gracias!
Sustituye los IDs de producto y los nombres de las rutas. El último punto crea un slash command reutilizable que puedes ejecutar antes de cada versión.
Pégalo en Claude Code (primero en modo plan)
Dentro de nuestras apps de iOS y Android (creadas con WebViewGold y detectables por el token WebViewGoldApp en el user agent), las compras digitales deben pasar por el App Store y Google Play. WebViewGold integra RevenueCat: navegar a revenuecat://purchase?external_id=USER_ID&product=PRODUCT_ID inicia una compra nativa, restoreinapppurchases:// restaura las compras y cancelinapppurchase:// abre los ajustes de suscripción de la tienda. En iOS 15 o posterior, revenuecat://launchPaywall?external_id=USER_ID&offering=OFFERING_ID muestra el paywall de RevenueCat; la documentación lo indica solo para iOS, así que en Android se usa revenuecat://purchase por producto. Lee las secciones In-App Purchases en https://www.webviewgold.com/llms-full.txt y no uses nada que no esté documentado allí. Nuestros IDs de producto son pro_monthly y pro_yearly, y el entitlement es pro.
Primero haz un plan y espera mi aprobación. El plan debe enumerar cada lugar donde vendemos, ponemos precio o anunciamos algo digital (checkout de Stripe, página de precios, banners de mejora de plan, correos con enlaces de pago) y cómo se comporta cada uno dentro de la app.
Después implementa:
1. src/lib/native.ts: reutiliza isNativeApp() y añade isIOSApp() basado en el user agent.
2. Acciones del paywall: buyPro(productId) navega a revenuecat://purchase con el id del usuario con sesión iniciada (codificado para URL) como external_id. Nunca inicies una compra para un usuario sin sesión; mándalo primero al login. En los navegadores, mantén sin cambios el flujo actual de Stripe.
3. Dentro de la app, oculta todos los botones de Stripe, precios web y enlaces de pago externos. No menciones precios web más baratos.
4. El paywall muestra el nombre del plan, la duración, el precio por periodo, que la suscripción se renueva automáticamente hasta que se cancele, y enlaces a /terms y /privacy. En iOS, da preferencia a launchPaywall, que muestra los precios de la tienda. Donde mostremos nuestro propio paywall, lee el storefront con getstorelocation:// (window.storeLocation) y toma los precios de una tabla por storefront en src/config/prices.ts.
5. Un botón "Restaurar compras" (restoreinapppurchases://) y un enlace "Gestionar suscripción" (cancelinapppurchase://) en los ajustes y en el paywall, solo dentro de la app.
6. POST /api/webhooks/revenuecat: compara la cabecera Authorization con REVENUECAT_WEBHOOK_AUTH, lee event.type, event.app_user_id, event.product_id, event.expiration_at_ms, event.environment y event.store, y haz upsert de una fila en entitlements (user_id, entitlement, product_id, store, expires_at, will_renew, sandbox). CANCELLATION solo significa que la renovación automática está desactivada; el acceso dura hasta expires_at. Añade la migración.
7. GET /api/me/entitlement devuelve si el usuario tiene pro en este momento. Cuando se inicie una compra, consúltalo cada 4 segundos durante un minuto como máximo y otra vez en visibilitychange, y luego desbloquea la interfaz. El webhook es la única fuente de verdad; nunca desbloquees a partir del estado del cliente.
8. Crea .claude/commands/iap-check.md: un slash command que busque en los componentes que se muestran dentro de la app llamadas a Stripe, enlaces de checkout y precios escritos a mano, e informe de todo lo que entraría en conflicto con la directriz 3.1.1 de Apple.
Termina con las variables de entorno, los ajustes del panel de RevenueCat que debo introducir y un plan de pruebas.
La ruta del webhook que controla tus entitlements
RevenueCat envía con cada evento el valor de la cabecera Authorization que introdujiste en su panel. app_user_id es el external_id que pasó tu botón, y por eso tiene que ser tu ID de usuario estable.
typescript
// app/api/webhooks/revenuecat/route.ts
import { NextResponse } from "next/server";
import { db } from "@/lib/server";
const GRANTS = new Set(["INITIAL_PURCHASE", "RENEWAL", "PRODUCT_CHANGE", "UNCANCELLATION", "NON_RENEWING_PURCHASE"]);
export async function POST(req: Request) {
// El mismo valor que escribiste en RevenueCat -> Integrations -> Webhooks -> Authorization header
if (req.headers.get("authorization") !== process.env.REVENUECAT_WEBHOOK_AUTH) {
return NextResponse.json({ error: "unauthorized" }, { status: 401 });
}
const { event } = await req.json();
const userId: string | undefined = event?.app_user_id;
if (!userId || userId.startsWith("$RCAnonymousID")) {
return NextResponse.json({ ok: true, skipped: "no user id" }); // compra sin external_id
}
const base = {
userId,
entitlement: "pro",
productId: event.product_id,
store: event.store, // APP_STORE o PLAY_STORE
sandbox: event.environment === "SANDBOX",
};
const expiresAt = event.expiration_at_ms ? new Date(event.expiration_at_ms) : null;
if (GRANTS.has(event.type)) {
await db.entitlements.upsert({ ...base, expiresAt, willRenew: true });
} else if (event.type === "CANCELLATION" || event.type === "BILLING_ISSUE") {
// Renovación automática desactivada o pago fallido: mantén el acceso hasta que la tienda diga que ha caducado
await db.entitlements.upsert({ ...base, expiresAt, willRenew: false });
} else if (event.type === "EXPIRATION") {
await db.entitlements.upsert({ ...base, expiresAt: new Date(), willRenew: false });
}
// Los eventos TRANSFER traen transferred_from / transferred_to en su lugar; gestiónalos si permites cambiar de cuenta
return NextResponse.json({ ok: true });
}
Checklist de verificación (sandbox, dispositivos reales)
iOS: instala la compilación de TestFlight y compra. Las compras en TestFlight se ejecutan en el sandbox y no se cobran; allí las suscripciones se renuevan una vez al día, hasta seis veces. Para probar renovaciones y caducidad más rápido, inicia sesión con una Sandbox Apple Account (App Store Connect → Usuarios y acceso → Sandbox), donde se puede ajustar la frecuencia de renovación.
Android: añade tu cuenta de Gmail en Play Console → Configuración → Pruebas de licencias, instala desde el canal de pruebas internas (no un APK instalado a mano) y compra; las compras de prueba no se cobran.
En RevenueCat, cambia el panel a los datos de sandbox y busca al cliente por tu ID de usuario. Si la compra aparece con un ID anónimo, external_id estaba vacío.
Tu tabla entitlements tiene una fila con sandbox = true, y la app desbloquea pro en pocos segundos sin recargar a mano.
Borra la app, reinstálala, inicia sesión y toca "Restaurar compras": el acceso vuelve. Toca "Gestionar suscripción": se abre la pantalla de suscripciones de la tienda.
Abre el sitio web en un navegador de escritorio con la misma cuenta: pro también está desbloqueado allí, y Stripe se sigue ofreciendo a los visitantes web.
Ejecuta /iap-check en Claude Code: no se puede llegar dentro de la app a ningún botón de Stripe, enlace de checkout ni precio web.
Errores frecuentes
Un external_id inestable. Pasa el ID de usuario de tu base de datos, no un correo electrónico que puede cambiar. Exige iniciar sesión antes del paywall; así los clientes de RevenueCat y tus cuentas se corresponden uno a uno.
Precios escritos a mano. Los precios de las tiendas varían por país y cambian con los impuestos. En iOS, da preferencia al paywall de RevenueCat y mantén cualquier tabla por storefront sincronizada con las tiendas.
Tratar CANCELLATION como el final. Solo desactiva la renovación automática. Retira el acceso con EXPIRATION o cuando haya pasado expires_at.
Sesiones borradas. Con deletecache (iOS) o CLEAR_CACHE_ON_STARTUP (Android) en true, los usuarios pierden la sesión en cada inicio y tu botón no tiene ningún ID de usuario que enviar.
Stripe que se cuela en la app. Una página de precios accesible desde un enlace del pie, un correo de mejora de plan que se abre dentro de la app, una página de confirmación del checkout: cada uno es un riesgo de 3.1.1. Para eso está el slash command.
Preguntas frecuentes
¿Tengo que usar RevenueCat?
No. WebViewGold también tiene una API manual (inapppurchase:// e inappsubscription:// con URLs de éxito y de caducidad, más las variables inyectadas planID, transactionIdentifier y subreceipts). RevenueCat es la opción predeterminada porque te da un único entitlement y un único webhook para ambas tiendas.
¿Puedo mantener Stripe en mi sitio web?
Sí. Los visitantes del navegador conservan el checkout de Stripe. Ambas fuentes escriben en la misma tabla de entitlements, así que un usuario que pagó en la web también es pro en la app, siempre que la app no anuncie la compra en la web.
¿Y las normas de EE. UU. sobre enlaces de pago externos?
Las directrices actuales de Apple permiten botones y enlaces a otros métodos de pago en las apps del storefront de Estados Unidos, y Google cambió su política para EE. UU. tras la sentencia del caso Epic. Las normas varían según la región y siguen cambiando, así que lee el texto actual de la directriz y de la política antes de basarte en ellas.
¿Claude Code necesita mis secretos de RevenueCat?
No. Las claves públicas del SDK en Config.swift y Config.java están pensadas para ir dentro de la app. El valor Authorization del webhook y las claves de la base de datos viven en las variables de entorno de tu hosting; dale al agente solo sus nombres.
¿Cómo sabe la app web que una compra se completó?
La documentación no define un callback de éxito para revenuecat://purchase, así que trata el webhook como la fuente de verdad: la app consulta tu endpoint de entitlements durante un momento después de iniciar una compra y desbloquea en cuanto aparece la fila.
Tres formas de llevar tu app a las tiendas
El mismo resultado, con distinta cantidad de trabajo por tu parte. Las tres incluyen la garantía de aprobación y soporte humano 24/7.
Hazlo tú mismo
$109 por plataforma, pago único
La licencia de WebViewGold: un proyecto de Xcode o Android Studio listo para usar. Pega tu URL, define nombre e icono, activa funciones, compila y envía. Actualizaciones gratuitas para siempre.
Sin Mac, sin Xcode, sin Android Studio: configura en el navegador, compilamos ambas apps en la nube y las entregamos a App Store Connect y a Play Console.
Envíanos tu URL y tu icono. Nuestro equipo configura, compila y entrega tu app en 72 horas, en marca blanca y con los proyectos fuente. Ambas plataformas por $1,158.
Los reembolsos se conceden bajo las condiciones indicadas en nuestras FAQ: un rechazo por motivos ajenos al contenido por parte de Apple y/o Google, reclamado dentro de los 14 días posteriores a la compra.