Durante las pruebas de IAP antes del lanzamiento, nos topamos con un problema típico pero muy confuso:
- El usuario toca «Continuar compra»
- El botón cambia a «Procesando»
- Después de unos segundos 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 bien, pero al tocar comprar:
- Se llamaba a
purchase() - No aparecía ningún diálogo del sistema
- La UI terminaba regresando al estado de no comprado
A primera vista parecía que «la compra no arrancaba», pero los logs mostraban otra historia.
2. Primera ronda: confirmar si el flujo de compra realmente corre
Primero agregamos logs en todos los puntos críticos del flujo:
- Parámetros del toque 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 fue correcta y también se ejecutó
transaction.finish() - Pero
entitledIDsseguía vacío
Esto indica que: no era «la compra no empezó», sino «después de comprar no se reconocieron los permisos».
3. Hallazgos clave al profundizar
Al ampliar los logs, aparecieron señales decisivas:
- El conteo de iteraciones de
Transaction.currentEntitlementsera0 Transaction.latest(for: monthly)tenía un registro válido, pero vencido- Más importante: antes de comprar había transacciones antiguas en
Transaction.unfinished - El
purchase()actual devolvía una transacción de una cadena anterior, 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 una sola falla, 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 vencido (aunque exista
latest transaction, puede no ser un entitlement activo)
El comportamiento final fue: sin ventana, éxito aparente y permisos 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)- Conteo de iteraciones de
currentEntitlements - Datos de
transaction/expirationenlatest(for:)
5.3 Doble vía para validar permisos (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á vencida o no)
- Registra el número de
unfinisheden rutas críticas - Prueba tres escenarios: «primera compra / renovación después de vencer / restaurar compras»
- Revisa consistencia entre
currentEntitlementsylatest - Si aparece «sin ventana», revisa primero si cayó en una ruta de transacción antigua
7. Conclusión
«Tocar 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.
Agregar estos logs y la limpieza previa antes de publicar reduce muchos problemas de IAP difíciles de reproducir en producción.