Durante las pruebas de IAP antes del lanzamiento, nos encontramos con un problema típico pero muy confuso:
- El usuario pulsa «Continuar compra»
- El botón pasa a «Procesando»
- Unos segundos después vuelve a «Continuar compra»
- No aparece la ventana de confirmación de compra de App Store
Este artículo documenta todo el recorrido de diagnóstico, los logs clave, la causa raíz y una estrategia de corrección aplicable antes de publicar.
1. Síntoma
La lista de productos en la pantalla de compra cargaba correctamente, pero al pulsar comprar:
- Se llamaba a
purchase() - No aparecía ningún diálogo del sistema
- La UI terminaba volviendo al estado de no comprado
A simple vista parecía que «la compra no se iniciaba», pero los logs mostraban otra realidad.
2. Primera ronda: verificar si el flujo de compra realmente se ejecuta
Primero añadimos logs en todos los puntos críticos del flujo:
- Parámetros del clic en el botón
- Entrada de
purchaseSelectedProduct()y ramas deguard - Antes/después de
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
Resultado:
purchase()devolvía.success- La verificación de la transacción era correcta y también se ejecutaba
transaction.finish() - Pero
entitledIDsseguía vacío
Esto indica que: no era «la compra no arrancó», sino «tras la compra no se reconocieron los derechos».
3. Hallazgos clave al profundizar
Al ampliar los logs, aparecieron señales decisivas:
- El recuento de iteraciones de
Transaction.currentEntitlementsera0 Transaction.latest(for: monthly)tenía un registro válido, pero caducado- Más importante: antes de comprar había transacciones antiguas en
Transaction.unfinished - El
purchase()actual devolvía una transacción de una cadena antigua, no de una compra interactiva nueva
Esto explica por qué «no sale ventana»:
- StoreKit devuelve primero una transacción antigua procesable (unfinished / cadena histórica)
- Tu código la finaliza con
finish() - La solicitud parece «exitosa», pero no pasa por el flujo de confirmación nuevo que espera el usuario
4. Resumen de la causa raíz
No fue un fallo puntual, sino la combinación de dos factores:
- Transacciones no finalizadas (
unfinished transaction) que interfieren en el flujo de compra - Cadena de suscripción sandbox en estado caducado (aunque exista
latest transaction, puede no ser un entitlement activo)
El comportamiento final fue: sin ventana, éxito aparente y derechos vacíos.
5. Estrategia de corrección (validada en práctica)
5.1 Limpiar transacciones unfinished antes de comprar
Al iniciar la app y antes de comprar, recorre Transaction.unfinished y ejecuta finish() en las transacciones verificadas para evitar que las transacciones antiguas secuestren el flujo nuevo.
5.2 Mantener y reforzar logs de diagnóstico (recomendado antes de publicar)
Conviene mantener estos logs activos hasta el lanzamiento:
- Snapshot de
unfinished transaction subscription status(state, renewal info)- Recuento de iteraciones de
currentEntitlements - Datos de
transaction/expirationenlatest(for:)
5.3 Doble vía para validar derechos (mejora opcional)
Si en algunos entornos currentEntitlements queda vacío temporalmente, puedes usar latest(for:) como fallback (siempre validando que siga activo).
6. Checklist IAP antes del lanzamiento
- Antes de cada prueba, verifica el estado de suscripción de la cuenta Sandbox (si está caducada o no)
- Registra el número de
unfinisheden rutas críticas - Prueba tres escenarios: «primera compra / renovación tras caducar / restaurar compras»
- Revisa consistencia entre
currentEntitlementsylatest - Si aparece «sin ventana», comprueba primero si cayó en una ruta de transacción antigua
7. Conclusión
«Pulsar comprar sin ventana» no implica necesariamente un problema de UI o de timing de llamada.
En StoreKit 2, el estado de transacciones, la cola de unfinished y el ciclo de vida de la suscripción afectan directamente el resultado final.
Añadir estos logs y la limpieza previa antes de publicar reduce muchos problemas de IAP difíciles de reproducir en producción.