In-App Purchases & Subscriptions

¿Cómo respondo automáticamente a las solicitudes de reembolso de Apple para compras dentro de la app?

Descubre cómo la automatización de reembolsos de Apple puede simplificar la gestión de solicitudes de reembolso y reducir el trabajo manual para los desarrolladores de apps.

5 min read
¿Cómo respondo automáticamente a las solicitudes de reembolso de Apple para compras dentro de la app?

Perdí casi un día entero por el error Undelivered Consumption Percentage Non Zero Error. No porque sea un error difícil. Fue porque configuré Delivery Status con un valor distinto de DELIVERED y dejé Consumption Percentage en 60000, y Apple rechaza esa combinación sin mucha contemplación.

Lo cual es una forma indirecta de llegar a la pregunta real.

Si estás averiguando cómo responder automáticamente a las solicitudes de reembolso de Apple, es una tarea más pequeña de lo que la mayoría de los equipos supone. La parte difícil nunca fue la llamada a la API. Es tener los datos para incluir en ella. La explicación campo por campo está en Apple CONSUMPTION_REQUEST explained. Este artículo trata sobre cómo configurarlo para que nadie tenga que estar despierto.

Puntos clave

  • La automatización de reembolsos de Apple consiste en un webhook más un PUT. Apple envía un CONSUMPTION_REQUEST, y tú respondes dentro de las 12 horas.

  • Necesitas una clave In-App Purchase, no tu clave de la API de App Store Connect. No son intercambiables.

  • Tres de los cinco campos son obligatorios, y dos de ellos tienen restricciones que solo se manifiestan como errores.

  • Sin el consentimiento del cliente no deberías responder en absoluto, y no puedes obtener el consentimiento de forma retroactiva.

  • El endpoint se trasladó a la v2. En producción es api.storekit.apple.com, y en el entorno sandbox tienes 5 minutos en lugar de 12 horas.

Cómo funcionan en realidad las solicitudes de reembolso de Apple para desarrolladores

Un cliente abre reportaproblem.apple.com, busca la compra realizada en los últimos 90 días, elige un motivo y envía la solicitud. Si has configurado las App Store Server Notifications V2, tu endpoint recibe un CONSUMPTION_REQUEST como un payload JWS firmado que incluye la transacción, el producto y el motivo que eligió.

Ese webhook es todo el desencadenante para un reembolso de una compra dentro de la app en App Store. No aparece nada en tu dashboard, y no hay ninguna cola que revisar. Por eso los equipos descubren que este sistema existe semanas después de empezar a perder dinero por su causa.

Después, tienes 12 horas.

Algo que vale la pena corregir, porque se repite en todas partes. Esto no se limita a los productos consumibles. La documentación actual de Apple indica que un CONSUMPTION_REQUEST llega para cualquier tipo de producto: consumible, no consumible, suscripción no renovable y suscripción autorrenovable. Las guías más antiguas todavía dicen que solo aplica a consumibles y suscripciones. Están desactualizadas.

Qué significa "responder" en términos concretos

Envías mediante PUT un cuerpo JSON a un endpoint.

PUT https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}

El entorno sandbox es api.storekit-sandbox.apple.com, con la misma ruta. Una respuesta exitosa devuelve 202 Accepted, no 200, lo cual confunde a quienes están verificando los códigos de estado.

La autenticación es un JWT ES256 de corta duración, firmado con una clave In-App Purchase generada en App Store Connect, dentro de Users and Access, en Integrations. No es tu clave de la API de App Store Connect. El archivo .p8 se descarga exactamente una vez, en el momento de su creación, así que si lo pierdes tendrás que generar uno nuevo y rotar todo lo que lo usa. Pregúntame cómo lo sé.

Los cinco campos, y los dos que muerden

El cuerpo es un Consumption Request. Tres campos son obligatorios, dos opcionales.

  • Customer Consented (obligatorio). Es true solo si tienes un consentimiento genuino. Más detalles abajo, porque este campo es el que condiciona todo lo demás.

  • Delivery Status (obligatorio). Indica si la compra llegó y funcionó.

  • Sample Content Provided (obligatorio). Indica si existía una muestra gratuita, una prueba o información sobre la funcionalidad antes de la compra.

  • Consumption Percentage (opcional). Cuánto consumieron, en miliunidades. De 0 a 100000, de modo que 45.2 por ciento equivale a 45200.

  • Refund Preference (opcional). GRANT_FULL, GRANT_PRORATED, DECLINE o NO_PREFERENCE.

Dos restricciones que te costarán una tarde cada una.

Si Delivery Status no es DELIVERED, Consumption Percentage debe ser 0. Cualquier otro valor falla con el error Undelivered Consumption Percentage Non Zero Error. Así se me fue el día.

Y si envías GRANT_PRORATED para una suscripción autorrenovable, no incluyas consumptionPercentage en absoluto. Apple lo calcula a partir del tiempo transcurrido en el periodo de facturación y rechaza el tuyo con el error Consumption Percentage Auto Renewable Subscription Error. Al menos los nombres de los errores son descriptivos, que es más de lo que esperaba.

El consentimiento es el filtro, no una formalidad

La documentación de Apple es inusualmente directa en este punto: si el cliente no ha dado su consentimiento, no respondas a la notificación. No se trata de responder con Customer Consented en false. No respondas.

Tampoco puedes obtener el consentimiento después de que llega una solicitud. Debe existir de antemano, lo que significa que ese texto debe formar parte de tus términos o tu política de privacidad antes de lanzar cualquiera de estas funciones, no después de tu primer reembolso disputado. La indicación de Apple es que el consentimiento debe ser otorgado libremente, específico, informado e inequívoco, que el opt-in es preferible al opt-out, y que obtenerlo es responsabilidad exclusivamente tuya.

