RF38: Usuario realiza pago de suscripción
Descripción
Como usuario autenticado, quiero pagar mi plan de suscripción para activar el acceso premium de Finnova.
El backend crea la Subscription en Stripe vinculada al Price del plan y al PaymentMethod del usuario. La activación del acceso premium se confirma de forma autoritativa por webhook (customer.subscription.created / invoice.payment_succeeded), no solo por la respuesta síncrona (Stripe §1 Alta y §Webhooks).
| Campo | Valor |
|---|---|
| Módulo | Subscription Module |
| Actor | Usuario autenticado |
| Endpoint | POST /subscription + POST /webhooks/stripe |
| Precondiciones | Sesión activa; método de pago válido (RF35); plan seleccionado (RF33) |
| Prioridad | Alta (MVP) |
| Etapa | MVP |
| Requisitos relacionados | RF33, RF35, RF45, RF40 |
Reglas de negocio
- RN-38.1 — La creación de la
Subscriptionusa idempotency key para evitar cobros duplicados ante reintentos. - RN-38.2 — El acceso premium se activa cuando llega el webhook validado de pago/suscripción exitosa, no por la respuesta inicial.
- RN-38.3 — Si el pago requiere 3D Secure, se completa el reto antes de confirmar.
- RN-38.4 — Si el pago falla, se inicia el flujo de dunning de Stripe y se notifica al usuario (RF41); no se activa premium.
- RN-38.5 — En móvil, el cobro respeta la estrategia elegida (in-app store billing o web-first con Stripe — Suscripciones Mobile).
Validaciones de entrada
| Campo | Reglas | Mensaje de error |
|---|---|---|
planId | Obligatorio. Plan válido. | "Selecciona un plan válido." |
paymentMethodId | Obligatorio si no hay default. Token válido. | "Registra un método de pago válido." |
| Webhook | Firma verificada con STRIPE_WEBHOOK_SECRET. | Se rechaza si la firma no es válida. |
Criterios de aceptación
Escenario 1: Pago y activación exitosos
Dado que tengo un método de pago válido y selecciono un plan de pago,
Cuando confirmo el pago,
Entonces el backend crea la Subscription en Stripe (idempotente),
Y al recibir el webhook validado de pago exitoso, activa mi acceso premium,
Y responde reflejando la suscripción activa.
Escenario 2: Pago requiere 3D Secure
Dado que mi banco exige 3DS, Cuando pago, Entonces completo el reto 3DS con Stripe, Y la suscripción se activa solo tras la confirmación.
Escenario 3: Pago fallido
Dado que el cobro es rechazado, Cuando intento pagar, Entonces no se activa premium, Y se inicia el dunning y se me notifica (RF41) con un mensaje claro.
Escenario 4: Webhook con firma inválida (seguridad)
Dado que llega un webhook a /webhooks/stripe con firma inválida,
Cuando el backend lo procesa,
Entonces lo rechaza sin alterar el estado de la suscripción.
Escenario 5: Reintento idempotente
Dado que reintento el pago por un fallo de red, Cuando llega con la misma idempotency key, Entonces Stripe no genera un cargo duplicado.
Criterios no funcionales
- Finnova nunca almacena datos de tarjeta (PCI SAQ A).
- La activación premium es consistente con el webhook (fuente de verdad).
- Comunicación TLS 1.2+; secretos de Stripe solo en el servidor.