Saltar al contenido
WebViewGold Del vibe coding a la App Store
Español
Consigue WebViewGold

ChatGPT & Codex

Compras dentro de la app para una app web creada con Codex: RevenueCat, WebViewGold y un pull request de confianza

Los pagos son el punto en el que un agente puede costarte un rechazo o dinero de verdad, así que trata esta tarea como una tarea en la nube con una revisión estricta. Codex encuentra cada lugar en el que vendes bienes digitales, los dirige a RevenueCat cuando la página se ejecuta dentro de la app y escribe el webhook que concede el acceso en tu servidor. Tú te quedas con los productos, las claves y el botón de merge.

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

Objetivo y punto de vista del revisor

El objetivo: dentro de las apps de iOS y Android, cada producto digital (suscripciones, créditos, funciones premium, contenido desbloqueable) se compra a través de la tienda; en los navegadores, Stripe sigue funcionando; y los dos caminos escriben el mismo registro de entitlement en tu servidor.

El revisor comprueba tres cosas. Un botón de compra que abre un formulario de tarjeta o un checkout externo para bienes digitales es un rechazo por la Directriz 3.1.1. Un paywall de suscripción sin precio, periodo de facturación, condiciones de renovación y enlaces a los términos y a la privacidad es un rechazo por la 3.1.2. La falta de un control para restaurar compras también se señala. Los bienes físicos y los servicios del mundo real son otra categoría y pueden seguir usando Stripe dentro de la app (cómo distinguirlos).

Las reglas para enlazar a compras externas han cambiado en algunas regiones, por ejemplo en la tienda de EE. UU. tras la sentencia de Epic de 2025 y en la UE con la DMA. Si quieres usar esas opciones, lee primero el texto actual de las directrices en App Store Connect y déjalas fuera de tu primer envío. El resto de esta página asume el camino estándar, que es también lo que recomienda el flujo de publicación con Codex. Todas las opciones de compra que ofrece WebViewGold están resumidas en el centro de compras dentro de la app.

Todos los caminos del dinero, mapeados

SituaciónQué hace la app webNotas
Un usuario del navegador compraEl Stripe Checkout de siempre, sin cambiosEl webhook de Stripe escribe el entitlement
Un usuario de la app compra un productorevenuecat://purchase?external_id=UID&product=PRODUCT_IDiOS y Android; UID es tu ID de usuario estable y PRODUCT_ID, el que aparece en RevenueCat
Un usuario de la app abre un paywalliOS: revenuecat://launchPaywall?external_id=UID&offering=OFFERING (iOS 15 o posterior). Android: tu propia página de paywall que llama a purchaseLa documentación describe launchPaywall solo para iOS
Restaurarrestoreinapppurchases://Un control visible, que se espera en suscripciones y productos no consumibles
Gestionar o cancelarcancelinapppurchase://Abre la pantalla de suscripciones de la tienda
Mostrar compras anterioresgetpurchasehistory:// rellena window.purchaseHistoryPor dispositivo; los nombres de los campos difieren entre iOS y Android
Conceder el accesoNada en el clienteEl webhook de RevenueCat hacia Express escribe el entitlement

WebViewGold nos ha permitido el tiempo de mercado más corto y eficiente que jamás sea posible de todas las otras plataformas de aplicaciones que hemos probado durante los últimos 5 años. No sé por qué no lo había descubierto antes, ¡pero estoy muy, muy contento de haberlo hecho. Actualmente cerca de 6000 descargas de aplicaciones tanto en iOS App Store & Google Play Store sin ningún problema!

Antonio Davila · CodeCanyon

Un AGENTS.md anidado para el código de facturación

Codex combina los archivos AGENTS.md desde la raíz del repositorio hasta la carpeta en la que trabaja, y el archivo más cercano al código tiene prioridad. Un pequeño server/billing/AGENTS.md mantiene las reglas de pago junto al código de pagos, por encima de tu AGENTS.md raíz, y su sección Review guidelines le dice a la revisión de código de Codex qué debe señalar en cada pull request que toque la facturación. La revisión de Codex en GitHub informa de hallazgos P0 y P1, así que las reglas usan esos niveles. Puedes dejar el archivo en inglés: Codex lo entiende igual de bien.

markdown
# server/billing/AGENTS.md

## Invariants
- Entitlements live in Firestore at entitlements/{uid} and are written ONLY by
  webhooks: /webhooks/stripe (source "stripe") and /webhooks/revenuecat
  (source "revenuecat"). No client code, callable function or admin page sets premium.
