# OQI-005: Integración Stripe - Análisis Técnico

**Módulo:** OQI-005 (pagos-stripe)
**Enfoque:** Frontend Stripe integration, flujos de checkout, métodos de pago
**Fecha:** 2026-01-25

---

## 1. Integración Stripe - Visión General

### Estado Actual
- ✅ **Checkout Modal:** Stripe Hosted Checkout (redirect)
- ✅ **Métodos de Pago:** Gestión tarjetas via API backend
- ✅ **Webhooks:** Procesados en backend (no visible en frontend)
- ⚠️ **Stripe.js Elements:** NO implementado (formulario manual sin tokenización)
- ⚠️ **3D Secure / SCA:** Delegado a backend/Stripe

### Flujo de Pago Principal

```
Usuario selecciona plan
    ↓
Pricing.tsx → handleSelectPlan()
    ↓
payment.service.createCheckoutSession(planId, interval)
    ↓
POST /payments/checkout → Backend crea Stripe Session
    ↓
Respuesta: { sessionId, url }
    ↓
window.location.href = url (redirige a Stripe Hosted Checkout)
    ↓
Usuario completa pago en Stripe
    ↓
Stripe redirige a CheckoutSuccess page con session_id param
    ↓
CheckoutSuccess espera 2s y llama fetchCurrentSubscription()
    ↓
Dashboard actualizado con nueva suscripción
```

---

## 2. Flujos de Checkout Implementados

### 2.1 Checkout Sesión (Subscripción)

**Página:** `Pricing.tsx`

```typescript
const handleSelectPlan = async (planId: string, selectedInterval: PlanInterval) => {
  try {
    const checkoutUrl = await createCheckoutSession(planId, selectedInterval);
    window.location.href = checkoutUrl;  // Stripe Hosted Checkout
  } catch (error) {
    console.error('Error creating checkout session:', error);
  }
};
```

**Endpoint Backend:**
- `POST /payments/checkout`
- **Params:** `{ planId, billingCycle (monthly/yearly), successUrl, cancelUrl }`
- **Response:** `{ sessionId, url }`

**Flujo:**
1. Obtiene lista de planes desde store
2. Filtra planes activos
3. Ordena por precio
4. Renderiza 4 tarjetas (grid responsivo)
5. Al hacer click → crea sesión checkout
6. Redirige a Stripe Hosted Checkout

**Características:**
- Toggle mensual/anual con visualización de descuento
- Badge "Más Popular" en plan principal
- Badge "Plan Actual" si ya tiene suscripción
- Cálculo mensual equivalente (price/12 para anual)
- Tabla comparativa características
- FAQ preguntas frecuentes

### 2.2 Cambio de Plan (Upgrade/Downgrade)

**Componente:** `SubscriptionUpgradeFlow.tsx`

**Flujo 3 Pasos:**

```
PASO 1: Seleccionar Plan
├── Lista planes activos
├── Muestra precio y 4 características
├── Indica si es Upgrade/Downgrade
└── Click → Fetch preview

PASO 2: Previsualizar Cambios
├── Comparación plan actual vs nuevo
├── Cálculo prorrateo (si upgrade → inmediato, si downgrade → fin período)
├── Resumen financiero:
│   ├── Crédito prorrateo (si aplica)
│   ├── Precio nuevo plan
│   └── Monto total a pagar/acreditar
├── Fecha efectiva
└── Botón confirmar

PASO 3: Éxito
├── Confirmación visual
├── Nombre plan nuevo
└── Botón cerrar
```

**Endpoint Backend:**
- `POST /payments/subscription/change-plan`
- **Params:** `{ planId, billingCycle }`
- **Response:** `{ id, status, amount, currentPeriodEnd, ... }`

**Nota:** Preview es MOCK en frontend (no implementado en backend)

