Loading repository data…
Loading repository data…
Lostovayne / repository
Integración completa de pagos con Transbank Webpay Plus en Next.js 16. BetterAuth (2FA, multi-session), rate limiting Upstash Redis, pino logging, audit trail contable, 99 tests Vitest, FoxGuard SAST, arquitectura DDD con Prisma 7 + PostgreSQL. Template profesional para e-commerce chileno.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Template profesional para e-commerce chileno con autenticación, rate limiting, logging estructurado, y arquitectura hexagonal.
Documentación Transbank · API Reference · Reportar Bug
Implementación completa y lista para producción del flujo de pagos Webpay Plus de Transbank en Next.js 16 App Router con TypeScript y Prisma 7.
No usa el SDK oficial de Transbank. Solo fetch, tipos estrictos, y una arquitectura hexagonal que sobrevive en producción.
La mayoría de integraciones Webpay que encuentras en línea tienen los mismos errores críticos:
commit sin manejar 422 → marcan transacciones como FAILED cuando el usuario sí pagóTBK_TOKEN)TBK_TOKEN) del flujo normal (POST con token_ws)INITIALIZEDEste repositorio resuelve todos estos casos. La implementación está auditada contra la referencia oficial de la API v1.2.
| Característica | Estado |
|---|---|
| Webpay Plus REST API v1.2 (sin SDK) | ✅ |
| Manejo correcto de los 3 escenarios de return URL | ✅ |
| Confirmación idempotente (doble clic/reload seguro) | ✅ |
| Fallback inteligente para 422 (ya procesada → sin FAILED) | ✅ |
| Polling worker para transacciones abandonadas (Vercel Cron) | ✅ |
| Máquina de estados explícita en dominio (INITIALIZED → terminal) | ✅ |
| Anti-Corruption Layer (el dominio no conoce HTTP) | ✅ |
| Validación de variables de entorno con Zod 4 al startup | ✅ |
| Página de éxito verifica estado real de la BD | ✅ |
| Persistencia antes de llamada a red (trazabilidad garantizada) | ✅ |
API de reembolsos (requestRefund) | ✅ |
| Rate limiting (Upstash Redis + fallback en memoria) | ✅ |
| BetterAuth (email/password, 2FA, multi-session) | ✅ |
| Verificación de email + reset de contraseña (Resend) | ✅ |
| Sesiones JWE en cookies encriptadas | ✅ |
Audit logging completo (tabla transaction_audit_log) | ✅ |
| Pino structured logging (JSON para Datadog/ELK) | ✅ |
| Idempotencia con P2002 race condition handling | ✅ |
| 99 tests con Vitest (unit + integration) | ✅ |
| Capa | Tecnología |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Lenguaje | TypeScript 5 (strict mode) |
| ORM | Prisma 7 |
| Base de datos | PostgreSQL 17+ |
| Validación | Zod 4 |
| Autenticación | BetterAuth 1.6 (email/password, 2FA, multi-session) |
| Resend (verificación, OTP, reset de contraseña) | |
| Rate Limiting | Upstash Redis (sliding window) |
| Sesiones | Upstash Redis (secondary storage) |
| Logging | Pino (structured JSON) |
| Testing | Vitest (99 tests) |
| Package Manager | Bun |
| Deploy | Vercel (Cron Jobs incluidos) |
| SAST | GGA (pre-commit hook) |
Arquitectura Hexagonal (Puertos y Adaptadores) organizada por scope de features:
src/
├── app/ # Next.js App Router (capa de presentación)
│ ├── api/
│ │ ├── auth/[[...all]]/route.ts # BetterAuth catch-all handler
│ │ └── webpay/
│ │ ├── checkout/route.ts # POST — iniciar pago
│ │ ├── return/route.ts # POST + GET — callback de Transbank
│ │ └── poll/route.ts # GET — worker de recuperación (cron)
│ └── checkout/
│ ├── page.tsx # UI de checkout
│ ├── success/page.tsx # Confirmación de pago (verifica BD)
│ └── error/page.tsx # Pantalla de error
│
├── features/
│ ├── auth/ # Módulo de autenticación
│ │ ├── auth.ts # Configuración de BetterAuth
│ │ └── infrastructure/
│ │ ├── email-service.ts # Templates + envío de emails (Resend)
│ │ ├── upstash-secondary-storage.ts # Adaptador Redis para sesiones
│ │ └── upstash-secondary-storage.test.ts
│ │
│ ├── webpay/ # Módulo de pagos
│ │ ├── domain/
│ │ │ └── Transaction.ts # Entidad + máquina de estados
│ │ ├── application/
│ │ │ └── transactionActions.ts # Casos de uso (Server Actions)
│ │ └── infrastructure/
│ │ ├── TransbankGateway.ts # Adaptador HTTP → API Transbank
│ │ └── PrismaTransactionRepository.ts # Adaptador BD → Dominio
│ │
│ └── rate-limit/ # Módulo de rate limiting
│ ├── domain/
│ │ ├── RateLimitGateway.ts # Interfaz (intercambiable)
│ │ └── parseWindow.ts # Parser de ventana compartido
│ └── infrastructure/
│ ├── UpstashRateLimitGateway.ts # Adaptador Upstash
│ └── MemoryRateLimitGateway.ts # Fallback para desarrollo
│
└── shared/
├── env.ts # Variables de entorno validadas con Zod
├── lib/prisma.ts # Singleton de Prisma client
└── rate-limit.ts # Factory + helpers de rate limiting
┌─────────────────────────────────────────────────────────────────────┐
│ 1. INICIAR │
│ checkout/page.tsx │
│ └── initiateTransactionAction(amount) │
│ ├── Crear WebpayTransaction (INITIALIZED) │
│ ├── Persistir ANTES de llamada a red │
│ ├── TransbankGateway.createTransaction() → token + URL │
│ ├── Guardar token en BD │
│ └── redirect() → formulario de pago de Transbank │
├─────────────────────────────────────────────────────────────────────┤
│ 2. CONFIRMAR (callback de Transbank) │
│ POST /api/webpay/return?token_ws=<token> │
│ └── confirmTransactionAction(token) │
│ ├── A) Normal: commitTransaction() → AUTHORIZED | REJECTED │
│ ├── B) 422: getTransactionStatus() → fallback, sin FAILED │
│ └── C) Ya terminal: idempotente, retornar estado actual │
├─────────────────────────────────────────────────────────────────────┤
│ 3. RECUPERAR (Vercel Cron) │
│ GET /api/webpay/poll [Authorization: Bearer <CRON_SECRET>] │
│ └── pollStaleTransactionsAction() │
│ └── Buscar INITIALIZED > 10 min → getTransactionStatus() │
└─────────────────────────────────────────────────────────────────────┘
Aquí es donde falla el 90% de las integraciones. Transbank puede llamar a la return_url de tres formas diferentes, y debe manejar todas:
| Escenario | Método HTTP | Parámetros |
|---|---|---|
| Pago completado (aprobado o rechazado) | POST | token_ws=<token> |
| Usuario presionó "Cancelar" en la página de pago | POST | TBK_TOKEN=<t> + TBK_ORDEN_COMPRA=<bo> + TBK_ID_SESION=<s> |
| Timeout (5 min sin acción del usuario) | GET | TBK_TOKEN=<t> + TBK_ORDEN_COMPRA=<bo> + TBK_ID_SESION=<s> |
[!IMPORTANT] Cuando el usuario cancela o hay timeout,
token_wsNO está presente. Si solo manejatoken_ws, está ignorando dos de los tres escenarios.
┌──────────┐
│ INITIALIZED │
└─────┬────┘
┌──────────────┼──────────────┬──────────────┐
▼ ▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ AUTHORIZED │ │ REJECTED │ │ ABORTED │ │ FAILED │
└──────┬──────┘ └──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐
│ REVERSED │
└──────────┘
[!CAUTION] Una transacción
AUTHORIZEDNUNCA puede revertirse aFAILED. Si Transbank ya cobró y tu sistema falla después, debe llamar arequestRefund(). Un rollback de estado es un desastre contable y una violación de las políticas de Transbank.
| Módulo | Tests | Descripción |
|---|---|---|
Transaction.test.ts | 24 | Máquina de estados del dominio |
transactionActions.test.ts | 17 | Casos de uso de aplicación |
route.test.ts | 12 | Handlers de rutas API |
upstash-secondary-storage.test.ts | 14 | Adaptador Redis |
PrismaTransactionRepository.test.ts | 8 | Adaptador de BD |
TransbankGateway.test.ts | 6 | Adaptador HTTP Transbank |
auth.test.ts | 18 | Autenticación BetterAuth |
| Total | 99 |
# Ejecutar todos los tests
bun run test
# Ejecutar con coverage
bunx vitest run --coverage
# Ejecutar un archivo específico
bunx vitest run src/features/webpay/domain/Transaction.test.ts
fetch() mockeado — sin Redis real| Característica | Configuración |
|---|---|
| Auth email/password | Habilitada |
| Verificación de email | Requerida antes del primer login |
| 2FA (TOTP + OTP) | Habilitada |
| Multi-session | Permitida (múltiples dispositivos) |
| Expiración de sesión | 7 días |
| Refresh de sesión | Cada 24 horas |
| Fresh age (re-auth) | 30 minutos para acciones sensibles |
| Cache de cookies | JWE encriptado (anti-tampering) |
| CSRF protection | Habilitada (sameSite: strict) |
| Rate limiting | 5 intentos/min para login, 3/min para registro |
| Endpoint | Límite | Ventana |
|---|---|---|
| POST /api/auth/sign-in | 5 intentos | 1 minuto |
| POST /api/auth/sign-up | 3 intentos | 1 minuto |
| POST /api/webpay/checkout | 10 requests | 1 minuto |
BETTER_AUTH_URL: sin default en producción (lanza error si falta)RESEND_API_KEY/RESEND_FROM_EMAIL: opcionales en dev, requeridas en produccióngit clone https://github.com/Lostovayne/WebpayPlus-in-Next16.git
cd WebpayPlus-in-Next16
bun install
cp .env.ex