- Every access check reads entitlements/{uid} on the server. Never trust a flag,
  cookie or localStorage value from the device.
- The RevenueCat app user ID is the Firebase uid, passed as external_id in
  revenuecat://purchase?external_id=...&product=... (never the email address).
- Webhooks: compare the Authorization header with REVENUECAT_WEBHOOK_AUTH, dedupe by
  event.id in billingEvents/{eventId}, answer 200 quickly. Tests use fixtures in
  server/billing/__fixtures__, never live RevenueCat or Stripe APIs.
- Inside the native app (isNativeApp()), digital goods use the bridge only:
  revenuecat://purchase, revenuecat://launchPaywall (iOS only),
  restoreinapppurchases://, cancelinapppurchase://.
  No Stripe links, no card forms, no "cheaper on the web" copy.
- Product IDs come from src/config/products.ts, never hardcoded in components.

## Review guidelines
- P0: any code path that grants or extends premium outside the two webhooks.
- P0: a webhook handler that does not check the Authorization header.
- P1: a Stripe checkout, price link or external payment URL for a digital product
  that is reachable when isNativeApp() is true.
- P1: a paywall without price, billing period, renewal terms, Terms and Privacy
  links, or without a Restore Purchases control.

La tarea en la nube

Ejecútalo como tarea en la nube para que el resultado llegue como un diff que puedes convertir en pull request, y no como cambios en tu copia de trabajo. Lánzala desde la página de Codex en ChatGPT, desde la extensión del IDE o con codex cloud en la terminal. El entorno necesita el repositorio y un script de configuración que instale las dependencias (por ejemplo npm ci). No necesita acceso de red a RevenueCat ni a Stripe, y nunca debería tener claves de producción.

Pégalo en una tarea en la nube de Codex (o en la CLI)
Lee AGENTS.md y server/billing/AGENTS.md. Objetivo: dentro de las apps nativas de iOS y Android (isNativeApp() es true), cada producto digital se vende mediante compras dentro de la app a través del puente de RevenueCat de WebViewGold. Los navegadores siguen con Stripe. Stack: front end en React, API en Express, Firebase Auth, Firestore.

1. Inventario: enumera cada lugar en el que se vende o se anuncia con precio una suscripción, un paquete de créditos o una función premium (componentes, rutas, emails o banners que enlazan al checkout). Pon la lista en la descripción del pull request, con el archivo de cada uno.
2. Cliente: crea src/lib/purchase.ts con buy(productKey), openPaywall(), restorePurchases() y manageSubscription(). En la app, buy navega a revenuecat://purchase?external_id=UID&product=PRODUCT_ID, con UID = el uid de Firebase (codificado para URL) y PRODUCT_ID tomado de src/config/products.ts. restorePurchases navega a restoreinapppurchases:// y manageSubscription a cancelinapppurchase://. En los navegadores, buy mantiene el Stripe Checkout actual.
3. Paywall: en iOS, openPaywall puede usar revenuecat://launchPaywall?external_id=UID&offering=default (documentado solo para iOS 15 o posterior). Mantén nuestro propio componente de paywall para Android y como alternativa, detrás de un flag en src/config/products.ts. Nuestro paywall muestra el precio, el periodo de facturación, las condiciones de renovación y enlaces a /terms y /privacy.
4. En la app, elimina los botones de Stripe, los enlaces a precios web y los textos del tipo "más barato en la web" para bienes digitales. Los bienes físicos y los servicios siguen con Stripe.
5. Servidor: POST /webhooks/revenuecat en server/billing/. Devuelve 401 salvo que la cabecera Authorization sea igual a REVENUECAT_WEBHOOK_AUTH. Elimina duplicados por event.id en billingEvents. Escribe entitlements/{uid} con premium, source, store, productId, expiresAt y sandbox. El webhook de Stripe escribe el mismo documento con source "stripe". Toda comprobación de premium en el servidor lee solo este documento.
6. Cuando se cierra la hoja de compra nativa, el cliente no concede nada. Consulta GET /api/entitlements durante un máximo de 20 segundos y mientras tanto muestra "Procesando tu compra".
7. Ajustes: filas para Restaurar compras y Gestionar suscripción, solo en la app nativa.
8. Tests con eventos de fixture (INITIAL_PURCHASE, RENEWAL, CANCELLATION, EXPIRATION, TEST, un ID de evento duplicado) y una petición con una cabecera Authorization incorrecta. Nada de llamadas a las APIs reales de RevenueCat o Stripe.
9. Descripción del pull request: el inventario, las variables de entorno que debo configurar, los pasos que quedan en el panel de RevenueCat y un plan de pruebas en sandbox para iOS y Android.

