# MII-011: Pagos con Tarjeta --- id: MII-011 type: Epic status: Completado priority: P0 phase: 3 story_points: 8 created_date: 2026-01-10 updated_date: 2026-01-13 simco_version: "4.0.0" --- ## Metadata | Campo | Valor | |-------|-------| | **ID** | MII-011 | | **Nombre** | Pagos con Tarjeta | | **Fase** | 3 - Monetizacion | | **Prioridad** | P0 | | **Story Points** | 8 | | **Estado** | Completado | --- ## 1. Descripcion Implementar pagos con tarjeta de credito/debito usando Stripe, con PCI compliance delegado y experiencia de pago fluida. ### Objetivo Permitir a los usuarios comprar creditos instantaneamente con tarjeta de manera segura. --- ## 2. Requerimientos Relacionados | RF | Descripcion | Prioridad | |----|-------------|-----------| | FR-100 | Pago inmediato con tarjeta, PCI delegado (Stripe) | P0 | | FR-103 | Confirmacion de pago via webhooks | P0 | | FR-104 | Reconciliacion (CREATED/PENDING/PAID/EXPIRED/CANCELED) | P0 | --- ## 3. Criterios de Aceptacion ### AC-001: Pago con Tarjeta ```gherkin DADO que seleccione un paquete CUANDO ingreso mis datos de tarjeta ENTONCES el pago se procesa inmediatamente Y recibo confirmacion de exito o error Y mis creditos se acreditan al instante ``` ### AC-002: Formulario Seguro ```gherkin DADO que estoy en el checkout CUANDO ingreso datos de tarjeta ENTONCES uso el componente Stripe Elements Y los datos nunca tocan mi servidor Y veo indicadores de seguridad ``` ### AC-003: Tarjeta Guardada ```gherkin DADO que pague con una tarjeta CUANDO hago otra compra ENTONCES puedo usar la tarjeta guardada Y solo ingreso CVV O puedo agregar otra tarjeta ``` ### AC-004: Error de Pago ```gherkin DADO que mi pago falla CUANDO veo el error ENTONCES el mensaje es claro (fondos, tarjeta invalida, etc) Y puedo intentar de nuevo Y no se cobran creditos ``` ### AC-005: Webhook de Confirmacion ```gherkin DADO que Stripe confirma el pago CUANDO recibo el webhook ENTONCES actualizo el estado de la orden Y acredito los creditos Y envio confirmacion al usuario ``` --- ## 4. Tareas Tecnicas | ID | Tarea | Estimacion | Estado | |----|-------|------------|--------| | T-001 | Configurar Stripe SDK backend | 1 SP | Completado | | T-002 | Crear endpoints de pago | 2 SP | Completado | | T-003 | Implementar webhook handler | 2 SP | Completado | | T-004 | Integrar Stripe Elements en mobile | 2 SP | Completado | | T-005 | Implementar guardado de tarjetas | 1 SP | Completado | --- ## 5. Modelo de Datos ### Tabla: payment_orders ```sql CREATE TABLE payment_orders ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id), package_id UUID REFERENCES packages(id), amount_mxn DECIMAL(10,2) NOT NULL, credits_amount DECIMAL(12,4) NOT NULL, payment_method VARCHAR(20), -- 'CARD', 'OXXO', 'SEVEN_ELEVEN' status VARCHAR(20) DEFAULT 'CREATED', stripe_payment_intent_id VARCHAR(100), stripe_customer_id VARCHAR(100), paid_at TIMESTAMP, expires_at TIMESTAMP, metadata JSONB, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Status: CREATED, PENDING, PAID, FAILED, EXPIRED, CANCELED, REFUNDED ``` ### Tabla: payment_methods ```sql CREATE TABLE payment_methods ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id), type VARCHAR(20), -- 'CARD' stripe_payment_method_id VARCHAR(100), last_four VARCHAR(4), brand VARCHAR(20), exp_month INT, exp_year INT, is_default BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW() ); ``` --- ## 6. Flujo de Pago ``` ┌─────────────────────────────────────────────────────────────────┐ │ FLUJO DE PAGO CON TARJETA │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Seleccionar │→│ Crear │→│ Pagar │→│ Confirmar│ │ │ │ Paquete │ │ Orden │ │ (Stripe) │ │(Webhook) │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ │ │ Mostrar CREATED PENDING PAID │ │ precios en DB PaymentIntent Acreditar │ │ creditos │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 7. Endpoints API | Metodo | Endpoint | Descripcion | Auth | |--------|----------|-------------|------| | POST | /payments/card/intent | Crear PaymentIntent | JWT | | POST | /payments/card/confirm | Confirmar pago | JWT | | GET | /payments/methods | Listar tarjetas guardadas | JWT | | POST | /payments/methods | Agregar tarjeta | JWT | | DELETE | /payments/methods/:id | Eliminar tarjeta | JWT | | POST | /payments/webhook/stripe | Webhook Stripe | Stripe Sig | --- ## 8. Integracion Stripe ### Backend - Crear PaymentIntent ```typescript async createCardPayment(userId: string, packageId: string) { const pkg = await this.packagesService.findOne(packageId); const user = await this.usersService.findOne(userId); // Crear o obtener Stripe Customer let customerId = user.stripeCustomerId; if (!customerId) { const customer = await this.stripe.customers.create({ email: user.email, phone: user.phone, metadata: { userId } }); customerId = customer.id; await this.usersService.update(userId, { stripeCustomerId: customerId }); } // Crear orden const order = await this.ordersService.create({ userId, packageId, amountMxn: pkg.priceMxn, creditsAmount: pkg.currentCredits, paymentMethod: 'CARD', status: 'CREATED' }); // Crear PaymentIntent const paymentIntent = await this.stripe.paymentIntents.create({ amount: Math.round(pkg.priceMxn * 100), // centavos currency: 'mxn', customer: customerId, metadata: { orderId: order.id, userId, packageId } }); await this.ordersService.update(order.id, { stripePaymentIntentId: paymentIntent.id, stripeCustomerId: customerId, status: 'PENDING' }); return { orderId: order.id, clientSecret: paymentIntent.client_secret }; } ``` ### Webhook Handler ```typescript @Post('webhook/stripe') async handleStripeWebhook(@Req() req: RawBodyRequest) { const sig = req.headers['stripe-signature']; const event = this.stripe.webhooks.constructEvent( req.rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET ); switch (event.type) { case 'payment_intent.succeeded': await this.handlePaymentSuccess(event.data.object); break; case 'payment_intent.payment_failed': await this.handlePaymentFailed(event.data.object); break; } return { received: true }; } async handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) { const orderId = paymentIntent.metadata.orderId; const order = await this.ordersService.findOne(orderId); // Actualizar orden await this.ordersService.update(orderId, { status: 'PAID', paidAt: new Date() }); // Acreditar creditos await this.creditsService.addCredits( order.userId, order.creditsAmount, 'PURCHASE', orderId ); // Notificar usuario await this.notificationsService.send(order.userId, { title: 'Pago exitoso', body: `Se acreditaron ${order.creditsAmount} creditos a tu cuenta` }); } ``` --- ## 9. Pantallas Mobile | Pantalla | Componentes | |----------|-------------| | **CheckoutScreen** | Resumen, Stripe Elements, confirmar | | **SavedCardsScreen** | Lista tarjetas, agregar nueva | | **PaymentSuccessScreen** | Confirmacion, creditos, continuar | | **PaymentErrorScreen** | Error, reintentar, soporte | --- ## 10. Dependencias ### Entrada (Requiere) - MII-009: Wallet y Creditos - MII-010: Paquetes de Recarga ### Salida (Bloquea) - Ninguna directa --- ## 11. Seguridad | Aspecto | Implementacion | |---------|----------------| | PCI DSS | Delegado a Stripe | | Datos tarjeta | Nunca tocan nuestro servidor | | Webhooks | Verificacion de firma Stripe | | Idempotencia | Payment Intent ID unico | --- ## 12. Riesgos | Riesgo | Probabilidad | Impacto | Mitigacion | |--------|--------------|---------|------------| | Fraude | Media | Alto | Stripe Radar, 3D Secure | | Webhook perdido | Baja | Alto | Retry, reconciliacion | | Doble cobro | Baja | Alto | Idempotencia, locks | --- ## 13. Referencias - [REQUERIMIENTOS-FUNCIONALES.md](../00-vision-general/REQUERIMIENTOS-FUNCIONALES.md) - Seccion 5.11 - [INT-001](../02-integraciones/INT-001-stripe.md) - Integracion Stripe - [ADR-0004](../97-adr/ADR-0004-pagos-efectivo-mexico.md) - Estrategia de pagos --- **Ultima Actualizacion:** 2026-01-10