Some checks failed
Build / Build Backend (push) Has been cancelled
Build / Build Mobile (TypeScript Check) (push) Has been cancelled
Lint / Lint Backend (push) Has been cancelled
Lint / Lint Mobile (push) Has been cancelled
Test / Backend E2E Tests (push) Has been cancelled
Test / Mobile Unit Tests (push) Has been cancelled
Build / Build Docker Image (push) Has been cancelled
- Add exports module with PDF/CSV/Excel generation - Add reports module for inventory analytics - Add POS integrations module - Add database migrations for exports, movements and integrations - Add GitHub Actions CI/CD workflow with Docker support - Add mobile export and reports screens with tests - Update epic documentation with traceability - Add deployment and security guides Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
403 lines
13 KiB
Markdown
403 lines
13 KiB
Markdown
# MII-012: Pagos OXXO
|
|
|
|
---
|
|
id: MII-012
|
|
type: Epic
|
|
status: Completado
|
|
priority: P0
|
|
phase: 3
|
|
story_points: 13
|
|
created_date: 2026-01-10
|
|
updated_date: 2026-01-13
|
|
simco_version: "4.0.0"
|
|
---
|
|
|
|
## Metadata
|
|
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| **ID** | MII-012 |
|
|
| **Nombre** | Pagos OXXO |
|
|
| **Fase** | 3 - Monetizacion |
|
|
| **Prioridad** | P0 |
|
|
| **Story Points** | 13 |
|
|
| **Estado** | Completado |
|
|
|
|
---
|
|
|
|
## 1. Descripcion
|
|
|
|
Implementar pagos en efectivo via OXXO usando Stripe OXXO vouchers, permitiendo a usuarios sin tarjeta comprar creditos.
|
|
|
|
### Objetivo
|
|
|
|
Habilitar pagos en efectivo para el mercado mexicano donde muchos usuarios prefieren pagar en tiendas de conveniencia.
|
|
|
|
---
|
|
|
|
## 2. Requerimientos Relacionados
|
|
|
|
| RF | Descripcion | Prioridad |
|
|
|----|-------------|-----------|
|
|
| FR-101 | Generacion de voucher OXXO, confirmacion asincrona | P0 |
|
|
| FR-103 | Confirmacion de pago via webhooks | P0 |
|
|
| FR-104 | Reconciliacion de estados | P0 |
|
|
| FR-105 | Expiracion de referencia (24-72h configurable) | P1 |
|
|
|
|
---
|
|
|
|
## 3. Criterios de Aceptacion
|
|
|
|
### AC-001: Generar Voucher
|
|
```gherkin
|
|
DADO que seleccione pago en OXXO
|
|
CUANDO confirmo el pedido
|
|
ENTONCES recibo un voucher con:
|
|
- Numero de referencia
|
|
- Monto a pagar
|
|
- Codigo de barras
|
|
- Fecha de expiracion
|
|
```
|
|
|
|
### AC-002: Ver Voucher
|
|
```gherkin
|
|
DADO que genere un voucher
|
|
CUANDO voy a "Mis pagos pendientes"
|
|
ENTONCES puedo ver el voucher
|
|
Y puedo compartirlo o guardarlo
|
|
Y veo cuanto tiempo me queda
|
|
```
|
|
|
|
### AC-003: Pago en Tienda
|
|
```gherkin
|
|
DADO que tengo un voucher valido
|
|
CUANDO pago en OXXO
|
|
ENTONCES recibo un ticket de confirmacion
|
|
Y en 10-30 minutos mis creditos se acreditan
|
|
Y recibo notificacion push
|
|
```
|
|
|
|
### AC-004: Voucher Expirado
|
|
```gherkin
|
|
DADO que mi voucher expiro
|
|
CUANDO intento pagar
|
|
ENTONCES OXXO rechaza el pago
|
|
Y en la app veo que el voucher expiro
|
|
Y puedo generar uno nuevo
|
|
```
|
|
|
|
### AC-005: Confirmacion Asincrona
|
|
```gherkin
|
|
DADO que pague en OXXO
|
|
CUANDO Stripe confirma el pago
|
|
ENTONCES el webhook actualiza mi orden
|
|
Y mis creditos se acreditan
|
|
Y recibo confirmacion
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Tareas Tecnicas
|
|
|
|
| ID | Tarea | Estimacion | Estado |
|
|
|----|-------|------------|--------|
|
|
| T-001 | Configurar Stripe OXXO en backend | 2 SP | Completado |
|
|
| T-002 | Crear generador de vouchers | 2 SP | Completado |
|
|
| T-003 | Implementar pantalla de voucher | 2 SP | Completado |
|
|
| T-004 | Crear vista de pagos pendientes | 2 SP | Completado |
|
|
| T-005 | Implementar webhook OXXO | 2 SP | Completado |
|
|
| T-006 | Crear job de expiracion | 1 SP | Completado |
|
|
| T-007 | Implementar compartir voucher | 2 SP | Completado |
|
|
|
|
---
|
|
|
|
## 5. Flujo de Pago OXXO
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ FLUJO DE PAGO OXXO │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ APP OXXO STRIPE │
|
|
│ │ │ │ │
|
|
│ │ 1. Seleccionar OXXO │ │ │
|
|
│ │─────────────────────────────────────────────────────▶ │
|
|
│ │ │ PaymentIntent │ │
|
|
│ │◀───────────────────────────────────────────────────── │
|
|
│ │ Voucher + Referencia │ │ │
|
|
│ │ │ │ │
|
|
│ │ 2. Mostrar voucher │ │ │
|
|
│ │ al usuario │ │ │
|
|
│ │ │ │ │
|
|
│ │ 3. Usuario va │ │ │
|
|
│ │ a OXXO │ │ │
|
|
│ │ ───────▶│ │ │
|
|
│ │ │ 4. Paga y recibe │ │
|
|
│ │ │ ticket │ │
|
|
│ │ │────────────────────────▶ │
|
|
│ │ │ │ │
|
|
│ │ │ 5. Confirma pago │ │
|
|
│ │◀───────────────────────────────────────────────────── │
|
|
│ │ Webhook │ │ │
|
|
│ │ │ │ │
|
|
│ │ 6. Acreditar creditos │ │ │
|
|
│ │ Notificar usuario │ │ │
|
|
│ │ │ │ │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Generacion de Voucher
|
|
|
|
```typescript
|
|
async createOxxoPayment(userId: string, packageId: string) {
|
|
const pkg = await this.packagesService.findOne(packageId);
|
|
const user = await this.usersService.findOne(userId);
|
|
|
|
// Verificar ordenes pendientes
|
|
const pending = await this.ordersService.findPending(userId, 'OXXO');
|
|
if (pending.length >= 3) {
|
|
throw new BadRequestException('Maximo 3 vouchers pendientes');
|
|
}
|
|
|
|
// Crear orden
|
|
const order = await this.ordersService.create({
|
|
userId,
|
|
packageId,
|
|
amountMxn: pkg.priceMxn,
|
|
creditsAmount: pkg.currentCredits,
|
|
paymentMethod: 'OXXO',
|
|
status: 'CREATED',
|
|
expiresAt: addHours(new Date(), 72)
|
|
});
|
|
|
|
// Crear PaymentIntent con OXXO
|
|
const paymentIntent = await this.stripe.paymentIntents.create({
|
|
amount: Math.round(pkg.priceMxn * 100),
|
|
currency: 'mxn',
|
|
payment_method_types: ['oxxo'],
|
|
payment_method_data: {
|
|
type: 'oxxo',
|
|
billing_details: {
|
|
name: user.name || 'Usuario MiInventario',
|
|
email: user.email
|
|
}
|
|
},
|
|
metadata: {
|
|
orderId: order.id,
|
|
userId,
|
|
packageId
|
|
}
|
|
});
|
|
|
|
// Confirmar para generar voucher
|
|
const confirmed = await this.stripe.paymentIntents.confirm(
|
|
paymentIntent.id
|
|
);
|
|
|
|
const oxxoDetails = confirmed.next_action?.oxxo_display_details;
|
|
|
|
await this.ordersService.update(order.id, {
|
|
stripePaymentIntentId: paymentIntent.id,
|
|
status: 'PENDING',
|
|
metadata: {
|
|
oxxoNumber: oxxoDetails.number,
|
|
oxxoExpiresAt: oxxoDetails.expires_after,
|
|
hostedVoucherUrl: oxxoDetails.hosted_voucher_url
|
|
}
|
|
});
|
|
|
|
return {
|
|
orderId: order.id,
|
|
voucher: {
|
|
number: oxxoDetails.number,
|
|
amount: pkg.priceMxn,
|
|
expiresAt: order.expiresAt,
|
|
hostedUrl: oxxoDetails.hosted_voucher_url
|
|
}
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Endpoints API
|
|
|
|
| Metodo | Endpoint | Descripcion | Auth |
|
|
|--------|----------|-------------|------|
|
|
| POST | /payments/oxxo/create | Crear voucher OXXO | JWT |
|
|
| GET | /payments/pending | Listar pagos pendientes | JWT |
|
|
| GET | /payments/oxxo/:orderId/voucher | Obtener voucher | JWT |
|
|
| POST | /payments/oxxo/:orderId/cancel | Cancelar voucher | JWT |
|
|
| POST | /payments/webhook/stripe | Webhook Stripe | Stripe Sig |
|
|
|
|
---
|
|
|
|
## 8. Pantallas Mobile
|
|
|
|
| Pantalla | Componentes |
|
|
|----------|-------------|
|
|
| **OxxoVoucherScreen** | Referencia, monto, barcode, timer |
|
|
| **PendingPaymentsScreen** | Lista vouchers pendientes |
|
|
| **VoucherShareScreen** | Opciones compartir, guardar |
|
|
| **PaymentConfirmScreen** | Exito, creditos acreditados |
|
|
|
|
---
|
|
|
|
## 9. UI de Voucher
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ PAGO EN OXXO │
|
|
├─────────────────────────────────────────┤
|
|
│ │
|
|
│ Presenta este codigo en cualquier │
|
|
│ tienda OXXO para completar tu pago │
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ │ │
|
|
│ │ ||||| |||| ||||| |||| ||||| │ │
|
|
│ │ [CODIGO DE BARRAS] │ │
|
|
│ │ │ │
|
|
│ │ 1234 5678 9012 3456 │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ REFERENCIA │
|
|
│ ───────────── │
|
|
│ 1234 5678 9012 3456 │
|
|
│ │
|
|
│ MONTO A PAGAR │
|
|
│ ───────────── │
|
|
│ $100.00 MXN │
|
|
│ │
|
|
│ EXPIRA EN │
|
|
│ ───────────── │
|
|
│ ⏱️ 71:45:32 │
|
|
│ (12 Ene 2026, 10:30 AM) │
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ 📤 COMPARTIR VOUCHER │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────┐ │
|
|
│ │ 💾 GUARDAR IMAGEN │ │
|
|
│ └─────────────────────────────────┘ │
|
|
│ │
|
|
│ ⓘ El pago se refleja en 10-30 min │
|
|
│ despues de pagar en tienda │
|
|
│ │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Webhook OXXO
|
|
|
|
```typescript
|
|
async handleOxxoPayment(paymentIntent: Stripe.PaymentIntent) {
|
|
const orderId = paymentIntent.metadata.orderId;
|
|
|
|
switch (paymentIntent.status) {
|
|
case 'succeeded':
|
|
// Pago exitoso
|
|
await this.completeOxxoPayment(orderId);
|
|
break;
|
|
|
|
case 'canceled':
|
|
// Voucher expirado o cancelado
|
|
await this.cancelOxxoPayment(orderId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
async completeOxxoPayment(orderId: string) {
|
|
const order = await this.ordersService.findOne(orderId);
|
|
|
|
await this.ordersService.update(orderId, {
|
|
status: 'PAID',
|
|
paidAt: new Date()
|
|
});
|
|
|
|
await this.creditsService.addCredits(
|
|
order.userId,
|
|
order.creditsAmount,
|
|
'PURCHASE',
|
|
orderId
|
|
);
|
|
|
|
await this.notificationsService.send(order.userId, {
|
|
title: 'Pago recibido',
|
|
body: `Tu pago en OXXO fue confirmado. +${order.creditsAmount} creditos`,
|
|
data: { screen: 'wallet' }
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Job de Expiracion
|
|
|
|
```typescript
|
|
@Cron('0 * * * *') // Cada hora
|
|
async expireOxxoVouchers() {
|
|
const expired = await this.ordersService.findExpired('OXXO');
|
|
|
|
for (const order of expired) {
|
|
await this.ordersService.update(order.id, {
|
|
status: 'EXPIRED'
|
|
});
|
|
|
|
await this.notificationsService.send(order.userId, {
|
|
title: 'Voucher expirado',
|
|
body: 'Tu voucher OXXO ha expirado. Genera uno nuevo si deseas continuar.'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Dependencias
|
|
|
|
### Entrada (Requiere)
|
|
- MII-009: Wallet y Creditos
|
|
- MII-010: Paquetes de Recarga
|
|
|
|
### Salida (Bloquea)
|
|
- Ninguna directa
|
|
|
|
---
|
|
|
|
## 13. Configuracion
|
|
|
|
| Parametro | Valor Default | Descripcion |
|
|
|-----------|---------------|-------------|
|
|
| OXXO_EXPIRATION_HOURS | 72 | Horas hasta expiracion |
|
|
| OXXO_MAX_PENDING | 3 | Max vouchers por usuario |
|
|
| OXXO_MIN_AMOUNT | 10 | Monto minimo MXN |
|
|
| OXXO_MAX_AMOUNT | 10000 | Monto maximo MXN |
|
|
|
|
---
|
|
|
|
## 14. Riesgos
|
|
|
|
| Riesgo | Probabilidad | Impacto | Mitigacion |
|
|
|--------|--------------|---------|------------|
|
|
| Usuario olvida pagar | Alta | Bajo | Recordatorios, push |
|
|
| Webhook perdido | Baja | Alto | Reconciliacion periodica |
|
|
| Voucher ilegible | Baja | Medio | PDF de alta calidad |
|
|
|
|
---
|
|
|
|
## 15. Referencias
|
|
|
|
- [REQUERIMIENTOS-FUNCIONALES.md](../00-vision-general/REQUERIMIENTOS-FUNCIONALES.md) - Seccion 5.11
|
|
- [INT-002](../02-integraciones/INT-002-oxxo.md) - Integracion OXXO
|
|
- [ADR-0004](../97-adr/ADR-0004-pagos-efectivo-mexico.md) - Pagos en efectivo
|
|
|
|
---
|
|
|
|
**Ultima Actualizacion:** 2026-01-10
|