Todos los artículos
Deep dive8 min de lectura

La detección de reembolsos en StoreKit 2 se reduce a una propiedad de la transacción, y es revocationDate

Cuando Apple reembolsa a uno de tus clientes, el reembolso ya está dentro de tu app, en la revocationDate de la transacción, antes de que se ejecute el trabajo de tu servidor. Aquí verás dónde aparece la detección de reembolsos de StoreKit 2 en el dispositivo, qué te dicen revocationDate y revocationReason, y por qué el cliente sirve para la rapidez y el servidor para la verdad.

Un smartphone brillando sobre un escritorio oscuro de desarrollador junto a un reloj mecánico, que ilustra la detección de reembolsos de StoreKit 2 apareciendo dentro de una app

Puntos clave

  • En StoreKit 2 una compra reembolsada lleva una revocationDate no nil en su Transaction, así que tu app puede detectar el reembolso por sí sola, sin llamar a tu servidor.
  • revocationDate se establece cuando la App Store reembolsa una transacción o cuando un cliente la pierde a través de Family Sharing, así que una fecha no nil no siempre es un reembolso.
  • revocationReason te dice por qué: developerIssue significa que el cliente señaló un problema en tu app, y other abarca todos los demás motivos de reembolso.
  • Transaction.currentEntitlements ya excluye las compras reembolsadas y revocadas, así que el filtro de acceso más limpio del lado del cliente es simplemente si un producto sigue apareciendo allí.
  • Transaction.updates solo entrega un reembolso que ocurrió mientras tu app estaba cerrada si empiezas a escuchar al arrancar, así que un Task que falta significa un reembolso perdido.
  • La detección del lado del cliente solo se activa mientras la app está abierta, por eso el REFUND de App Store Server Notifications V2 sigue siendo la señal autoritativa que evita que pagues por atender a un usuario reembolsado.
  • Un reembolso se puede revertir, y cuando ocurre, los campos de revocación se eliminan de la transacción y se espera que restaures el acceso que cortaste.

La mayoría de las apps se enteran de un reembolso de Apple por su servidor, a través de una App Store Server Notification, y nunca notan que el mismo reembolso ya está dentro de la app. Está en la transacción, en una propiedad llamada revocationDate, y leerla permite que tu app corte el acceso de un cliente reembolsado la próxima vez que la abra, en lugar de esperar a un trabajo del backend. La detección de reembolsos de StoreKit 2 es una señal del lado del cliente que la mayoría de los equipos se saltan. Aquí verás exactamente dónde aparece un reembolso en el dispositivo, qué te dice, qué no, y por qué debe ir junto a tus notificaciones de servidor y no en su lugar.

Dónde aparece un reembolso dentro de StoreKit 2

StoreKit 2 te entrega las transacciones como valores firmados, y un reembolso no elimina la transacción. La marca. Dos propiedades de la Transaction llevan esa marca, y ambas se mantienen en nil durante toda la vida de una compra sana. Cuando una de ellas pasa a no ser nil, la App Store ha recuperado la compra.

revocationDate es el campo que cambia

revocationDate es un Date opcional. La propia descripción de Apple es exacta: es la fecha en que la App Store reembolsó la transacción o la revocó de Family Sharing. Para una compra que sigue siendo válida, es nil. En el momento en que se procesa un reembolso, contiene la marca de tiempo de ese reembolso. Esa única comprobación, si revocationDate no es nil, es toda la detección de reembolsos del lado del cliente. Todo lo demás es matiz apilado encima.

revocationReason te dice por qué Apple la retiró

revocationReason está junto a la fecha y explica la causa. StoreKit le da dos valores que importan para los reembolsos. developerIssue significa que el cliente le dijo a Apple que el reembolso se debía a un problema real o percibido en tu app. other abarca todos los demás motivos. Un tercer valor, upgradedToBundle, no es un reembolso en absoluto; marca una transacción que la App Store revocó porque el cliente pasó a un paquete de suscripción. Lee el motivo antes de actuar, porque developerIssue es el que vale la pena contar: un grupo de ellos es tu propio producto diciéndote dónde falló.

PropiedadTipoQué significa un valor no nil
revocationDateDate?La App Store reembolsó esta transacción, o la revocó a través de Family Sharing, en esta fecha
revocationReason es developerIssuemotivoEl cliente señaló un problema real o percibido en tu app
revocationReason es othermotivoEl reembolso ocurrió por algún otro motivo que Apple no detalla
revocationReason es upgradedToBundlemotivoNo es un reembolso; la transacción se revocó porque el cliente cambió a un paquete de suscripción

