# ST4.2: PCI-DSS Compliance - Context & Analysis **Blocker:** BLOCKER-002 **Prioridad:** P0 - CRÍTICO **Esfuerzo Estimado:** 80h **Fecha:** 2026-01-26 **Estado:** 🔄 ANÁLISIS EN PROGRESO --- ## C: CONTEXTO - Revisión Implementación Actual ### Backend (✅ PCI-DSS Compliant) #### 1. Payment Intents Implementation **Archivo:** `apps/backend/src/modules/payments/services/stripe.service.ts` **Métodos encontrados:** - ✅ `createPaymentIntent()` (línea 336) - Crea Payment Intent en Stripe - Retorna `clientSecret` al frontend - No maneja datos de tarjeta directamente - ✅ `confirmPaymentIntent()` (línea 361) - Confirma Payment Intent server-side - Usado para flujos sin cliente ```typescript async createPaymentIntent( userId: string, amount: number, currency: string = 'usd', metadata: Record = {} ): Promise { const customer = await this.getOrCreateCustomer(userId, userResult.rows[0].email); const paymentIntent = await stripe.paymentIntents.create({ amount: Math.round(amount * 100), currency, customer: customer.stripeCustomerId, metadata: { userId, ...metadata }, }); return paymentIntent; } ``` **Estado:** ✅ Implementación correcta PCI-DSS compliant --- #### 2. Webhook Handler **Archivo:** `apps/backend/src/modules/payments/controllers/payments.controller.ts` **Método:** `handleStripeWebhook()` (líneas 391-490) **Eventos procesados:** - ✅ `checkout.session.completed` - ✅ `customer.subscription.created` - ✅ `customer.subscription.updated` - ✅ `customer.subscription.deleted` - ✅ `invoice.paid` - ✅ `invoice.payment_failed` ```typescript export async function handleStripeWebhook(req: Request, res: Response, next: NextFunction): Promise { try { const signature = req.headers['stripe-signature'] as string; const event = stripeService.constructWebhookEvent(req.body, signature); switch (event.type) { case 'checkout.session.completed': { await handleCheckoutComplete(session); break; } // ... more cases } res.json({ received: true }); } catch (error) { next(error); } } ``` **Estado:** ✅ Webhook handler completo y funcional --- #### 3. Routes **Archivo:** `apps/backend/src/modules/payments/payments.routes.ts` **Rutas encontradas:** - ✅ `POST /payments/webhook` - Stripe webhook endpoint - ✅ `POST /payments/checkout` - Create checkout session - ✅ `POST /payments/wallet/deposit` - Wallet deposit (usado por DepositForm) **Estado:** ✅ Rutas configuradas correctamente --- ### Frontend - Análisis de Componentes #### 1. ✅ CORRECTO: DepositForm (PCI-DSS Compliant) **Archivo:** `apps/frontend/src/modules/investment/components/DepositForm.tsx` **Implementación:** ```typescript import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; // Renderiza CardElement (NO maneja datos de tarjeta) // Confirma pago con Stripe const { error, paymentIntent } = await stripe.confirmCardPayment( clientSecret, { payment_method: { card: cardElement, // ← Stripe.js maneja datos }, } ); ``` **Flujo:** 1. Usuario completa formulario (monto, cuenta) 2. Frontend llama `POST /api/v1/payments/wallet/deposit` 3. Backend crea Payment Intent → devuelve `clientSecret` 4. Frontend usa `stripe.confirmCardPayment(clientSecret, { payment_method: { card: cardElement } })` 5. Stripe procesa pago sin que frontend toque datos sensibles **Estado:** ✅ **100% PCI-DSS compliant** **Mensaje en UI (línea 260):** > "Your payment is secured by Stripe. We never store your card details." --- #### 2. ✅ CORRECTO: StripeElementsWrapper (PCI-DSS Compliant) **Archivo:** `apps/frontend/src/components/payments/StripeElementsWrapper.tsx` **Implementación:** ```typescript import { Elements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; // Carga Stripe.js const stripe = loadStripe(stripeConfig.publicKey, { locale: stripeConfig.locale, }); // Renderiza Elements provider {children} ``` **Comentario en código (línea 6):** > "IMPORTANT: This component is required for PCI-DSS compliance. > All payment forms must be wrapped with this provider." **Features:** - Carga Stripe.js desde CDN - Configura tema oscuro personalizado - Manejo de errores y loading states - HOC `withStripeElements()` para wrapping **Estado:** ✅ **Infraestructura correcta para PCI-DSS** --- #### 3. ❌ VIOLACIÓN CRÍTICA: PaymentMethodForm **Archivo:** `apps/frontend/src/components/payments/PaymentMethodForm.tsx` **🚨 VIOLACIONES DETECTADAS:** **Violación 1: Estado local almacena datos sensibles (líneas 30-32)** ```typescript const [cardNumber, setCardNumber] = useState(''); // ❌ PAN completo en memoria const [expiry, setExpiry] = useState(''); // ❌ Fecha expiración const [cvc, setCvc] = useState(''); // ❌ CVV/CVC ``` **Violación 2: Inputs directos para datos de tarjeta (líneas 151-202)** ```typescript setCardNumber(formatCardNumber(e.target.value))} placeholder="1234 5678 9012 3456" /> ``` **Violación 3: Envío de datos sensibles al backend (líneas 116-128)** ```typescript const result = await addPaymentMethod({ type: 'card', card: { number: cardNumber.replace(/\s/g, ''), // ❌ Envía PAN completo exp_month: parseInt(expiry.split('/')[0]), // ❌ Envía mes expiración exp_year: 2000 + parseInt(expiry.split('/')[1]), // ❌ Envía año expiración cvc, // ❌ Envía CVV }, billing_details: { name: cardholderName, }, setAsDefault: makeDefault, }); ``` **Comentario en código (línea 114):** > "In a real implementation, this would use Stripe.js to create a PaymentMethod" **PCI-DSS Requirements Violated:** - ❌ Requirement 3: Protect stored cardholder data - ❌ Requirement 4: Encrypt transmission of cardholder data - ❌ Requirement 6: Develop secure systems and applications **Nivel de Severidad:** 🔴 **CRÍTICO** **Estado de Uso:** - ✅ **NO está siendo usado** en ninguna página actualmente - Solo exportado en `components/payments/index.ts` - Parece ser código legacy/demo --- #### 4. ✅ CORRECTO: Billing Page **Archivo:** `apps/frontend/src/modules/payments/pages/Billing.tsx` **Gestión de métodos de pago:** - ✅ Usa **Stripe Customer Portal** para agregar métodos de pago (líneas 214-220) - ✅ Llama `openBillingPortal()` que redirige a portal hosted por Stripe - ✅ NO usa PaymentMethodForm ```typescript ``` **Estado:** ✅ **PCI-DSS compliant** (usa portal hosted por Stripe) --- #### 5. Frontend payment.service.ts **Archivo:** `apps/frontend/src/services/payment.service.ts` **Problemas encontrados:** **❌ Problema 1: No usa apiClient centralizado (líneas 29-43)** ```typescript // ❌ PROBLEMA: Axios local, no usa apiClient de ST4.1 const api = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, }); // ❌ Interceptor duplicado api.interceptors.request.use((config) => { const token = localStorage.getItem('auth_token'); // ❌ Clave diferente: 'auth_token' vs 'token' if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); ``` **Impacto:** - NO se beneficia del auto-refresh de ST4.1 - Puede causar 401 y logout inesperado - Inconsistencia con otros servicios **Prioridad:** ⚠️ ALTA (debe migrarse a apiClient como parte de ST4.1.3) --- ## A: ANÁLISIS - Gaps y Riesgos ### 1. Estado Actual PCI-DSS | Componente | Estado | Compliance | |------------|--------|------------| | Backend Payment Intents | ✅ Implementado | ✅ Compliant | | Backend Webhooks | ✅ Implementado | ✅ Compliant | | Frontend DepositForm | ✅ Usa CardElement | ✅ Compliant | | Frontend StripeElementsWrapper | ✅ Implementado | ✅ Compliant | | Frontend Billing Page | ✅ Usa Customer Portal | ✅ Compliant | | **Frontend PaymentMethodForm** | ❌ **Manejo directo datos** | ❌ **VIOLATION** | **Nivel de Riesgo Global:** 🟡 **MEDIO-BAJO** **Razón:** - El componente inseguro **NO está en uso** actualmente - Los flujos de producción usan implementaciones seguras - Riesgo: Developer podría usar PaymentMethodForm sin saber que es inseguro --- ### 2. Gaps Identificados #### Gap 1: Código Legacy Inseguro **Descripción:** PaymentMethodForm existe pero no cumple PCI-DSS **Impacto:** - Riesgo de uso accidental por developers - Confusión en código base (2 formas de agregar métodos de pago) **Remediación:** - ❌ ELIMINAR PaymentMethodForm.tsx - ✅ DOCUMENTAR que debe usarse Stripe Customer Portal o CardElement --- #### Gap 2: payment.service.ts no usa apiClient **Descripción:** Usa axios local en lugar de apiClient centralizado de ST4.1 **Impacto:** - NO se beneficia de auto-refresh tokens - Inconsistencia con otros servicios - Puede causar 401 inesperados **Remediación:** - ⚠️ Migrar a apiClient (parte de ST4.1.3) --- #### Gap 3: Falta documentación PCI-DSS **Descripción:** No hay documento técnico que explique arquitectura PCI-DSS **Impacto:** - Developers no saben qué componentes usar - Riesgo de implementaciones inseguras futuras **Remediación:** - 📄 Crear ET-PAY-006: PCI-DSS Architecture & Guidelines --- ### 3. Arquitectura Actual vs Ideal #### Arquitectura Actual (Flujos en Uso) ``` ┌─────────────────────────────────────────────────────────────┐ │ FLUJO ACTUAL (SEGURO) │ └─────────────────────────────────────────────────────────────┘ 1. AGREGAR MÉTODO DE PAGO: User → Billing Page → openBillingPortal() → Stripe Customer Portal ↓ User adds card ↓ Webhook → Backend ↓ Store PaymentMethod 2. DEPOSIT TO WALLET: User → DepositForm → → stripe.confirmCardPayment() ↓ ↓ (Stripe.js handles) Backend Payment Intent ↓ ↓ Success/Error Webhook updates DB ✅ AMBOS FLUJOS SON PCI-DSS COMPLIANT ``` #### Código Legacy (No Usado) ``` ┌─────────────────────────────────────────────────────────────┐ │ CÓDIGO LEGACY (INSEGURO - NO USADO) │ └─────────────────────────────────────────────────────────────┘ PaymentMethodForm: User → → addPaymentMethod() ↓ ↓ (PAN en memoria) POST /api con datos raw ↓ ↓ ❌ VIOLATION ❌ VIOLATION ❌ NO DEBE USARSE - ELIMINAR ``` --- ## P: PLANEACIÓN - Estrategia de Remediación ### Opción 1: ELIMINAR Código Inseguro (RECOMENDADA) **Acción:** 1. Eliminar `PaymentMethodForm.tsx` 2. Remover export de `components/payments/index.ts` 3. Documentar en ET-PAY-006 que se debe usar: - Stripe Customer Portal (para métodos de pago) - CardElement (para pagos one-time) **Ventajas:** - ✅ Elimina riesgo de uso accidental - ✅ Simplifica codebase - ✅ Clarifica arquitectura PCI-DSS **Desventajas:** - Ninguna (código no está en uso) **Esfuerzo:** 0.5h --- ### Opción 2: REFACTOR a Stripe Elements (No Recomendada) **Acción:** 1. Refactorizar PaymentMethodForm para usar CardElement 2. Integrar con Payment Intents backend 3. Mantener ambas opciones (Portal + Form) **Ventajas:** - ✅ Permite agregar métodos sin salir de la app **Desventajas:** - ❌ Más código a mantener - ❌ Stripe Customer Portal ya cubre este caso - ❌ Duplicación de funcionalidad **Esfuerzo:** 8h **Decisión:** ❌ NO RECOMENDADA --- ### Opción 3: MANTENER Status Quo (No Recomendada) **Acción:** - No tocar PaymentMethodForm - Confiar en que no se usará **Ventajas:** - Ninguna **Desventajas:** - ❌ Riesgo de uso accidental - ❌ Código legacy confunde a developers - ❌ No cumple con best practices **Decisión:** ❌ NO RECOMENDADA --- ## Recomendación Final ### ✅ OPCIÓN 1: Eliminar Código Inseguro + Documentar Arquitectura **Justificación:** 1. PaymentMethodForm **NO está en uso** → Eliminar es seguro 2. Flujos actuales (Customer Portal + CardElement) **YA cumplen** PCI-DSS 3. Eliminar código inseguro **previene** riesgos futuros 4. Documentación clara **guía** a developers **Tareas ST4.2:** | ID | Tarea | Esfuerzo | Prioridad | |----|-------|----------|-----------| | ST4.2.1 | ❌ ELIMINAR PaymentMethodForm.tsx | 0.25h | P0 | | ST4.2.2 | 📄 CREAR ET-PAY-006 PCI-DSS Architecture | 4h | P0 | | ST4.2.3 | ✅ Validar flujos actuales (tests) | 8h | P1 | | ST4.2.4 | 🔍 Security audit PCI-DSS SAQ-A | 8h | P1 | | ST4.2.5 | 📄 ACTUALIZAR developer guidelines | 2h | P2 | **Total Esfuerzo Revisado:** ~22h (en lugar de 80h originales) **Ahorro:** 58h (73% reducción) debido a que backend ya está completo y frontend usa implementaciones seguras --- ## Próximos Pasos 1. ✅ **Aprobar plan** (Opción 1) 2. 🔄 **Ejecutar ST4.2.1**: Eliminar PaymentMethodForm 3. 📄 **Ejecutar ST4.2.2**: Crear ET-PAY-006 4. ✅ **Ejecutar ST4.2.3**: Tests E2E de flujos de pago 5. 🔍 **Ejecutar ST4.2.4**: Security audit 6. ✅ **Marcar ST4.2 como COMPLETADO** --- **Estado:** ⏳ Pendiente aprobación de usuario **Última actualización:** 2026-01-26 **Autor:** Claude Opus 4.5