```typescript
export async function previewSubscriptionChange(
  planId: string,
  interval: PlanInterval,
  couponCode?: string
): Promise<SubscriptionPreview> {
  // Returns hardcoded mock - needs backend implementation
  return {
    subtotal: 0,
    discount: 0,
    tax: 0,
    total: 0,
    currency: 'USD',
    interval,
  };
}
```

### 2.3 Cancelación de Suscripción

**Página:** `Billing.tsx` → Tab "Resumen"

**Endpoint Backend:**
- `POST /payments/subscription/cancel`
- **Params:** `{ immediately: boolean }`
- **Response:** `{ id, status: 'canceled', canceledAt, ... }`

**Comportamiento:**
- Por defecto `immediately = false` (cancela al final del período)
- UI muestra aviso: "Tu suscripción se cancelará el [fecha]"
- Opción "Reactivar" disponible antes de la fecha de cancelación

**Endpoint Backend (Reactivar):**
- `POST /payments/subscription/resume`
- **Response:** `{ id, status: 'active', ... }`

---

## 3. Métodos de Pago

### 3.1 Agregar Método de Pago

**Componente:** `PaymentMethodForm.tsx`

**Flujo:**
```
Formulario Manual (sin Stripe.js)
├── Card Number (validación Luhn, detección brand)
├── Cardholder Name
├── Expiry (MM/YY)
├── CVC (3-4 dígitos)
└── Checkbox "Set as Default"

Click "Add Card"
    ↓
Validaciones locales:
  ├── Nombre no vacío
  ├── Número 13-19 dígitos
  ├── Fecha válida (MM 1-12, no expirada)
  └── CVC 3-4 dígitos
    ↓
POST /payments/methods
    ↓
Response: { id, brand, last4, expiryMonth, expiryYear, isDefault }
```

**Problemas de Seguridad:**
- ⚠️ **NO usa Stripe.js Elements** - tarjeta se envía al backend en texto plano (NO compliant PCI DSS en frontend)
- ✅ Debería usar: `@stripe/react-stripe-js` con `CardElement`
- ✅ Token debe generarse en frontend y pasarse al backend

**Validaciones Locales:**
```typescript
const formatCardNumber = (value: string) => {
  const v = value.replace(/\s+/g, '').replace(/[^0-9]/gi, '');
  const matches = v.match(/\d{4,16}/g);
  const match = (matches && matches[0]) || '';
  const parts = [];
  for (let i = 0, len = match.length; i < len; i += 4) {
    parts.push(match.substring(i, i + 4));
  }
  return parts.length ? parts.join(' ') : value;
};

const getCardType = (number: string): string => {
  const cleanNumber = number.replace(/\s/g, '');
  if (/^4/.test(cleanNumber)) return 'Visa';
  if (/^5[1-5]/.test(cleanNumber)) return 'Mastercard';
  if (/^3[47]/.test(cleanNumber)) return 'Amex';
  if (/^6(?:011|5)/.test(cleanNumber)) return 'Discover';
  return 'Card';
};
```

### 3.2 Gestionar Métodos de Pago

**Componente:** `PaymentMethodsList.tsx`

**Funcionalidades:**
- Listar métodos guardados (brand + last4)
- Badge "Default" si es método predeterminado
- Alerta si vence en 3 meses
- Alerta si ya está expirado (desactivado)
- Menú dropdown (Set as Default, Remove)
- Modal de confirmación antes de eliminar

**Endpoints:**
| Acción | Método | Endpoint |
|--------|--------|----------|
| Listar | GET | `/payments/methods` |
| Agregar | POST | `/payments/methods` |
| Establecer Default | POST | `/payments/methods/default` |
| Eliminar | DELETE | `/payments/methods/{id}` |

**Lógica Expiración:**
```typescript
const isExpiringSoon = (expMonth: number, expYear: number) => {
  const now = new Date();
  const expDate = new Date(expYear, expMonth - 1);
  const threeMonths = new Date();
  threeMonths.setMonth(threeMonths.getMonth() + 3);
  return expDate <= threeMonths && expDate >= now;
};

const isExpired = (expMonth: number, expYear: number) => {
  const now = new Date();
  const expDate = new Date(expYear, expMonth);
  return expDate < now;
};
```

