--- id: "RF-PAY-009" title: "Sistema de Transferencias P2P" type: "Requirement" status: "Draft" priority: "Media" epic: "OQI-005" project: "trading-platform" version: "1.0.0" created_date: "2026-01-04" updated_date: "2026-01-04" --- # RF-PAY-009: Sistema de Transferencias P2P **Version:** 1.0.0 **Fecha:** 2026-01-04 **Estado:** Draft **Prioridad:** P2 (Media) **Story Points:** 5 **Epica:** [OQI-005](../_MAP.md) --- ## Descripcion El sistema debe permitir a los usuarios realizar transferencias de fondos peer-to-peer (P2P) entre wallets internos de la plataforma, de manera instantanea y sin comisiones. Los usuarios podran enviar dinero a otros usuarios identificandolos por email, username o wallet ID. --- ## Objetivo de Negocio - Fomentar comunidad activa y engagement entre usuarios - Facilitar pagos entre traders (apuestas amistosas, compartir senales) - Reducir friccion para onboarding de nuevos usuarios (amigos invitan) - Retener fondos dentro del ecosistema (no salen a bancos) - Habilitar futuros marketplaces internos (venta de cursos, senales) --- ## Casos de Uso 1. **Envio por Email:** Usuario A envia $50 USD a usuario B usando su email 2. **Envio por Username:** Usuario A envia $100 USD a @trader_pro 3. **Envio por Wallet ID:** Usuario A pega wallet ID y envia $25 USD 4. **Con Mensaje:** Usuario A envia $10 USD con nota "Para el cafe de ayer" 5. **Recepcion:** Usuario B recibe notificacion y ve fondos en wallet 6. **Historial:** Usuario revisa todas las transferencias enviadas/recibidas --- ## Requisitos Funcionales ### RF-PAY-009.1: Identificacion de Destinatario **DEBE:** 1. Buscar usuario por email exacto 2. Buscar usuario por username (con o sin @) 3. Buscar usuario por wallet ID (UUID corto de 8 caracteres) 4. Mostrar preview del destinatario antes de confirmar 5. Validar que destinatario tiene cuenta activa **Preview de destinatario:** ``` Enviar a: +---------------------------+ | @trader_pro | | Juan P****z | (nombre parcialmente oculto) | Miembro desde 2024 | | [x] Cuenta verificada | +---------------------------+ ``` ### RF-PAY-009.2: Creacion de Transferencia **DEBE:** 1. Validar saldo suficiente en wallet del remitente 2. Validar monto dentro de limites ($1 - $5,000) 3. Validar limite diario acumulado ($10,000) 4. Permitir mensaje opcional (max 140 caracteres) 5. Requerir 2FA para montos > $500 USD **Modelo de datos:** ```typescript @Entity({ name: 'p2p_transfers', schema: 'billing' }) class P2PTransfer { id: string; // UUID shortId: string; // ID corto 8 chars para referencia senderId: string; // FK a users senderWalletId: string; // FK a wallets receiverId: string; // FK a users receiverWalletId: string; // FK a wallets amount: Decimal; // Monto en USD message?: string; // Mensaje opcional (140 chars max) status: P2PTransferStatus; // pending, completed, failed, reversed requires2FA: boolean; // true si amount > 500 verified2FA: boolean; // true si 2FA completado createdAt: Date; completedAt?: Date; updatedAt: Date; } enum P2PTransferStatus { PENDING = 'pending', // Esperando 2FA COMPLETED = 'completed', // Transferido exitosamente FAILED = 'failed', // Error en proceso REVERSED = 'reversed' // Revertido por admin } ``` ### RF-PAY-009.3: Procesamiento de Transferencia **DEBE:** 1. Debitar wallet del remitente atomicamente 2. Acreditar wallet del destinatario atomicamente 3. Crear registros en `wallet_transactions` para ambos 4. Marcar transferencia como `completed` 5. Emitir eventos para notificaciones **Transaccion atomica:** ```typescript await db.transaction(async (tx) => { // 1. Lock wallets en orden consistente (evitar deadlock) const [senderWallet, receiverWallet] = await Promise.all([ tx.wallet.findOne({ id: senderWalletId }, { lock: true }), tx.wallet.findOne({ id: receiverWalletId }, { lock: true }) ]); // 2. Validar saldo if (senderWallet.balance < amount) { throw new InsufficientFundsError(); } // 3. Debitar remitente await tx.walletTransaction.create({ walletId: senderWallet.id, type: 'debit', amount: -amount, reference: transferId, description: `Transferencia P2P a ${receiverUsername}` }); await tx.wallet.decrement(senderWallet.id, 'balance', amount); // 4. Acreditar destinatario await tx.walletTransaction.create({ walletId: receiverWallet.id, type: 'credit', amount: amount, reference: transferId, description: `Transferencia P2P de ${senderUsername}` }); await tx.wallet.increment(receiverWallet.id, 'balance', amount); // 5. Actualizar estado await tx.p2pTransfer.update(transferId, { status: 'completed', completedAt: new Date() }); }); ``` ### RF-PAY-009.4: Verificacion 2FA **DEBE:** 1. Requerir 2FA para transferencias > $500 USD 2. Soportar TOTP (Google Authenticator) 3. Soportar SMS como fallback 4. Codigo valido por 5 minutos 5. Maximo 3 intentos fallidos **Flujo 2FA:** ``` 1. Usuario crea transferencia de $600 2. Backend crea transfer con status: PENDING, requires2FA: true 3. Frontend muestra modal de 2FA 4. Usuario ingresa codigo 5. Backend valida codigo 6. Si valido: procesar transferencia 7. Si invalido: incrementar intentos, bloquear si > 3 ``` ### RF-PAY-009.5: Notificaciones **DEBE:** 1. Push notification instantanea al destinatario 2. Email de confirmacion a ambas partes 3. In-app notification en historial 4. Mostrar mensaje del remitente si existe **Contenido de notificacion (destinatario):** ``` @juan_trader te envio $50.00 USD "Para el cafe de ayer" Tu nuevo saldo: $150.00 USD ``` **Contenido de notificacion (remitente):** ``` Transferencia exitosa Enviaste $50.00 USD a @trader_pro Referencia: ABC12345 Tu nuevo saldo: $200.00 USD ``` ### RF-PAY-009.6: Historial de Transferencias **DEBE:** 1. Mostrar transferencias enviadas y recibidas 2. Filtrar por tipo (enviadas/recibidas/todas) 3. Filtrar por rango de fechas 4. Buscar por destinatario/remitente 5. Mostrar mensaje asociado **Vista de historial:** ``` +--------------------------------------------+ | Transferencias P2P | +--------------------------------------------+ | [Todas] [Enviadas] [Recibidas] | +--------------------------------------------+ | 04 Ene 2026 @trader_pro | | Enviado -$50.00 USD "Para el cafe" | +--------------------------------------------+ | 03 Ene 2026 @maria_fx | | Recibido +$100.00 USD "Gracias!" | +--------------------------------------------+ | 02 Ene 2026 @carlos_btc | | Enviado -$25.00 USD | +--------------------------------------------+ ``` --- ## Flujo de Transferencia P2P ``` +-------------+ +-------------+ +-------------+ +-------------+ | Remitente | | Frontend | | Backend | | Destinatario| +------+------+ +------+------+ +------+------+ +------+------+ | | | | | Click "Enviar | | | | Dinero" | | | |------------------>| | | | | | | | Ingresa: | | | | - @trader_pro | | | | - $50 USD | | | | - "Para el cafe" | | | |------------------>| | | | | | | | | GET /users/lookup | | | | ?q=@trader_pro | | | |------------------>| | | |<------------------| | | | { username, | | | | displayName, | | | | walletId } | | | | | | |<------------------| | | | Preview: | | | | "Juan P****z" | | | | | | | | Confirma | | | |------------------>| | | | | | | | | POST /p2p/transfer| | | | { receiverId, | | | | amount: 50, | | | | message } | | | |------------------>| | | | | | | | | 1. Validate user | | | | 2. Validate | | | | balance | | | | 3. Validate | | | | limits | | | | 4. Check 2FA req | | | | ($50 < $500) | | | | NO 2FA needed | | | | | | | | BEGIN TX | | | | - Debit sender | | | | - Credit receiver | | | | - Create records | | | | COMMIT TX | | | | | | |<------------------| | | | { status: | | | | "completed", | | | | transferId } | | | | | | |<------------------| |------------------>| | "Enviado!" | | Push: "Recibiste | | | | $50 de @juan" | | | | | |<------------------| |------------------>| | Email: | | Email: | | Confirmacion | | Confirmacion | | | | | ``` --- ## Flujo con 2FA (Monto > $500) ``` +-------------+ +-------------+ +-------------+ | Remitente | | Frontend | | Backend | +------+------+ +------+------+ +------+------+ | | | | Enviar $600 a | | | @trader_pro | | |------------------>| | | | | | | POST /p2p/transfer| | | { amount: 600 } | | |------------------>| | | | | | | Detecta: amount > | | | $500, requiere 2FA | | | | | | Crear transfer: | | | status: PENDING | | | requires2FA: true | | | | |<------------------| | | { transferId, | | | requires2FA: | | | true } | | | | |<------------------| | | Modal: "Ingresa | | | codigo 2FA" | | | | | | Ingresa: 123456 | | |------------------>| | | | | | | POST /p2p/transfer| | | /{id}/verify | | | { code: 123456 } | | |------------------>| | | | | | | Validar TOTP | | | Procesar transfer | | | | |<------------------| | | { status: | | | "completed" } | | | | |<------------------| | | "Enviado!" | | | | | ``` --- ## Reglas de Negocio ### RN-001: Limites de Transferencia | Limite | Valor | |--------|-------| | Monto minimo | $1.00 USD | | Monto maximo por transferencia | $5,000.00 USD | | Limite diario acumulado | $10,000.00 USD | | Limite mensual | Sin limite | ### RN-002: Comisiones | Concepto | Fee | |----------|-----| | Transferencia P2P | **0% (gratis)** | | Sin fees ocultos | - | ### RN-003: Seguridad 2FA | Monto | Requiere 2FA | |-------|--------------| | $0 - $500 | No | | $500.01+ | Si | ### RN-004: Restricciones - No se puede enviar a si mismo - Ambas cuentas deben estar activas - No se permite enviar a cuentas suspendidas - Wallet del remitente debe tener saldo suficiente - No se pueden revertir transferencias (excepto admin) ### RN-005: Mensajes - Mensaje opcional - Maximo 140 caracteres - No se permiten URLs - Filtro de palabras prohibidas - Mensaje visible para ambas partes --- ## API Endpoints ### Buscar Usuario ```yaml GET /api/v1/users/lookup: description: Buscar usuario para transferencia P2P query: q: string (email, username o walletId) response: id: string username: string displayName: string (parcialmente oculto) walletId: string verified: boolean memberSince: string ``` ### Crear Transferencia ```yaml POST /api/v1/p2p/transfers: description: Crear transferencia P2P body: receiverId: string amount: number message?: string response: transferId: string shortId: string status: string requires2FA: boolean amount: number receiver: username: string displayName: string ``` ### Verificar 2FA ```yaml POST /api/v1/p2p/transfers/{id}/verify: description: Verificar 2FA para transferencia pendiente body: code: string (6 digitos) response: transferId: string status: "completed" completedAt: string ``` ### Historial de Transferencias ```yaml GET /api/v1/p2p/transfers: description: Listar transferencias P2P del usuario query: type: string (sent | received | all) page: number limit: number dateFrom: string dateTo: string response: data: - id: string shortId: string type: string (sent | received) amount: number otherUser: username: string displayName: string message: string status: string createdAt: string pagination: total: number page: number pages: number ``` ### Detalle de Transferencia ```yaml GET /api/v1/p2p/transfers/{id}: description: Obtener detalle de una transferencia response: id: string shortId: string type: string amount: number message: string status: string sender: username: string displayName: string receiver: username: string displayName: string createdAt: string completedAt: string ``` --- ## Seguridad ### Prevencion de Fraude - Rate limit: maximo 10 transferencias por hora - Alerta si multiples transferencias al mismo destinatario - Bloqueo temporal si patron sospechoso - Monitoreo de transferencias circulares (A→B→A) - Limite diario reduce riesgo de robo de cuenta ### Validaciones ```typescript async function validateP2PTransfer( senderId: string, receiverId: string, amount: number ): Promise { // 1. No enviar a si mismo if (senderId === receiverId) { throw new Error('No puedes enviarte dinero a ti mismo'); } // 2. Destinatario existe y activo const receiver = await userRepo.findOne({ id: receiverId }); if (!receiver || receiver.status !== 'active') { throw new Error('Usuario no encontrado o inactivo'); } // 3. Saldo suficiente const wallet = await walletRepo.findOne({ userId: senderId }); if (wallet.balance < amount) { throw new Error(`Saldo insuficiente. Disponible: $${wallet.balance}`); } // 4. Dentro de limites if (amount < 1 || amount > 5000) { throw new Error('Monto debe ser entre $1 y $5,000 USD'); } // 5. Limite diario const todayTotal = await getDailyTransferTotal(senderId); if (todayTotal + amount > 10000) { throw new Error(`Excedes limite diario. Disponible hoy: $${10000 - todayTotal}`); } return { valid: true, requires2FA: amount > 500 }; } ``` ### Auditoria - Log de todas las transferencias - Guardar IP y User-Agent del remitente - Registro de intentos fallidos de 2FA - Alertas a seguridad por patrones anomalos --- ## Manejo de Errores | Error | Codigo | Mensaje Usuario | |-------|--------|-----------------| | Usuario no encontrado | 404 | No encontramos un usuario con ese email/username | | Cuenta inactiva | 403 | Este usuario no puede recibir transferencias | | Saldo insuficiente | 400 | Saldo insuficiente. Tienes $X disponible | | Monto muy bajo | 400 | El monto minimo es $1 USD | | Monto muy alto | 400 | El monto maximo por transferencia es $5,000 USD | | Limite diario | 400 | Has alcanzado tu limite diario de $10,000 | | Auto-transferencia | 400 | No puedes enviarte dinero a ti mismo | | 2FA invalido | 401 | Codigo 2FA incorrecto. Te quedan X intentos | | 2FA expirado | 401 | Codigo expirado. Solicita uno nuevo | | Rate limit | 429 | Demasiadas transferencias. Espera 1 hora | --- ## Configuracion Requerida ```env # Limites P2P P2P_TRANSFER_MIN_USD=1 P2P_TRANSFER_MAX_USD=5000 P2P_TRANSFER_DAILY_LIMIT_USD=10000 P2P_2FA_THRESHOLD_USD=500 # Rate Limits P2P_MAX_TRANSFERS_PER_HOUR=10 P2P_MAX_TRANSFERS_PER_DAY=50 # 2FA P2P_2FA_CODE_EXPIRY_MINUTES=5 P2P_2FA_MAX_ATTEMPTS=3 # Mensajes P2P_MESSAGE_MAX_LENGTH=140 ``` --- ## Metricas de Negocio ### KPIs a Rastrear - **Transferencias P2P/dia:** Numero de transferencias completadas - **Volumen P2P/dia:** Total transferido en USD - **Usuarios activos P2P:** Usuarios unicos que envian/reciben - **Ticket promedio:** Monto promedio por transferencia - **Ratio envio/recepcion:** Balance de actividad - **Tasa de conversion 2FA:** % de transferencias >$500 completadas --- ## Criterios de Aceptacion - [ ] Usuario puede buscar destinatario por email - [ ] Usuario puede buscar destinatario por username - [ ] Usuario puede buscar destinatario por wallet ID - [ ] Preview muestra nombre parcialmente oculto - [ ] Transferencia de $50 se procesa sin 2FA - [ ] Transferencia de $600 requiere 2FA - [ ] Saldo se actualiza instantaneamente en ambos wallets - [ ] Mensaje opcional se muestra a destinatario - [ ] Push notification llega al destinatario - [ ] Email de confirmacion llega a ambas partes - [ ] Historial muestra transferencias enviadas y recibidas - [ ] Limites de monto se validan correctamente - [ ] Limite diario se calcula correctamente - [ ] No se permite enviar a si mismo - [ ] No se permite enviar a cuenta suspendida --- ## Especificacion Tecnica Relacionada - [ET-PAY-009: P2P Transfers](../especificaciones/ET-PAY-009-p2p.md) ## Historias de Usuario Relacionadas - [US-PAY-030: Enviar Dinero P2P](../historias-usuario/US-PAY-030-enviar-p2p.md) - [US-PAY-031: Recibir Dinero P2P](../historias-usuario/US-PAY-031-recibir-p2p.md) - [US-PAY-032: Ver Historial P2P](../historias-usuario/US-PAY-032-historial-p2p.md)