currentEntitlements ya descarta una compra reembolsada

No siempre tienes que leer los campos de revocación tú mismo. Transaction.currentEntitlements es la secuencia de compras a las que un cliente sigue teniendo derecho ahora mismo, y Apple la construye para dejar fuera las que no deberías honrar. Un producto que la App Store ha reembolsado o revocado no aparece en ella. Tampoco las suscripciones caducadas, ni los consumibles, que desaparecen en el momento en que se agotan.

Eso hace de currentEntitlements el filtro más limpio para el acceso. Pregúntale qué posee el cliente, concede exactamente eso, y un reembolso elimina el derecho por ti sin una sola comprobación de revocationDate. Los campos de revocación son para cuando quieres el detalle, la fecha y el motivo, para registrar el evento o reaccionar a él. La lista de derechos es para la pregunta llana de si mantener las luces encendidas.

La detección de reembolsos de StoreKit 2 en la práctica, desde el arranque y mientras la app se ejecuta

Hay dos momentos en que tu app puede captar un reembolso en el dispositivo, y necesitan código distinto. Uno es mientras la app está abierta y un reembolso ocurre en vivo o en otro dispositivo. El otro es al arrancar, poniéndote al día con todo lo que cambió mientras estabas cerrada. Falla en el segundo y tu detección de reembolsos de StoreKit 2 tiene un agujero justo donde caen la mayoría de los reembolsos, porque los clientes rara vez tienen tu app abierta cuando piden uno.

Empieza a escuchar al arrancar o te pierdes los reembolsos que ocurrieron mientras estaba cerrada

Transaction.updates es la secuencia asíncrona que emite una transacción cada vez que el sistema crea o actualiza una fuera de tu app o en otro dispositivo, un reembolso incluido. La instrucción de Apple es tajante: inicia un Task que la recorra en cuanto tu app arranque, o puedes perder las transacciones que entrega una sola vez al inicio. Un reembolso que llegó de madrugada aparece a través de updates la próxima vez que la app se abre, pero solo si ya hay un escucha en marcha para recibirlo. Sin escucha, no hay evento, y el reembolso queda invisible hasta que algo más lo reconcilie.

Una compra en el mismo dispositivo no llega a través de updates

Una trampa atrapa a quienes prueban reembolsos a mano. Una compra normal hecha en el mismo dispositivo no llega a través de updates; StoreKit la devuelve directamente desde el resultado de la llamada de compra. updates es para los cambios fuera de banda: reembolsos, aprobaciones de Ask to Buy, canjes de códigos de oferta y compras hechas en otro lugar. Así que construye tu manejo de reembolsos en torno a updates y currentEntitlements, no en torno al flujo de compra, porque el reembolso nunca volverá por el camino que tomó la venta.

Un recibo de papel arrugado sobre una superficie oscura con un tenue sello rojo estampado encima, que representa una transacción reembolsada que StoreKit 2 marca con una fecha de revocación

Lo que la detección del lado del cliente no puede hacer por ti

Leer reembolsos en el dispositivo es rápido y es gratis, pero tiene un techo, y fingir que no lo tiene es como se fuga el ingreso. El dispositivo solo sabe lo que StoreKit le ha dicho, y StoreKit solo habla mientras tu app se ejecuta. Un cliente que recibe un reembolso y nunca vuelve a abrir tu app es un cliente que tu comprobación del lado del cliente nunca ve.

Una revocationDate no siempre es un reembolso

El mismo campo cambia por Family Sharing. Cuando un cliente pierde el acceso a una compra compartida, porque el organizador lo quitó o el uso compartido terminó, esa transacción también recibe una revocationDate. Así que una fecha no nil significa que el cliente ya no tiene esta compra, que es exactamente lo que necesitas para el control de acceso, pero no siempre significa que salió dinero de vuelta. Si estás contando reembolsos para los ingresos, separa las revocaciones de Family Sharing de las reales antes de fiarte del número.

Un reembolso se puede revertir

Un reembolso no siempre es definitivo. Apple puede revertir uno, y cuando lo hace, los campos de revocación se eliminan de la transacción y la compra vuelve a ser válida. Si cortaste el acceso por el reembolso, se espera que lo restaures en la reversión. En el dispositivo eso aparece como otro evento de updates con una transacción limpia; en tu servidor es una notificación distinta, REFUND_REVERSED. Maneja solo el reembolso y dejarás varado a un cliente que paga sin acceso y con un recibo que funciona.

Lo que realmente cuesta una revocación tardía