### 3.3 Portal de Stripe

**Implementación:** `Billing.tsx` → Botón "Portal de Stripe"

**Endpoint Backend:**
- `POST /payments/billing-portal`
- **Params:** `{ returnUrl }`
- **Response:** `{ url }`

**Uso:**
```typescript
const handleOpenBillingPortal = async () => {
  try {
    const { url } = await createPortalSession(returnUrl);
    window.location.href = url;
  } catch (error) {
    console.error('Error opening portal:', error);
  }
};
```

**Funcionalidades en Portal (delegadas a Stripe):**
- Cambiar método de pago predeterminado
- Actualizar información de facturación
- Cambiar plan
- Cancelar/reactivar suscripción
- Descargar facturas
- Ver historial de pagos

---

## 4. Flujos Post-Checkout

### 4.1 Checkout Success

**Página:** `CheckoutSuccess.tsx`

```
URL: /checkout/success?session_id=cs_...

Renderiza:
├── Loading (2 segundos - espera webhooks Stripe)
├── Entonces:
│   ├── CheckCircle icon (verde)
│   ├── "Pago Exitoso"
│   ├── Detalles suscripción:
│   │   ├── Plan name
│   │   ├── Ciclo (mensual/anual)
│   │   ├── Estado (Activo)
│   │   └── Próxima factura
│   └── Botones:
│       ├── Ir al Dashboard
│       └── Ver Detalles de Facturación
└── Session ID visible (debug)
```

**Lógica:**
```typescript
useEffect(() => {
  const timer = setTimeout(async () => {
    try {
      await fetchCurrentSubscription();  // Refresca desde backend
    } catch (error) {
      console.error('Error fetching subscription:', error);
    } finally {
      setLoading(false);
    }
  }, 2000);  // Espera webhooks Stripe
  return () => clearTimeout(timer);
}, [fetchCurrentSubscription]);
```

### 4.2 Checkout Cancel

**Página:** `CheckoutCancel.tsx`

```
URL: /checkout/cancel?reason=expired|user_cancelled

Renderiza:
├── XCircle icon (gris)
├── "Pago Cancelado"
├── Mensaje basado en reason:
│   ├── reason=expired: "La sesión ha expirado"
│   └── default: "Has cancelado el proceso"
├── Help section:
│   ├── Si tarjeta rechazada → intenta otro método
│   ├── Contactar soporte
│   └── Pagos seguros via Stripe
└── Botones:
    ├── Volver a Planes
    └── Contactar Soporte
```

---

## 5. Cupones de Descuento

**Componente:** `CouponForm.tsx`

**Flujo:**
```
Input código (uppercase)
    ↓
Click "Apply" o Enter
    ↓
POST /payments/coupons/validate
    ↓
Respuesta:
{
  valid: boolean,
  discountType: 'percent' | 'fixed',
  discountValue: number,
  message?: string
}
    ↓
Si válido:
├── Calcula descuento en monto
├── Muestra badge verde con código
├── Botón X para quitar
└── onApply(couponInfo)

Si inválido:
└── Muestra error
```

**Estados:**
- No aplicado: Form vacío
- Validando: Spinner
- Aplicado: Badge verde con descuento
- Error: Mensaje rojo

**Tipos de Descuento:**
```typescript
interface CouponInfo {
  code: string;
  valid: boolean;
  discountType: 'percent' | 'fixed';
  discountValue: number;
  discountAmount?: number;  // Calculado en frontend
  expiresAt?: string;
  minPurchase?: number;
  maxUses?: number;
  usedCount?: number;
}
```

---

## 6. Matriz de Endpoints Stripe-Related