En la práctica: una línea que indique que puedes compartir datos de compra y de uso con Apple para ayudar a revisar las solicitudes de reembolso, además de una forma de retirar ese consentimiento. Haz que tu propio equipo legal lo revise. No soy abogado y esto no es un consejo legal.

Cómo automatizan los desarrolladores las solicitudes de reembolso de App Store

El flujo, en el orden en que yo lo construiría:

  1. Configura una Production Server URL en App Store Connect, dentro de App Information, y elige Version 2. Las consumption requests no viajan por V1.

  2. Verifica la cadena de firma JWS contra los certificados raíz de Apple antes de confiar en un solo campo.

  3. Asocia la transacción con un usuario. El appAccountToken que adjuntaste en el momento de la compra es el camino rápido. Sin él, tienes que hacer una búsqueda inversa por producto y fecha, lo cual se complica rápido.

  4. Revisa el consentimiento. Sin consentimiento, no hay respuesta. Regístralo y continúa.

  5. Obtén el uso real. No una estimación, no un valor predeterminado. Si no puedes responder "cuánto de esto usó el cliente", esa es la verdadera brecha en tu automatización.

  6. Construye el ConsumptionRequest, aplicando las dos restricciones mencionadas antes.

  7. Envíalo mediante PUT, confirma el 202 y guarda el payload que enviaste.

El paso 5 es donde la mayoría de los intentos se estancan. La llamada a la API es trabajo de una tarde. Tener datos de consumo reales por usuario para una compra realizada hace siete semanas es un problema de modelado de datos que quizás todavía no hayas resuelto.

Qué hace que funcione de forma sólida en producción

Más allá del camino feliz, cuatro cosas:

Idempotencia. Apple reintenta las notificaciones, y tu handler verá la misma dos veces. Deduplica usando el UUID de la notificación, o acabarás enviando respuestas duplicadas.

Alertas que de verdad avisen a alguien. Un handler que falla en silencio es peor que no tener handler, porque creerás que estás cubierto.

Un entorno de pruebas sandbox. El sandbox te da 5 minutos en lugar de 12 horas, así que no puedes probarlo a mano de forma casual. Automatízalo con un script.

Y un registro de lo que enviaste, por cada caso. Cuando se rechace un reembolso, querrás saber qué preferencia enviaste y por qué. Cuando se otorgue uno en contra de tu DECLINE, también querrás saberlo.

Estimación honesta: unos días de trabajo de integración si ya has construido antes la fontanería de webhooks, y unas semanas para confiar en ello. La mayor parte de ese tiempo no tiene que ver con el lado de Apple.

O deja que otra herramienta lo haga por ti

Si ya usas RevenueCat o Adapty, revisa primero qué ofrecen. Ambos han añadido funciones de respuesta a reembolsos, y tus recibos y el mapeo de clientes ya están ahí.

Un software de gestión de reembolsos de Apple diseñado específicamente para esto va más allá. Refund Sensor funciona con las claves de App Store Connect que ya tienes, responde cada solicitud con evidencia de la transacción dentro del plazo, y registra el resultado de cada caso. Sin SDK, sin cambios de código, unos cinco minutos para conectarlo. Comparamos el panorama más amplio en Apple refund management tools, si prefieres comparar opciones.

El lado del cliente, ya que la gente pregunta

Sobre cómo solicitar un reembolso por compras en App Store, desde la perspectiva del usuario: entra en reportaproblem.apple.com, inicia sesión, busca la compra de los últimos 90 días y elige un motivo. Apple les indica a los clientes que recibirán una respuesta dentro de 48 horas.

También puedes mostrar ese mismo formulario dentro de tu app usando el refund request sheet de StoreKit, que dirige a los usuarios insatisfechos hacia Apple en lugar de hacia una reseña de una estrella. No puede otorgar un reembolso. Solo Apple puede hacerlo. Apple Support's guide to requesting a refund explica lo que ve el cliente.

Vale la pena decirlo con claridad, porque la gestión de reembolsos de App Store se vende con exageración: no puedes aprobar ni denegar un reembolso. Puedes poner evidencia ante Apple mientras deciden, y expresar una preferencia. Esa es toda la influencia que tienes, y es más de lo que usa la mayoría de los desarrolladores.

Fuentes

Preguntas frecuentes

Sí. Para eso existen la notificación CONSUMPTION_REQUEST y el endpoint Send Consumption Information. No puedes otorgar ni denegar el reembolso, pero Apple usa lo que envías como un factor en la decisión.

Recibes las App Store Server Notifications V2, verificas la firma JWS, asocias la transacción con un usuario, revisas el consentimiento, reúnes datos de consumo reales y los envías mediante PUT al endpoint de consumo v2 dentro de las 12 horas. Todo lo demás es idempotencia, alertas y registro de resultados.

Una clave In-App Purchase de Users and Access, dentro de Integrations. Tu clave de la API de App Store Connect no autenticará estas llamadas, y obtendrás un error 401 que parecerá un problema de reloj.

No, es opcional. Pero si deliveryStatus no es DELIVERED, debe ser 0, y en una suscripción autorrenovable prorrateada debes omitirlo por completo.

No respondas. Las indicaciones de Apple son explícitas en este punto, y el consentimiento no se puede obtener después de que llega la solicitud.

Es un factor, no una garantía. Apple dice que utiliza una variedad de factores. En la práctica, un DECLINE bien respaldado con evidencia en una compra muy consumida se rechaza con mucha más frecuencia que una solicitud que nadie respondió

#:Google Play chargeback#Google Play chargeback handling#Chargeback automation#Google Play refunds#App revenue protection#Chargeback management for apps
Refund SensorRefund Sensor TeamRefund defense for App Store and Google Play developers