El webhook que deberías encontrar en el pull request

Simplificado, pero con las partes que importan en la revisión: autorización, idempotencia, el ID de usuario tomado de app_user_id (que es tu external_id) y ninguna intervención del cliente. Los reembolsos, las transferencias y los productos de por vida necesitan un tratamiento adicional en una app real.

javascript
// server/billing/revenuecat.js (Express + firebase-admin)
import express from "express";
import { getFirestore, FieldValue } from "firebase-admin/firestore";

const db = getFirestore();
export const revenuecatRouter = express.Router();
const GRANT = new Set(["INITIAL_PURCHASE", "RENEWAL", "UNCANCELLATION",
  "PRODUCT_CHANGE", "NON_RENEWING_PURCHASE", "SUBSCRIPTION_EXTENDED"]);

revenuecatRouter.post("/webhooks/revenuecat", express.json(), async (req, res) => {
  if (req.get("Authorization") !== process.env.REVENUECAT_WEBHOOK_AUTH) {
    return res.status(401).end();
  }
  const event = req.body && req.body.event;
  if (!event || !event.id) return res.status(400).end();
  if (event.type === "TEST") return res.status(200).end();

  const seen = db.collection("billingEvents").doc(event.id);
  if ((await seen.get()).exists) return res.status(200).end(); // duplicate delivery

  const uid = event.app_user_id; // = external_id from revenuecat://purchase
  if (uid) {
    const expiresAt = event.expiration_at_ms ? new Date(event.expiration_at_ms) : null;
    const ref = db.collection("entitlements").doc(uid);
    if (GRANT.has(event.type)) {
      await ref.set({ premium: true, source: "revenuecat", store: event.store,
        productId: event.product_id, expiresAt, sandbox: event.environment === "SANDBOX",
        updatedAt: FieldValue.serverTimestamp() }, { merge: true });
    } else if (event.type === "EXPIRATION") {
      await ref.set({ premium: false, expiresAt, updatedAt: FieldValue.serverTimestamp() }, { merge: true });
    }
    // CANCELLATION and BILLING_ISSUE: access usually runs until EXPIRATION.
  }
  await seen.set({ type: event.type, uid: uid || null, at: FieldValue.serverTimestamp() });
  res.status(200).end();
});

Del pull request a una compra en sandbox

  1. Revisa el pull request como un auditor

    Lee primero el inventario: cada punto de venta debería aparecer en la lista, y cada uno debería terminar o en Stripe (navegador) o en el puente (app). Después comenta @codex review for payment bypasses en el pull request para obtener una segunda opinión centrada en ese tema que aplique las review guidelines anidadas. Haz merge solo cuando puedas explicar cada camino.

  2. Configura el webhook

    En RevenueCat, añade un webhook que apunte a https://api.example.com/webhooks/revenuecat y define un valor para la cabecera de autorización; pon el mismo valor en REVENUECAT_WEBHOOK_AUTH en tu servidor. Envía el evento de prueba de RevenueCat y confirma que recibes una respuesta 200.

  3. Configura las claves nativas

    iOS, Config.swift: kRevenueCatEnable en true, revenueCatAPIKey y revenueCatProjectID; deja kRevenueCatShowDefaultOfferingPaywall en false salvo que quieras que el paywall se abra en cada arranque. Android, Config.java: REVENUECAT_API_KEY y REVENUECAT_PROJECT_ID. El bundle ID y el nombre de paquete deben coincidir exactamente con las apps de RevenueCat (documentación de IAP para iOS, documentación de IAP para Android).

  4. Prueba en el sandbox

    iOS: una build de TestFlight y una cuenta de Apple de sandbox. Android: testers de licencias y una pista de pruebas internas. Compra, restaura en un segundo dispositivo, deja que caduque una suscripción de sandbox y comprueba que el entitlement solo cambia cuando llega el webhook.

  5. Envía con los productos adjuntos

    Adjunta las primeras compras dentro de la app a la versión de la app en App Store Connect y dile al revisor dónde está el paywall y qué cuenta demo debe usar (plantilla de notas para la revisión).

