trading-platform/docs/02-definicion-modulos/OQI-005-payments-stripe/especificaciones/ET-PAY-006-security.md

515 lines
14 KiB
Markdown

# 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)**
OrbiQuant 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<boolean> {
// 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<void> {
// 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<boolean> {
// 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<void> {
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<string, any>;
}
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 <CardElement />;
};
```
```typescript
// INCORRECTO: NUNCA hacer esto
const BadPaymentForm = () => {
const [cardNumber, setCardNumber] = useState('');
const [cvv, setCvv] = useState('');
// ❌ NUNCA capturar datos de tarjeta directamente
return (
<form>
<input
type="text"
value={cardNumber}
onChange={(e) => setCardNumber(e.target.value)}
/>
<input
type="text"
value={cvv}
onChange={(e) => setCvv(e.target.value)}
/>
</form>
);
};
```
---
## 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)