| Endpoint | Método | Descripción | Frontend |
|----------|--------|-------------|----------|
| `/payments/plans` | GET | Lista planes activos | Pricing.tsx |
| `/payments/plans/{slug}` | GET | Detalle plan | (Optional) |
| `/payments/checkout` | POST | Crear sesión Stripe | Pricing.tsx |
| `/payments/subscription` | GET | Suscripción actual | Billing.tsx |
| `/payments/subscription/change-plan` | POST | Cambiar plan | SubscriptionUpgradeFlow |
| `/payments/subscription/cancel` | POST | Cancelar suscripción | Billing.tsx |
| `/payments/subscription/resume` | POST | Reactivar suscripción | SubscriptionCard |
| `/payments/methods` | GET | Listar métodos pago | PaymentMethodsList |
| `/payments/methods` | POST | Agregar método | PaymentMethodForm |
| `/payments/methods/default` | POST | Establecer default | PaymentMethodsList |
| `/payments/methods/{id}` | DELETE | Eliminar método | PaymentMethodsList |
| `/payments/billing-portal` | POST | Portal Stripe | Billing.tsx |
| `/payments/coupons/validate` | POST | Validar cupón | CouponForm |
| `/payments/invoices` | GET | Listar facturas | Billing.tsx, InvoiceList |
| `/payments/invoices/{id}` | GET | Detalle factura | InvoiceDetail |
| `/payments/invoices/{id}/pdf` | GET | Descargar PDF | InvoiceList, InvoiceDetail |
| `/payments/billing-info` | GET/PUT | Información facturación | BillingInfoForm |
| `/payments/usage` | GET | Límites del plan actual | UsageProgress |
| `/payments/wallet` | GET | Saldo wallet | WalletCard |
| `/payments/wallet/deposit` | POST | Depositar | WalletDepositModal |
| `/payments/wallet/withdraw` | POST | Retirar | WalletWithdrawModal |
| `/payments/wallet/transactions` | GET | Historial transacciones | TransactionHistory |
| `/payments/summary` | GET | Resumen completo | (Dashboard) |

---

## 7. Variables de Entorno

```env
# Obtenido de import.meta.env
VITE_API_URL=https://api.trading-platform.com/api/v1

# Publicable (lado cliente)
VITE_STRIPE_PUBLIC_KEY=pk_live_... (NO visible en frontend actual)

# Nota: Backend maneja STRIPE_SECRET_KEY
```

**Configuración actual en payment.service.ts:**
```typescript
const API_BASE_URL = import.meta.env?.VITE_API_URL || '/api/v1';
```

---

## 8. Seguridad - Notas Críticas

### Cumplimiento PCI DSS
| Item | Estado | Nota |
|------|--------|------|
| Tokenización Stripe | ⚠️ Parcial | Backend maneja, frontend envía números en texto |
| Stripe.js Elements | ❌ No | Necesario para compliant |
| 3D Secure / SCA | ✅ Backend | Manejado por Stripe + backend |
| HTTPS | ✅ Asumido | URL origen es HTTPS |
| Input Validation | ✅ Parcial | Cliente-side Luhn check presente |
| Rate Limiting | ❌ Desconocido | Debe estar en backend |

### Recomendaciones
1. **Implementar Stripe.js Elements** para PaymentMethodForm
2. **Usar Payment Intent API** con confirmación cliente
3. **Validar CSRF tokens** en todos los POST
4. **Implementar retry logic** en checkouts fallidos
5. **Auditar logs** de transacciones en backend

---

## 9. Estado de Implementación

### Completo ✅
- Checkout sesión (Hosted Checkout)
- Cambio de plan (preview MOCK)
- Cancelación/reactivación suscripción
- Gestión métodos de pago
- Cupones descuento
- Portal Stripe

### Parcial ⚠️
- Validación cupones (backend debe validar más)
- Preview cambio plan (MOCK, no real)
- Métodos de pago (falta Stripe.js Elements)

### No Implementado ❌
- Refunds UI
- Histórico cambios de plan
- Preview factura antes de pagar
- Payment Intent flow completo
- Apple Pay / Google Pay

