--- id: "ET-PAY-006" title: "Seguridad PCI DSS" type: "Technical Specification" status: "Done" priority: "Alta" epic: "OQI-005" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # ET-PAY-006: Seguridad PCI DSS **Epic:** OQI-005 Pagos y Stripe **Versión:** 1.0 **Fecha:** 2025-12-05 --- ## 1. Descripción Implementación de medidas de seguridad para cumplimiento PCI DSS: - Tokenización de tarjetas con Stripe - No almacenamiento de datos sensibles - Validaciones de seguridad - Logs de auditoría - Encriptación de datos - Prevención de fraude --- ## 2. Arquitectura de Seguridad ``` ┌─────────────────────────────────────────────────────────────────┐ │ Payment Security Stack │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Frontend Backend Stripe │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Stripe.js │───►│ Tokenization │───►│ Vault │ │ │ │ (PCI SAQ-A) │ │ Only Tokens │ │ (Card Data) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ Fraud │ │ │ │ Detection │ │ │ └──────────────┘ │ │ │ │ ┌──────────────┐ │ │ │ Audit Logs │ │ │ └──────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 3. PCI DSS Requirements ### 3.1 Nivel de Cumplimiento **SAQ-A (Self-Assessment Questionnaire A)** Trading Platform califica para SAQ-A porque: - No almacena, procesa ni transmite datos de tarjetas - Usa Stripe.js y Elements (redirección completa a Stripe) - Solo maneja tokens de Stripe, no datos de tarjetas ### 3.2 Requisitos SAQ-A 1. Usar solo proveedores PCI DSS compliant (Stripe) 2. No almacenar datos sensibles (CVV, track data, PIN) 3. Mantener política de seguridad 4. Usar conexiones seguras (HTTPS) 5. No usar contraseñas por defecto --- ## 4. Implementación de Seguridad ### 4.1 Tokenization Service ```typescript // src/services/security/tokenization.service.ts import { StripeService } from '../stripe/stripe.service'; import { AppError } from '../../utils/errors'; export class TokenizationService { private stripeService: StripeService; constructor() { this.stripeService = new StripeService(); } /** * NUNCA acepta datos de tarjeta directamente * Solo acepta tokens de Stripe */ async validatePaymentToken(token: string): Promise { // Verificar que es un token válido de Stripe if (!token.startsWith('pm_') && !token.startsWith('tok_')) { throw new AppError('Invalid payment token format', 400); } return true; } /** * Guardar payment method (solo token) */ async savePaymentMethod(params: { user_id: string; payment_method_id: string; // Token de Stripe, NO datos de tarjeta customer_id: string; }): Promise { // Validar token await this.validatePaymentToken(params.payment_method_id); // Adjuntar a customer en Stripe await this.stripeService.attachPaymentMethod( params.payment_method_id, params.customer_id ); // Guardar solo metadata en DB (NO datos de tarjeta) // Ver ET-PAY-001 para estructura de tabla payment_methods } } ``` ### 4.2 Data Validation ```typescript // src/middlewares/payment-validation.middleware.ts import { Request, Response, NextFunction } from 'express'; import { AppError } from '../utils/errors'; /** * Valida que NO se envíen datos sensibles de tarjetas */ export const preventCardDataSubmission = ( req: Request, res: Response, next: NextFunction ): void => { const sensitiveFields = [ 'card_number', 'cvv', 'cvc', 'card_cvv', 'expiry', 'exp_month', 'exp_year', ]; const body = JSON.stringify(req.body).toLowerCase(); for (const field of sensitiveFields) { if (body.includes(field)) { throw new AppError( 'Card data not accepted. Use Stripe tokenization.', 400 ); } } next(); }; ``` ### 4.3 Fraud Detection ```typescript // src/services/security/fraud-detection.service.ts import { PaymentRepository } from '../../modules/payments/payment.repository'; import { logger } from '../../utils/logger'; export class FraudDetectionService { private paymentRepo: PaymentRepository; constructor() { this.paymentRepo = new PaymentRepository(); } /** * Detecta actividad sospechosa de pagos */ async detectFraud(params: { user_id: string; amount: number; ip_address?: string; }): Promise<{ is_suspicious: boolean; reasons: string[] }> { const reasons: string[] = []; // 1. Verificar múltiples pagos fallidos const failedPayments = await this.paymentRepo.getRecentFailedPayments( params.user_id, 3600 // última hora ); if (failedPayments.length >= 3) { reasons.push('Multiple failed payment attempts'); } // 2. Verificar monto inusualmente alto const avgPayment = await this.paymentRepo.getAveragePaymentAmount(params.user_id); if (avgPayment > 0 && params.amount > avgPayment * 10) { reasons.push('Unusually high payment amount'); } // 3. Velocity check - múltiples pagos en corto tiempo const recentPayments = await this.paymentRepo.getRecentPayments( params.user_id, 1800 // últimos 30 min ); if (recentPayments.length >= 5) { reasons.push('Too many payments in short time'); } // 4. Verificar cambios frecuentes de payment method const recentMethods = await this.paymentRepo.getRecentPaymentMethodChanges( params.user_id, 86400 // último día ); if (recentMethods.length >= 3) { reasons.push('Frequent payment method changes'); } const is_suspicious = reasons.length > 0; if (is_suspicious) { logger.warn('Suspicious payment activity detected', { user_id: params.user_id, reasons, amount: params.amount, }); } return { is_suspicious, reasons }; } /** * Verifica si usuario está en lista de bloqueo */ async isBlocked(userId: string): Promise { // Implementar lógica de lista negra // Puede usar Redis o tabla en DB return false; } /** * Bloquea usuario temporalmente */ async blockUser(userId: string, reason: string, durationSeconds: number): Promise { logger.warn('User blocked from payments', { user_id: userId, reason, duration: durationSeconds, }); // Guardar en Redis con TTL // await redis.setex(`blocked:${userId}`, durationSeconds, reason); } } ``` ### 4.4 Audit Logger ```typescript // src/services/security/payment-audit.service.ts import { logger } from '../../utils/logger'; export enum PaymentAuditAction { PAYMENT_INITIATED = 'PAYMENT_INITIATED', PAYMENT_COMPLETED = 'PAYMENT_COMPLETED', PAYMENT_FAILED = 'PAYMENT_FAILED', REFUND_REQUESTED = 'REFUND_REQUESTED', REFUND_COMPLETED = 'REFUND_COMPLETED', SUBSCRIPTION_CREATED = 'SUBSCRIPTION_CREATED', SUBSCRIPTION_CANCELED = 'SUBSCRIPTION_CANCELED', PAYMENT_METHOD_ADDED = 'PAYMENT_METHOD_ADDED', PAYMENT_METHOD_REMOVED = 'PAYMENT_METHOD_REMOVED', FRAUD_DETECTED = 'FRAUD_DETECTED', } interface PaymentAuditEntry { action: PaymentAuditAction; user_id: string; amount?: number; payment_id?: string; ip_address?: string; user_agent?: string; metadata?: Record; } export class PaymentAuditService { log(entry: PaymentAuditEntry): void { logger.info('PAYMENT_AUDIT', { timestamp: new Date().toISOString(), action: entry.action, user_id: entry.user_id, amount: entry.amount, payment_id: entry.payment_id, ip_address: entry.ip_address, user_agent: entry.user_agent, metadata: entry.metadata, }); // Opcionalmente guardar en tabla de auditoría } } ``` --- ## 5. Frontend Security ### 5.1 Stripe.js Integration ```typescript // CORRECTO: Usar Stripe Elements import { CardElement } from '@stripe/react-stripe-js'; const PaymentForm = () => { const stripe = useStripe(); const elements = useElements(); const handleSubmit = async () => { const cardElement = elements.getElement(CardElement); // Crear token con Stripe.js (datos nunca pasan por nuestro servidor) const { token, error } = await stripe.createToken(cardElement); if (token) { // Enviar solo token al backend await api.post('/payments', { token: token.id }); } }; return ; }; ``` ```typescript // INCORRECTO: NUNCA hacer esto const BadPaymentForm = () => { const [cardNumber, setCardNumber] = useState(''); const [cvv, setCvv] = useState(''); // ❌ NUNCA capturar datos de tarjeta directamente return (
setCardNumber(e.target.value)} /> setCvv(e.target.value)} />
); }; ``` --- ## 6. Security Checklist ### 6.1 PCI DSS Compliance Checklist - [ ] Usar solo Stripe.js/Elements para captura de tarjetas - [ ] NUNCA almacenar CVV/CVC - [ ] NUNCA almacenar datos completos de tarjeta - [ ] Solo guardar tokens de Stripe - [ ] Usar HTTPS en todos los endpoints - [ ] Validar firma de webhooks de Stripe - [ ] Implementar rate limiting - [ ] Logs de auditoría para todas las transacciones - [ ] Detección de fraude básica - [ ] Encriptación de datos en tránsito (TLS 1.2+) - [ ] Acceso restringido a datos de pagos (RBAC) - [ ] Monitoreo de actividad sospechosa - [ ] Política de contraseñas fuertes - [ ] Autenticación de dos factores para admin ### 6.2 Development Checklist - [ ] Variables de entorno seguras - [ ] Secrets no en código fuente - [ ] Test mode keys para desarrollo - [ ] Production keys solo en producción - [ ] Webhook signatures verificadas - [ ] Error messages sin información sensible - [ ] Input validation en todos los endpoints - [ ] XSS protection - [ ] CSRF protection - [ ] SQL injection prevention --- ## 7. Incident Response ### 7.1 Procedimiento de Incidente 1. **Detección** - Monitoreo de logs - Alertas automáticas - Reportes de usuarios 2. **Contención** - Bloquear usuario afectado - Pausar procesos automáticos - Aislar sistemas comprometidos 3. **Investigación** - Analizar logs de auditoría - Identificar alcance - Documentar hallazgos 4. **Recuperación** - Revertir cambios no autorizados - Restaurar desde backup si necesario - Verificar integridad de datos 5. **Post-Mortem** - Documentar incidente - Implementar mejoras - Actualizar procedimientos --- ## 8. Monitoring y Alertas ### 8.1 Métricas Clave ```typescript // Alertas automáticas const ALERT_THRESHOLDS = { FAILED_PAYMENTS_PER_HOUR: 10, HIGH_VALUE_TRANSACTION: 10000, REFUND_RATE_PERCENTAGE: 5, WEBHOOK_FAILURE_RATE: 0.1, }; // Monitorear - Tasa de pagos fallidos - Volumen de reembolsos - Tiempo de respuesta de Stripe - Errores de webhook - Intentos de fraude detectados ``` --- ## 9. Configuración ```bash # Security STRIPE_SECRET_KEY=sk_live_... # Nunca sk_test_ en producción STRIPE_WEBHOOK_SECRET=whsec_... # Encryption ENCRYPTION_KEY=32-character-secure-key # Rate Limiting PAYMENT_RATE_LIMIT_PER_HOUR=10 REFUND_RATE_LIMIT_PER_DAY=3 # Fraud Detection FRAUD_DETECTION_ENABLED=true MAX_PAYMENT_AMOUNT=10000 ``` --- ## 10. Testing ```typescript // tests/security/pci-compliance.test.ts describe('PCI Compliance', () => { it('should reject card data in request body', async () => { const response = await request(app) .post('/api/v1/payments') .send({ card_number: '4242424242424242', cvv: '123', }); expect(response.status).toBe(400); expect(response.body.error).toContain('tokenization'); }); it('should only accept Stripe tokens', async () => { const response = await request(app) .post('/api/v1/payments') .send({ payment_method_id: 'pm_1234567890', amount: 100, }); expect(response.status).not.toBe(400); }); }); ``` --- ## 11. Referencias - [PCI DSS SAQ-A](https://www.pcisecuritystandards.org/document_library) - [Stripe Security](https://stripe.com/docs/security/stripe) - [OWASP Payment Security](https://owasp.org/www-project-payment-security/) - [PCI Compliance Guide](https://stripe.com/guides/pci-compliance)