--- id: "RF-PAY-007" title: "Sistema de Depositos y Retiros en Criptomonedas" type: "Requirement" status: "Draft" priority: "Alta" epic: "OQI-005" project: "trading-platform" version: "1.0.0" created_date: "2026-01-04" updated_date: "2026-01-04" --- # RF-PAY-007: Sistema de Depositos y Retiros en Criptomonedas **Version:** 1.0.0 **Fecha:** 2026-01-04 **Estado:** Draft **Prioridad:** P1 (Alta) **Story Points:** 13 **Epica:** [OQI-005](../_MAP.md) --- ## Descripcion El sistema debe permitir a los usuarios realizar depositos y retiros utilizando criptomonedas, integrando con proveedores de pago crypto como Coinbase Commerce o BitPay, soportando multiples monedas y redes blockchain, con conversion automatica a USD para mantener el balance del wallet interno. --- ## Objetivo de Negocio - Expandir opciones de pago para usuarios globales sin acceso bancario tradicional - Atraer segmento crypto-nativo con preferencia por pagos descentralizados - Reducir costos de transaccion en mercados internacionales - Ofrecer alternativa a usuarios con restricciones bancarias - Habilitar depositos anonimos (hasta limite KYC) --- ## Casos de Uso 1. **Deposito BTC:** Usuario envia 0.01 BTC a direccion unica y recibe $450 USD en wallet 2. **Deposito USDT:** Usuario envia 100 USDT (TRC-20) y recibe $100 USD en wallet 3. **Retiro ETH:** Usuario solicita retiro de $200 USD y recibe 0.08 ETH en su wallet externo 4. **Verificacion KYC:** Usuario completa KYC para habilitar retiros crypto 5. **Monitoreo de Confirmaciones:** Sistema muestra progreso de confirmaciones on-chain --- ## Monedas y Redes Soportadas | Moneda | Simbolo | Redes Soportadas | Confirmaciones Requeridas | |--------|---------|------------------|---------------------------| | Bitcoin | BTC | Bitcoin Network | 3 confirmaciones | | Ethereum | ETH | Ethereum Mainnet | 12 confirmaciones | | Tether | USDT | ERC-20, TRC-20 | 12 (ERC-20), 20 (TRC-20) | | USD Coin | USDC | ERC-20, Polygon | 12 (ERC-20), 30 (Polygon) | --- ## Requisitos Funcionales ### RF-PAY-007.1: Generacion de Direcciones Unicas **DEBE:** 1. Generar direccion unica de deposito por usuario por moneda/red 2. Asociar direccion a `userId` en tabla `crypto_addresses` 3. Mostrar QR code y direccion copiable en UI 4. Detectar red correcta segun direccion (ERC-20 vs TRC-20) 5. Reutilizar direccion para depositos futuros del mismo usuario **Modelo de datos:** ```typescript @Entity({ name: 'crypto_addresses', schema: 'billing' }) class CryptoAddress { id: string; // UUID userId: string; // FK a users currency: string; // BTC, ETH, USDT network: string; // bitcoin, ethereum, tron, polygon address: string; // Direccion de deposito unica derivationPath?: string; // Para HD wallets isActive: boolean; // true createdAt: Date; updatedAt: Date; } ``` ### RF-PAY-007.2: Deteccion y Procesamiento de Depositos **DEBE:** 1. Monitorear direcciones via webhooks del proveedor (Coinbase/BitPay) 2. Detectar transacciones entrantes en tiempo real 3. Mostrar estado "Pendiente" con contador de confirmaciones 4. Convertir a USD al alcanzar confirmaciones requeridas 5. Acreditar al wallet interno del usuario **Flujo de Confirmaciones:** ``` 0 confirmaciones → Estado: DETECTED (detectado) 1 confirmacion → Estado: PENDING (pendiente) 3+ confirmaciones → Estado: CONFIRMED (BTC) 12+ confirmaciones → Estado: CONFIRMED (ETH/ERC-20) 20+ confirmaciones → Estado: CONFIRMED (TRC-20) ``` ### RF-PAY-007.3: Conversion Automatica a USD **DEBE:** 1. Obtener tasa de cambio en tiempo real al momento de confirmacion 2. Aplicar spread de 0.5% sobre tasa de mercado 3. Convertir monto crypto a USD equivalente 4. Acreditar USD al wallet interno 5. Registrar tasa de cambio usada en transaccion **Calculo:** ``` USD_Amount = Crypto_Amount * Market_Rate * (1 - 0.005) Ejemplo: 0.01 BTC * $45,000/BTC * 0.995 = $447.75 USD ``` ### RF-PAY-007.4: Sistema de Retiros Crypto **DEBE:** 1. Permitir retiros a wallet externo del usuario 2. Solicitar direccion de destino y red 3. Validar formato de direccion segun red 4. Calcular monto crypto segun tasa actual 5. Procesar retiro via API del proveedor 6. Enviar notificacion con TX hash **Validaciones:** - KYC aprobado obligatorio para retiros - Monto minimo: $50 USD equivalente - Monto maximo: $10,000 USD por transaccion - Limite diario: $50,000 USD - Cooldown: 24h para nuevas direcciones ### RF-PAY-007.5: Verificacion KYC para Retiros **DEBE:** 1. Bloquear retiros crypto hasta completar KYC 2. Solicitar documentos: ID gubernamental + selfie + comprobante domicilio 3. Verificar via proveedor KYC (Jumio, Onfido) 4. Aprobar/rechazar en maximo 24 horas 5. Permitir retiros una vez aprobado **Niveles KYC:** | Nivel | Requisitos | Limite Retiro | |-------|------------|---------------| | Ninguno | Solo email | $0 (sin retiros) | | Basico | ID + Selfie | $1,000/dia | | Verificado | ID + Selfie + Domicilio | $50,000/dia | ### RF-PAY-007.6: Webhooks de Confirmaciones **DEBE:** 1. Recibir webhooks de Coinbase Commerce/BitPay 2. Validar firma HMAC del webhook 3. Actualizar estado de transaccion en BD 4. Emitir evento interno para procesamiento 5. Responder 200 OK inmediatamente (procesamiento async) **Eventos a manejar:** - `charge:created` - Transaccion detectada - `charge:pending` - Esperando confirmaciones - `charge:confirmed` - Confirmaciones completas - `charge:failed` - Transaccion fallida/expirada --- ## Modelo de Datos ### Tabla: crypto_transactions ```typescript @Entity({ name: 'crypto_transactions', schema: 'billing' }) class CryptoTransaction { id: string; // UUID userId: string; // FK a users walletId: string; // FK a wallets type: 'deposit' | 'withdrawal'; status: CryptoTxStatus; // detected, pending, confirmed, failed, expired currency: string; // BTC, ETH, USDT network: string; // bitcoin, ethereum, tron, polygon cryptoAmount: Decimal; // Monto en crypto usdAmount: Decimal; // Monto en USD (convertido) exchangeRate: Decimal; // Tasa de cambio usada fromAddress?: string; // Direccion origen (depositos) toAddress: string; // Direccion destino txHash?: string; // Hash de transaccion on-chain confirmations: number; // Confirmaciones actuales requiredConfirmations: number; // Confirmaciones requeridas providerRef: string; // ID en Coinbase/BitPay fee?: Decimal; // Fee de red metadata?: object; createdAt: Date; confirmedAt?: Date; updatedAt: Date; } enum CryptoTxStatus { DETECTED = 'detected', PENDING = 'pending', CONFIRMED = 'confirmed', FAILED = 'failed', EXPIRED = 'expired' } ``` --- ## Flujo de Deposito Crypto ``` +-------------+ +-------------+ +-------------+ +-------------+ +-------------+ | Usuario | | Frontend | | Backend | | Coinbase | | Blockchain | +------+------+ +------+------+ +------+------+ +------+------+ +------+------+ | | | | | | Selecciona | | | | | "Depositar BTC" | | | | |------------------>| | | | | | | | | | | GET /crypto/ | | | | | address?currency= | | | | | BTC&network=bitcoin| | | | |------------------>| | | | | | | | | | | Get/Create | | | | | unique address | | | | |------------------>| | | | |<------------------| | | | | | | | |<------------------| | | | | { address, qrCode } | | | | | | | |<------------------| | | | | Muestra QR + | | | | | direccion | | | | | | | | | | Usuario envia | | | | | 0.01 BTC desde | | | | | su wallet externo | | | | |---------------------------------------------------------->|----------------->| | | | | | | | | | TX broadcast | | | | |<-----------------| | | | | | | | |<------------------| | | | | Webhook: | | | | | charge:created | | | | | | | | | | Create | | | | | crypto_transaction| | | | | status: DETECTED | | | | | | | |<------------------|-------------------| | | | Push: "Deposito | | | | | detectado, 0/3 | | | | | confirmaciones" | | | | | | | | | | | |<------------------| | | | | Webhook: | | | | | charge:confirmed | | | | | (3 confirmations) | | | | | | | | | | BEGIN TX | | | | | 1. Get exchange | | | | | rate | | | | | 2. Convert to USD | | | | | 3. Credit wallet | | | | | 4. Update status | | | | | COMMIT TX | | | | | | | |<------------------|-------------------| | | | Push: "Deposito | | | | | confirmado! | | | | | +$447.75 USD" | | | | | | | | | ``` --- ## Flujo de Retiro Crypto ``` +-------------+ +-------------+ +-------------+ +-------------+ | Usuario | | Frontend | | Backend | | Coinbase | +------+------+ +------+------+ +------+------+ +------+------+ | | | | | Click "Retirar | | | | a Crypto" | | | |------------------>| | | | | | | | | GET /user/kyc | | | |------------------>| | | |<------------------| | | | { status: | | | | "verified" } | | | | | | | Ingresa: | | | | - $200 USD | | | | - BTC | | | | - bc1q...xyz | | | |------------------>| | | | | | | | | POST /crypto/ | | | | withdraw | | | | { amount: 200, | | | | currency: "BTC",| | | | address: "bc1q..| | | | network: "bitcoin"} | | |------------------>| | | | | | | | | 1. Validate KYC | | | | 2. Validate addr | | | | 3. Check limits | | | | 4. Get rate | | | | 5. Calculate BTC | | | | ($200 = 0.0044)| | | | | | | | 6. Debit wallet | | | | 7. Create pending | | | | withdrawal | | | | | | | | 8. Request payout | | | |------------------>| | | |<------------------| | | | { payoutId } | | | | | | |<------------------| | | | { status: "pending", | | | cryptoAmount: 0.0044, | | | estimatedTime: "30min" } | | | | | |<------------------| | | | "Retiro procesando| | | | 0.0044 BTC a | | | | bc1q...xyz" | | | | | | | | | |<------------------| | | | Webhook: | | | | payout:completed | | | | txHash: 0xabc... | | | | | |<------------------|-------------------| | | Email: "Retiro | | | | completado! | | | | TX: 0xabc..." | | | | | | | ``` --- ## Reglas de Negocio ### RN-001: Limites de Deposito | Limite | Valor | |--------|-------| | Deposito minimo | $10 USD equivalente | | Deposito maximo por transaccion | $50,000 USD equivalente | | Deposito maximo diario | $100,000 USD equivalente | | Sin limite mensual | - | ### RN-002: Limites de Retiro | Limite | Sin KYC | KYC Basico | KYC Verificado | |--------|---------|------------|----------------| | Por transaccion | $0 | $1,000 | $10,000 | | Diario | $0 | $1,000 | $50,000 | | Mensual | $0 | $5,000 | $200,000 | ### RN-003: Fees y Spreads | Concepto | Valor | |----------|-------| | Deposito | GRATIS (usuario paga fee de red) | | Spread conversion | 0.5% | | Retiro fee fijo | $5 USD | | Retiro fee red | Variable (estimado mostrado) | ### RN-004: Expiracion de Transacciones - Depositos no confirmados expiran en **72 horas** - Retiros pendientes expiran en **24 horas** - Transacciones expiradas se marcan como `EXPIRED` - Fondos de retiros expirados se devuelven al wallet ### RN-005: Direcciones de Retiro - Primera vez: direccion debe esperar 24h antes de usar - Direcciones usadas previamente: retiro inmediato - Maximo 5 direcciones guardadas por moneda - Cambio de direccion requiere 2FA --- ## Integracion con Proveedores ### Coinbase Commerce (Recomendado) ```typescript // Configuracion const coinbase = new CoinbaseCommerce({ apiKey: process.env.COINBASE_API_KEY, webhookSecret: process.env.COINBASE_WEBHOOK_SECRET }); // Crear charge para deposito const charge = await coinbase.charges.create({ name: `Deposito ${userId}`, description: 'Deposito a wallet trading-platform', pricing_type: 'no_price', // Usuario decide monto metadata: { userId, walletId, type: 'deposit' } }); ``` ### BitPay (Alternativa) ```typescript // Configuracion const bitpay = new BitPayClient({ token: process.env.BITPAY_TOKEN, environment: 'prod' }); // Crear invoice para deposito const invoice = await bitpay.createInvoice({ price: 0, // Variable currency: 'USD', buyer: { email: user.email }, posData: JSON.stringify({ userId, walletId }) }); ``` --- ## API Endpoints ### Depositos ```yaml GET /api/v1/crypto/deposit/address: description: Obtener direccion de deposito query: currency: string (BTC, ETH, USDT) network: string (bitcoin, ethereum, tron, polygon) response: address: string qrCode: string (base64) currency: string network: string minDeposit: number GET /api/v1/crypto/deposit/status/{txId}: description: Estado de deposito response: status: string confirmations: number requiredConfirmations: number cryptoAmount: number usdAmount: number txHash: string ``` ### Retiros ```yaml POST /api/v1/crypto/withdraw: description: Solicitar retiro crypto body: amount: number (USD) currency: string network: string address: string response: withdrawalId: string cryptoAmount: number exchangeRate: number fee: number estimatedTime: string GET /api/v1/crypto/withdraw/{id}: description: Estado de retiro response: status: string txHash: string cryptoAmount: number address: string ``` ### Direcciones Guardadas ```yaml GET /api/v1/crypto/addresses: description: Listar direcciones de retiro guardadas POST /api/v1/crypto/addresses: description: Agregar direccion de retiro body: currency: string network: string address: string label: string DELETE /api/v1/crypto/addresses/{id}: description: Eliminar direccion guardada ``` --- ## Seguridad ### Validacion de Direcciones ```typescript function validateCryptoAddress(address: string, network: string): boolean { switch (network) { case 'bitcoin': // Legacy (1...), SegWit (3...), Native SegWit (bc1...) return /^(1|3)[a-zA-Z0-9]{25,34}$|^bc1[a-z0-9]{39,59}$/.test(address); case 'ethereum': case 'polygon': // ERC-20 format return /^0x[a-fA-F0-9]{40}$/.test(address); case 'tron': // TRC-20 format return /^T[a-zA-Z0-9]{33}$/.test(address); default: return false; } } ``` ### Prevencion de Fraude - Monitorear patrones de deposito/retiro inusuales - Bloquear retiros inmediatos post-deposito (1h cooldown) - Verificar consistencia de direcciones (no reutilizar entre usuarios) - Alerta si retiro > 50% del deposito reciente - Require 2FA para retiros > $1,000 ### Hot/Cold Wallet - Hot wallet: maximo 10% de fondos totales - Cold wallet: 90% de fondos en almacenamiento offline - Rebalanceo automatico diario - Multi-sig para cold wallet (3 de 5 firmas) --- ## Manejo de Errores | Error | Codigo | Mensaje Usuario | |-------|--------|-----------------| | KYC no verificado | 403 | Completa la verificacion de identidad para retirar | | Direccion invalida | 400 | La direccion ingresada no es valida para {network} | | Limite excedido | 400 | Excedes el limite de ${limit}/dia. Intenta manana. | | Saldo insuficiente | 400 | Saldo insuficiente. Disponible: ${balance} | | Red no soportada | 400 | La red {network} no esta soportada para {currency} | | Direccion muy nueva | 400 | Nueva direccion. Espera 24h antes de usarla. | | Proveedor no disponible | 503 | Servicio temporalmente no disponible. Intenta mas tarde. | --- ## Webhooks Recibidos | Evento | Accion | |--------|--------| | `charge:created` | Crear crypto_transaction con status DETECTED | | `charge:pending` | Actualizar confirmaciones | | `charge:confirmed` | Convertir a USD, acreditar wallet | | `charge:failed` | Marcar como FAILED | | `payout:completed` | Actualizar retiro con txHash | | `payout:failed` | Revertir debito de wallet | --- ## Configuracion Requerida ```env # Coinbase Commerce COINBASE_API_KEY=xxx COINBASE_WEBHOOK_SECRET=xxx # BitPay (alternativo) BITPAY_TOKEN=xxx BITPAY_WEBHOOK_SECRET=xxx # Limites CRYPTO_DEPOSIT_MIN_USD=10 CRYPTO_DEPOSIT_MAX_USD=50000 CRYPTO_DEPOSIT_MAX_DAILY_USD=100000 CRYPTO_WITHDRAWAL_MIN_USD=50 CRYPTO_WITHDRAWAL_FEE_USD=5 CRYPTO_CONVERSION_SPREAD=0.005 # Confirmaciones CRYPTO_CONFIRMATIONS_BTC=3 CRYPTO_CONFIRMATIONS_ETH=12 CRYPTO_CONFIRMATIONS_TRC20=20 CRYPTO_CONFIRMATIONS_POLYGON=30 # Seguridad CRYPTO_NEW_ADDRESS_COOLDOWN_HOURS=24 CRYPTO_POST_DEPOSIT_COOLDOWN_HOURS=1 ``` --- ## Criterios de Aceptacion - [ ] Usuario puede obtener direccion unica de deposito por moneda/red - [ ] QR code se genera correctamente para cada direccion - [ ] Depositos se detectan via webhook en tiempo real - [ ] Contador de confirmaciones se actualiza correctamente - [ ] Conversion a USD aplica spread de 0.5% - [ ] Wallet se acredita automaticamente al confirmar deposito - [ ] Usuario sin KYC no puede retirar - [ ] Usuario con KYC puede retirar hasta su limite - [ ] Validacion de direcciones funciona para todas las redes - [ ] Retiros se procesan y notifican con TX hash - [ ] Limites diarios se aplican correctamente - [ ] Cooldown de 24h para nuevas direcciones funciona - [ ] Transacciones no confirmadas expiran en 72h --- ## Especificacion Tecnica Relacionada - [ET-PAY-007: Crypto Integration](../especificaciones/ET-PAY-007-crypto.md) ## Historias de Usuario Relacionadas - [US-PAY-020: Depositar Crypto](../historias-usuario/US-PAY-020-depositar-crypto.md) - [US-PAY-021: Retirar Crypto](../historias-usuario/US-PAY-021-retirar-crypto.md) - [US-PAY-022: Ver Estado Deposito](../historias-usuario/US-PAY-022-estado-deposito.md)