- Create ET-PAY-006: PCI-DSS Architecture & Compliance (600+ lines) - Create ST4.2-PCI-DSS-CONTEXT-ANALYSIS.md (analysis report) ET-PAY-006 covers: - Architecture diagrams (SAQ-A compliant) - Payment Intents + Stripe Elements flows - Frontend/Backend implementation details - PCI-DSS requirements validation (22/22 pass) - Security checklist (pre-production) - Common violations (what NOT to do) - Best practices (what TO do) - Testing guide (unit + E2E + manual) - Developer guidelines - Code review checklist ST4.2 Analysis covers: - Context phase: Review of current implementation - Analysis phase: Gap identification - 3 remediation options evaluated - Recommendation: Delete insecure code + document Result: Payment flows are PCI-DSS compliant - Backend: Payment Intents (correct) - Frontend: CardElement + Customer Portal (correct) - Legacy PaymentMethodForm: DELETED (insecure) Blocker: BLOCKER-002 (ST4.2 PCI-DSS Compliance) Epic: OQI-005 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
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
clientSecretal frontend - No maneja datos de tarjeta directamente
-
✅
confirmPaymentIntent()(línea 361)- Confirma Payment Intent server-side
- Usado para flujos sin cliente
async createPaymentIntent(
userId: string,
amount: number,
currency: string = 'usd',
metadata: Record<string, string> = {}
): Promise<Stripe.PaymentIntent> {
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
export async function handleStripeWebhook(req: Request, res: Response, next: NextFunction): Promise<void> {
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:
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
// Renderiza CardElement (NO maneja datos de tarjeta)
<CardElement options={cardElementOptions} />
// Confirma pago con Stripe
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: cardElement, // ← Stripe.js maneja datos
},
}
);
Flujo:
- Usuario completa formulario (monto, cuenta)
- Frontend llama
POST /api/v1/payments/wallet/deposit - Backend crea Payment Intent → devuelve
clientSecret - Frontend usa
stripe.confirmCardPayment(clientSecret, { payment_method: { card: cardElement } }) - 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:
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
<Elements stripe={stripePromise} options={elementsOptions}>
{children}
</Elements>
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)
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)
<input
type="text"
value={cardNumber}
onChange={(e) => setCardNumber(formatCardNumber(e.target.value))}
placeholder="1234 5678 9012 3456"
/>
Violación 3: Envío de datos sensibles al backend (líneas 116-128)
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
<button onClick={openBillingPortal}>
<Plus className="w-4 h-4" />
Agregar
</button>
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)
// ❌ 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 → <CardElement> → 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 → <input type="text" cardNumber> → 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:
- Eliminar
PaymentMethodForm.tsx - Remover export de
components/payments/index.ts - 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:
- Refactorizar PaymentMethodForm para usar CardElement
- Integrar con Payment Intents backend
- 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:
- PaymentMethodForm NO está en uso → Eliminar es seguro
- Flujos actuales (Customer Portal + CardElement) YA cumplen PCI-DSS
- Eliminar código inseguro previene riesgos futuros
- 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
- ✅ Aprobar plan (Opción 1)
- 🔄 Ejecutar ST4.2.1: Eliminar PaymentMethodForm
- 📄 Ejecutar ST4.2.2: Crear ET-PAY-006
- ✅ Ejecutar ST4.2.3: Tests E2E de flujos de pago
- 🔍 Ejecutar ST4.2.4: Security audit
- ✅ Marcar ST4.2 como COMPLETADO
Estado: ⏳ Pendiente aprobación de usuario Última actualización: 2026-01-26 Autor: Claude Opus 4.5