Checklist de pruebas

  • Navegador de escritorio: Stripe funciona exactamente igual que antes y no se puede llegar a ningún enlace revenuecat://.
  • App: ninguna pantalla muestra un botón de Stripe, un formulario de tarjeta o un enlace a precios web para bienes digitales.
  • Una compra en iOS y en Android desbloquea premium en segundos, y solo después de que el webhook haya escrito el entitlement.
  • Cerrar la app a la fuerza con la hoja de compra abierta no deja un estado a medias; el webhook sigue concediendo el acceso.
  • Restaurar en un segundo dispositivo (misma cuenta de la tienda, mismo login) recupera premium.
  • Gestionar suscripción abre la pantalla de la tienda; una suscripción de sandbox cancelada sigue activa hasta que caduca.
  • El paywall muestra el precio, el periodo, las condiciones de renovación y enlaces a los Términos y a la Privacidad.
  • Una petición al webhook con una cabecera Authorization incorrecta recibe un 401.

Trampas habituales

  • Usar la dirección de email como external_id. Los emails cambian y el cliente de RevenueCat se divide en dos. Usa el uid.
  • Vender antes del login. Sin un ID estable, la compra acaba en un cliente que luego no puedes asociar. Pide primero el login o usa un ID de dispositivo de get-uuid:// y fusiona las cuentas de forma deliberada.
  • Conceder premium en el cliente cuando se cierra la hoja de compra. Es un desbloqueo gratis para cualquiera que modifique una petición; consulta tu servidor en su lugar.
  • Fiarse del orden de los eventos. Los webhooks pueden llegar tarde o dos veces. Elimina duplicados por ID de evento; para un estado más robusto, haz que el handler vuelva a leer el cliente desde la API REST de RevenueCat en cada evento en lugar de interpretar cada tipo de evento.
  • Borrar las cookies en cada arranque (deletecache en iOS, CLEAR_CACHE_ON_STARTUP en Android). La documentación de WebViewGold recomienda dejar estas opciones desactivadas cuando el flujo de compra depende de la sesión.
  • Dar por hecho que window.purchaseHistory es igual en todas partes: iOS devuelve purchaseDateMs como cadena, y Android purchaseTime y orderId. Pide a Codex que escriba un único normalizador.

Preguntas frecuentes

¿El paywall tiene que ser nativo?

No. Una página de paywall en tu app web que llame a revenuecat://purchase funciona en las dos plataformas, siempre que muestre el precio, el periodo, las condiciones de renovación y los enlaces legales. En iOS también puedes abrir un paywall diseñado en RevenueCat con revenuecat://launchPaywall (iOS 15 o posterior).

¿Debería darle a la tarea en la nube mis claves de RevenueCat o Stripe?

No. La tarea escribe código y tests contra fixtures. Las claves van en el entorno de tu servidor y las configuras tú después del merge. Mantener los secretos de producción fuera de los entornos de los agentes es la forma más barata de reducir riesgos que tienes.

¿Pueden los suscriptores de la web usar premium en la app?

Normalmente sí. La regla multiplataforma de Apple (Directriz 3.1.3(b)) permite a los usuarios acceder a lo que compraron en tu web, siempre que esos mismos artículos también estén disponibles como compras dentro de la app. Como los dos webhooks escriben el mismo entitlement, un suscriptor de Stripe que inicia sesión también es premium en la app.

¿Cuánto se quedan Apple y Google?

La comisión estándar es del 30 por ciento. El Small Business Program de Apple y el tramo reducido de Google para el primer millón de dólares de ingresos anuales la bajan al 15 por ciento, y RevenueCat cobra un porcentaje de los ingresos por encima de su umbral gratuito.

¿Necesito la Extended License de WebViewGold?

Sí. La documentación de compras dentro de la app de WebViewGold exige la Extended License para usar compras dentro de la app en un producto final. Amplía la licencia antes de enviar la build que incluye las compras.

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.

Ver planes en la página principal

Full Service

$698 por plataforma $798

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.

Detalles del Full Service Con tecnología de WeSetupYourWebViewApp

Precios de la Oferta de invierno válidos hasta el 31 de diciembre de 2026.

¿Listo para lanzar tu app?

Garantía de aprobación en App Store & Google Play – o te devolvemos el dinero en 14 días

¿Prefieres delegarlo? Full Service: configuramos, compilamos y entregamos en 72 horas, desde $698 por plataforma. ¿Preguntas? Chatea con nosotros 24/7.

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.