Un reembolso rara vez es solo el precio de venta saliendo de tu cuenta. Para cuando se liquida, normalmente ya has gastado dinero real atendiendo esa compra, y ese gasto no vuelve. Las imágenes generadas cuestan minutos de GPU. Las respuestas del chat cuestan llamadas a la API del modelo que te facturaron por token. Las subidas cuestan almacenamiento que sigues pagando por retener. Si la compra financió un pago a un creador, ese dinero ya salió por la puerta. Nada de eso se revierte con el reembolso.

La detección del lado del cliente reduce la ventana en la única parte que aún puedes controlar, que es el gasto futuro. Cuanto antes sepas que una compra se reembolsó, antes dejas de atenderla. Pero el dispositivo solo te avisa mientras la app está abierta, así que un usuario reembolsado que nunca regresa conserva el acceso del lado del servidor que le concediste, costándote en silencio cada vez que un trabajo en segundo plano o un dispositivo sincronizado actúa en su nombre. El cliente hace la revocación rápida. No la hace garantizada.

Usa el cliente para la rapidez y el servidor para la verdad

El diseño limpio usa ambas señales para lo que cada una hace bien. En el dispositivo, Transaction.updates y currentEntitlements te dan una reacción local e instantánea en el momento en que un cliente reembolsado abre la app, buena para la interfaz y para terminar cambios de derechos sin una ida y vuelta. En el servidor, App Store Server Notifications V2 envía un mensaje REFUND que llega tanto si la app vuelve a abrirse como si no, que es la única señal que detiene de forma fiable el gasto de tu backend en una cuenta reembolsada.

SeñalDónde viveSe activa cuandoConfía en ella para
revocationDate en una transacciónDispositivo, StoreKit 2Tu app lee la transacciónDecirte que una compra concreta fue reembolsada o revocada
Transaction.updatesDispositivo, StoreKit 2Llega un reembolso mientras la app se ejecuta, o al arrancar si escuchasReaccionar al instante para un cliente que está presente
currentEntitlementsDispositivo, StoreKit 2Compruebas qué posee el cliente ahoraFiltrar el acceso sin rastrear los reembolsos tú mismo
Notificación REFUNDTu servidor, App Store Server Notifications V2Apple procesa el reembolso, con la app abierta o noDetener el gasto del lado del servidor en un cliente que nunca vuelve

Conecta las señales del dispositivo para el cliente que tiene el teléfono en la mano, y la notificación del servidor para el que no. El reembolso aparece en ambos lugares a propósito. Leer solo uno de ellos es como una cuenta reembolsada te sigue costando después de que la venta ya se fue.

Preguntas frecuentes

¿Cómo detecto un reembolso en StoreKit 2?
Comprueba la revocationDate de la transacción. Es nil para una compra válida y contiene una fecha una vez que la App Store reembolsa la transacción, así que una revocationDate no nil es la señal de que una compra fue reembolsada o revocada.
¿Cuál es la diferencia entre revocationDate y revocationReason?
revocationDate es cuándo la App Store recuperó la compra, y revocationReason es por qué. El motivo es developerIssue cuando el cliente señaló un problema en tu app y other para cualquier otra cosa.
¿Una compra reembolsada sigue apareciendo en currentEntitlements?
No. Transaction.currentEntitlements excluye las compras que la App Store ha reembolsado o revocado, así que un producto reembolsado cae por sí solo de los derechos del cliente, lo que lo convierte en un filtro seguro para el acceso.
¿StoreKit le avisará a mi app de un reembolso que ocurrió mientras la app estaba cerrada?
Solo si escuchas desde el arranque. Transaction.updates entrega esos cambios una sola vez al inicio, así que debes iniciar un Task que la recorra cuando tu app arranca o el reembolso se pierde hasta que algo más lo reconcilie.
¿Es suficiente por sí sola la detección de reembolsos del lado del cliente?
No. El dispositivo solo se entera de un reembolso mientras tu app se ejecuta, así que un cliente que nunca reabre la app es invisible para ella. El REFUND de App Store Server Notifications V2 es la señal que te llega de todos modos.
¿Una revocationDate siempre significa que el cliente fue reembolsado?
No. revocationDate también se establece cuando un cliente pierde una compra a través de Family Sharing, así que una fecha no nil significa que ya no tiene la compra pero no siempre que se devolvió dinero.

Fuentes y lecturas adicionales

RefundHalt

El piloto automático de reembolsos para App Store y Google Play

Seguir leyendo

La próxima solicitud de reembolso ya está en camino.

Configura RefundHalt en el tiempo que tardas en leer otro correo de soporte sobre un reembolso que no llegaste a impugnar.