trading-platform/docs/02-definicion-modulos/OQI-005-payments-stripe/requerimientos/RF-PAY-002-checkout.md
rckrdmrd c1b5081208 feat(ml): Complete FASE 11 - BTCUSD update and comprehensive documentation alignment
ML Engine Updates:
- Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records
- Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence)
- Backtest results: +176.71R profit with aggressive_filter strategy

Documentation Consolidation:
- Created docs/99-analisis/_MAP.md index with 13 new analysis documents
- Consolidated inventories: removed duplicates from orchestration/inventarios/
- Updated ML_INVENTORY.yml with BTCUSD metrics and training results
- Added execution reports: FASE11-BTCUSD, correction issues, alignment validation

Architecture & Integration:
- Updated all module documentation with NEXUS v3.4 frontmatter
- Fixed _MAP.md indexes across all folders
- Updated orchestration plans and traces

Files: 229 changed, 5064 insertions(+), 1872 deletions(-)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 09:31:29 -06:00

504 lines
17 KiB
Markdown

---
id: "RF-PAY-002"
title: "Checkout con Stripe Elements"
type: "Requirement"
status: "Done"
priority: "Alta"
epic: "OQI-005"
project: "trading-platform"
version: "1.0.0"
created_date: "2025-12-05"
updated_date: "2026-01-04"
---
# RF-PAY-002: Checkout con Stripe Elements
**Version:** 1.0.0
**Fecha:** 2025-12-05
**Estado:** ✅ Implementado
**Prioridad:** P0 (Crítica)
**Story Points:** 8
**Épica:** [OQI-005](../_MAP.md)
---
## Descripción
El sistema debe proporcionar interfaces de checkout seguras y optimizadas usando Stripe Elements y Stripe Checkout para procesar pagos únicos y suscripciones, cumpliendo con estándares PCI-DSS sin manejar datos de tarjeta directamente.
---
## Objetivo de Negocio
- Maximizar conversión con UX optimizada de checkout
- Eliminar responsabilidad PCI-DSS
- Reducir fraude con validaciones nativas de Stripe
- Soportar múltiples métodos de pago (tarjetas, wallets)
- Optimizar para mobile y desktop
---
## Modos de Checkout
### Modo 1: Embedded Checkout (Stripe Elements)
**Uso:** Compras de cursos, pagos únicos dentro de la app
**Ventajas:**
- Control total de UX
- Integración nativa en SPA
- Customización de estilos
- Mejor para flujos complejos
**Componentes:**
```javascript
- CardNumberElement
- CardExpiryElement
- CardCvcElement
- PaymentElement (todo-en-uno)
```
### Modo 2: Hosted Checkout (Stripe Checkout)
**Uso:** Suscripciones, compras rápidas
**Ventajas:**
- Implementación rápida
- Optimización automática de conversión
- Soporte automático de wallets (Apple Pay, Google Pay)
- Reducción de fricción
---
## Requisitos Funcionales
### RF-PAY-002.1: Inicialización de Stripe Elements
**DEBE:**
1. Cargar Stripe.js desde CDN (`https://js.stripe.com/v3/`)
2. Inicializar con Publishable Key
3. Crear instancia de Elements con opciones de estilo
4. Montar elementos en contenedores DOM específicos
5. Aplicar tema según modo claro/oscuro de la app
**Ejemplo de configuración:**
```typescript
const stripe = await loadStripe(STRIPE_PUBLISHABLE_KEY);
const elements = stripe.elements({
appearance: {
theme: 'stripe',
variables: {
colorPrimary: '#0066ff',
colorBackground: '#ffffff',
colorText: '#1a1a1a',
colorDanger: '#df1b41',
fontFamily: 'Inter, system-ui, sans-serif',
spacingUnit: '4px',
borderRadius: '8px',
}
},
clientSecret: paymentIntent.clientSecret
});
```
### RF-PAY-002.2: Creación de Payment Intent
**Backend DEBE:**
1. Recibir request con `amount`, `currency`, `type`, `metadata`
2. Validar monto mínimo ($0.50 USD)
3. Crear PaymentIntent en Stripe
4. Guardar registro en `billing.payments` (status: `pending`)
5. Retornar `clientSecret` al frontend
**Request:**
```json
POST /api/v1/payments/create-payment-intent
{
"amount": 4900,
"currency": "usd",
"type": "course_purchase",
"courseId": "uuid-curso",
"description": "Curso de Trading Avanzado"
}
```
**Response:**
```json
{
"clientSecret": "pi_3Abc123_secret_xyz",
"paymentIntentId": "pi_3Abc123",
"amount": 4900,
"currency": "usd"
}
```
### RF-PAY-002.3: Procesamiento de Pago
**Frontend DEBE:**
1. Recopilar información de tarjeta (via Stripe Elements)
2. Validar campos antes de submit
3. Llamar `stripe.confirmCardPayment(clientSecret, { payment_method })`
4. Manejar 3D Secure (SCA) automáticamente
5. Mostrar estados de loading/success/error
**Flujo de confirmación:**
```javascript
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: cardElement,
billing_details: {
name: userName,
email: userEmail,
}
}
}
);
if (error) {
// Mostrar error
} else if (paymentIntent.status === 'succeeded') {
// Pago exitoso
}
```
### RF-PAY-002.4: Hosted Checkout Session
**Backend DEBE:**
1. Crear Checkout Session en Stripe
2. Configurar `success_url` y `cancel_url`
3. Incluir line items con productos
4. Configurar modo (`payment` o `subscription`)
5. Retornar URL de checkout
**Request:**
```json
POST /api/v1/payments/create-checkout-session
{
"priceId": "price_1Sb3k64dPtEGmLmpm5n5bbJH",
"mode": "subscription",
"successUrl": "https://app.trading.com/checkout/success?session_id={CHECKOUT_SESSION_ID}",
"cancelUrl": "https://app.trading.com/pricing"
}
```
**Response:**
```json
{
"url": "https://checkout.stripe.com/c/pay/cs_test_abc123...",
"sessionId": "cs_test_abc123"
}
```
### RF-PAY-002.5: Validación de Formulario
**DEBE validar:**
- Número de tarjeta completo y válido (Luhn algorithm)
- Fecha de expiración futura
- CVC de 3-4 dígitos
- Nombre del titular no vacío
- Email válido
**Feedback en tiempo real:**
- Indicador visual de validez de campo
- Detección automática de marca de tarjeta (Visa, Mastercard, etc.)
- Mensajes de error específicos
### RF-PAY-002.6: Manejo de 3D Secure (SCA)
**DEBE:**
1. Detectar automáticamente si tarjeta requiere SCA
2. Mostrar modal/iframe de autenticación del banco
3. Esperar confirmación del usuario
4. Continuar procesamiento si autenticación exitosa
5. Rechazar pago si autenticación falla
---
## Flujo Completo de Checkout
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Usuario │ │ Frontend │ │ Backend │ │ Stripe │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │ │
│ Inicia compra │ │ │
│──────────────────▶│ │ │
│ │ │ │
│ │ POST /create- │ │
│ │ payment-intent │ │
│ │──────────────────▶│ │
│ │ │ │
│ │ │ Create │
│ │ │ PaymentIntent │
│ │ │──────────────────▶│
│ │ │◀──────────────────│
│ │ │ clientSecret │
│ │ │ │
│ │ │ Save to DB │
│ │ │ (pending) │
│ │ │ │
│ │◀──────────────────│ │
│ │ { clientSecret } │ │
│ │ │ │
│◀──────────────────│ │ │
│ Muestra form │ │ │
│ de tarjeta │ │ │
│ │ │ │
│ Ingresa datos │ │ │
│ de tarjeta │ │ │
│──────────────────▶│ │ │
│ │ │ │
│ Click "Pagar" │ │ │
│──────────────────▶│ │ │
│ │ │ │
│ │ confirmCardPayment│ │
│ │ (clientSecret) │ │
│ │──────────────────────────────────────▶│
│ │ │ │
│ │ │ │ Requiere
│◀──────────────────────────────────────────────────────────│ 3DS?
│ Modal 3DS │ │ │
│ del banco │ │ │
│ │ │ │
│ Autentica │ │ │
│──────────────────────────────────────────────────────────▶│
│ │ │ │
│ │◀──────────────────────────────────────│
│ │ { paymentIntent: │ │
│ │ status: │ │
│ │ 'succeeded' } │ │
│ │ │ │
│ │ │◀──────────────────│
│ │ │ Webhook: │
│ │ │ payment_intent. │
│ │ │ succeeded │
│ │ │ │
│ │ │ Update DB │
│ │ │ (succeeded) │
│ │ │ Grant access │
│ │ │ │
│◀──────────────────│ │ │
│ "Pago exitoso!" │ │ │
│ │ │ │
```
---
## Reglas de Negocio
### RN-001: Montos Mínimos
- Pago único: **$0.50 USD mínimo**
- Suscripción: según plan ($19, $49, $99)
- Bloquear pagos inferiores con mensaje claro
### RN-002: Monedas Soportadas
- **USD:** Moneda principal
- **MXN, COP, ARS, CLP, PEN:** LATAM (futuro)
- Conversión automática con tasas de Stripe
### RN-003: Reintentos de Pago
- **No reintentar automáticamente** en frontend
- Mostrar error específico al usuario
- Permitir editar datos de tarjeta e intentar de nuevo
- Backend registra intentos fallidos para análisis
### RN-004: Timeouts
- Payment Intent válido por **24 horas**
- Checkout Session válido por **24 horas**
- Expirar clientSecret después de uso exitoso
---
## Métodos de Pago Soportados
| Método | Embedded | Hosted | Región |
|--------|----------|--------|--------|
| Tarjetas (Visa, MC, Amex) | ✅ | ✅ | Global |
| Apple Pay | ⚠️ via PaymentRequest | ✅ | USA, Mx |
| Google Pay | ⚠️ via PaymentRequest | ✅ | Global |
| OXXO | ❌ | ✅ | México |
| Efecty | ❌ | ✅ | Colombia |
| PSE | ❌ | ✅ | Colombia |
---
## Estilos y UX
### Customización de Stripe Elements
```typescript
const appearance = {
theme: 'stripe', // 'stripe' | 'night' | 'flat'
variables: {
colorPrimary: '#0066ff',
colorBackground: '#ffffff',
colorText: '#1a1a1a',
colorDanger: '#df1b41',
fontFamily: 'Inter, system-ui, sans-serif',
spacingUnit: '4px',
borderRadius: '8px',
fontSizeBase: '16px',
},
rules: {
'.Input': {
border: '1px solid #e0e0e0',
boxShadow: 'none',
},
'.Input:focus': {
border: '1px solid #0066ff',
boxShadow: '0 0 0 3px rgba(0, 102, 255, 0.1)',
},
'.Input--invalid': {
border: '1px solid #df1b41',
}
}
};
```
### Estados del Formulario
| Estado | Indicador Visual |
|--------|------------------|
| Idle | Campos vacíos, botón habilitado |
| Typing | Validación en tiempo real |
| Valid | Checkmark verde, botón resaltado |
| Invalid | Mensaje de error rojo |
| Processing | Spinner, botón deshabilitado |
| Success | Checkmark animado, redirect |
| Error | Mensaje de error, retry habilitado |
---
## Manejo de Errores
### Errores de Stripe Elements
| Error Code | Mensaje Usuario | Acción |
|------------|-----------------|--------|
| `card_declined` | Tu tarjeta fue rechazada. Intenta con otra. | Permitir cambiar tarjeta |
| `insufficient_funds` | Fondos insuficientes. | Sugerir otra tarjeta |
| `expired_card` | Tarjeta expirada. Verifica la fecha. | Validar fecha |
| `incorrect_cvc` | Código de seguridad incorrecto. | Reintentar CVC |
| `processing_error` | Error de procesamiento. Intenta de nuevo. | Retry |
| `rate_limit` | Demasiados intentos. Espera un momento. | Backoff 30s |
### Errores de Backend
| Error | Código HTTP | Mensaje Usuario |
|-------|-------------|-----------------|
| Monto inválido | 400 | El monto debe ser mayor a $0.50 USD |
| Producto no encontrado | 404 | El curso no existe |
| Ya comprado | 409 | Ya tienes acceso a este curso |
| Stripe API error | 502 | Error de procesamiento. Contacta soporte. |
---
## Seguridad
### PCI Compliance
- **NUNCA** enviar datos de tarjeta a backend
- Usar Stripe.js para tokenización
- Validar solo en frontend con Stripe Elements
- Backend solo maneja tokens `pm_xxx`
### Validación de Origen
- Validar `userId` del JWT contra `metadata.userId` del PaymentIntent
- Verificar que el usuario no haya comprado ya el producto
- Rate limiting: máximo 5 intentos por 15 minutos
### Prevención de Fraude
- Stripe Radar activado (detección automática)
- Requerir CVC siempre
- Habilitar 3D Secure para transacciones > $30 USD
- Bloquear IPs con alto índice de rechazo
---
## Configuración Requerida
```env
# Frontend (.env.local)
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51Sb3k64dPtEGmLmp...
# Backend (.env)
STRIPE_SECRET_KEY=sk_test_51Sb3k64dPtEGmLmp...
STRIPE_WEBHOOK_SECRET=whsec_...
FRONTEND_URL=https://app.trading.com
```
### Stripe Checkout URLs
```typescript
// success_url
https://app.trading.com/checkout/success?session_id={CHECKOUT_SESSION_ID}
// cancel_url
https://app.trading.com/pricing
```
---
## Webhooks Relacionados
| Evento | Acción |
|--------|--------|
| `payment_intent.succeeded` | Actualizar pago a succeeded, otorgar acceso |
| `payment_intent.payment_failed` | Actualizar pago a failed, enviar email |
| `payment_intent.canceled` | Actualizar pago a canceled |
| `checkout.session.completed` | Confirmar suscripción/compra |
| `checkout.session.expired` | Notificar expiración |
---
## Performance
### Optimizaciones
- **Lazy load** Stripe.js solo en páginas de checkout
- **Prefetch** clientSecret al mostrar producto
- **Cache** Price IDs en memoria (Redis)
- **Timeout** de 30s para confirmCardPayment
### Métricas a Rastrear
- Tiempo de carga de Stripe.js
- Tasa de conversión por paso (form → submit → success)
- Tasa de rechazo por tipo de error
- Tiempo promedio de checkout
---
## Criterios de Aceptación
- [ ] Stripe Elements se carga sin errores
- [ ] Formulario valida tarjeta en tiempo real
- [ ] Marcas de tarjeta se detectan automáticamente
- [ ] Payment Intent se crea correctamente desde backend
- [ ] 3D Secure funciona para tarjetas que lo requieren
- [ ] Errores de Stripe se muestran claramente al usuario
- [ ] Hosted Checkout redirige a Stripe correctamente
- [ ] Success/cancel URLs funcionan después de Checkout
- [ ] Estilos de Elements coinciden con tema de app
- [ ] Checkout es responsive en mobile y desktop
---
## Especificación Técnica Relacionada
- [ET-PAY-002: Stripe Elements Integration](../especificaciones/ET-PAY-002-stripe-elements.md)
## Historias de Usuario Relacionadas
- [US-PAY-002: Suscribirse a Plan](../historias-usuario/US-PAY-002-suscribirse.md)
- [US-PAY-005: Comprar Curso](../historias-usuario/US-PAY-005-comprar-curso.md)
- [US-PAY-006: Agregar Método de Pago](../historias-usuario/US-PAY-006-agregar-metodo-pago.md)