- Renombrar 02-integraciones/ → 03-integraciones/ (resolver prefijo duplicado) - Renombrar 02-devops/ → 04-devops/ (resolver prefijo duplicado) - Renombrar architecture/ → 97-adr/ (agregar prefijo numerico) - Actualizar _MAP.md con nueva estructura y version 2.1.0 Estructura final: - 00-vision-general/ - 01-modulos/ - 02-especificaciones/ - 03-integraciones/ - 04-devops/ - 97-adr/ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
215 lines
5.2 KiB
Markdown
215 lines
5.2 KiB
Markdown
---
|
|
id: "ADR-003"
|
|
title: "Billing Integration con Stripe"
|
|
type: "ADR"
|
|
status: "Accepted"
|
|
priority: "P0"
|
|
supersedes: "N/A"
|
|
superseded_by: "N/A"
|
|
version: "1.0.0"
|
|
created_date: "2026-01-07"
|
|
updated_date: "2026-01-10"
|
|
---
|
|
|
|
# ADR-003: Billing Integration con Stripe
|
|
|
|
## Metadata
|
|
| Campo | Valor |
|
|
|-------|-------|
|
|
| ID | ADR-003 |
|
|
| Estado | Accepted |
|
|
| Fecha | 2026-01-10 |
|
|
| Supersede | N/A |
|
|
|
|
## Contexto
|
|
|
|
Template SaaS necesita un sistema de billing para:
|
|
- Planes de suscripción (mensual/anual)
|
|
- Trial periods
|
|
- Múltiples métodos de pago
|
|
- Facturación automática
|
|
- Portal de autoservicio
|
|
- Webhooks para sincronización
|
|
|
|
## Opciones Consideradas
|
|
|
|
### Opción 1: Implementación Custom
|
|
**Descripción:** Sistema de billing propio conectado a pasarelas de pago.
|
|
|
|
**Pros:**
|
|
- Control total
|
|
- Sin comisiones de plataforma
|
|
- Personalización completa
|
|
|
|
**Contras:**
|
|
- Complejidad extrema (PCI compliance)
|
|
- Time-to-market largo
|
|
- Mantenimiento costoso
|
|
- Riesgos de seguridad
|
|
|
|
### Opción 2: Stripe ✓
|
|
**Descripción:** Plataforma de pagos completa con Billing.
|
|
|
|
**Pros:**
|
|
- Rápido de implementar
|
|
- PCI compliance incluido
|
|
- Múltiples países/monedas
|
|
- Portal de cliente built-in
|
|
- Webhooks robustos
|
|
- Excelente documentación
|
|
|
|
**Contras:**
|
|
- Comisiones (~2.9% + 30¢)
|
|
- Dependencia de tercero
|
|
- Algunas limitaciones en personalización
|
|
|
|
### Opción 3: Paddle/Lemonsqueezy
|
|
**Descripción:** Merchant of Record (MoR) completo.
|
|
|
|
**Pros:**
|
|
- Maneja impuestos automáticamente
|
|
- Sin necesidad de entidad legal por país
|
|
- Más simple que Stripe
|
|
|
|
**Contras:**
|
|
- Comisiones más altas (~5-8%)
|
|
- Menos control
|
|
- Menos features
|
|
|
|
## Decisión
|
|
|
|
**Elegimos Stripe** por:
|
|
|
|
1. **Balance:** Buen equilibrio entre control y simplicidad
|
|
2. **Features:** Billing, Invoicing, Customer Portal, Tax
|
|
3. **Ecosistema:** SDKs, webhooks, testing, documentación
|
|
4. **Escalabilidad:** Crece con el negocio
|
|
5. **Confiabilidad:** Uptime excelente, soporte 24/7
|
|
|
|
## Implementación
|
|
|
|
### Arquitectura
|
|
|
|
```
|
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
│ Frontend │────▶│ Backend │────▶│ Stripe │
|
|
└─────────────┘ └─────────────┘ └─────────────┘
|
|
│ │
|
|
│◀───────────────────┘
|
|
│ Webhooks
|
|
┌──────▼──────┐
|
|
│ PostgreSQL │
|
|
└─────────────┘
|
|
```
|
|
|
|
### Modelos de Datos
|
|
|
|
```typescript
|
|
// Local (sincronizado con Stripe)
|
|
Subscription {
|
|
id: UUID
|
|
tenant_id: UUID
|
|
stripe_subscription_id: string
|
|
stripe_customer_id: string
|
|
plan_id: UUID
|
|
status: 'trialing' | 'active' | 'past_due' | 'cancelled'
|
|
current_period_start: Date
|
|
current_period_end: Date
|
|
}
|
|
|
|
Plan {
|
|
id: UUID
|
|
stripe_product_id: string
|
|
stripe_price_id_monthly: string
|
|
stripe_price_id_yearly: string
|
|
name: string
|
|
price_monthly: number
|
|
price_yearly: number
|
|
features: JSON
|
|
limits: JSON
|
|
}
|
|
```
|
|
|
|
### Flujo de Suscripción
|
|
|
|
```
|
|
1. Usuario selecciona plan
|
|
2. Backend crea Checkout Session
|
|
3. Redirect a Stripe Checkout
|
|
4. Usuario completa pago
|
|
5. Stripe envía webhook 'checkout.session.completed'
|
|
6. Backend crea/actualiza Subscription local
|
|
7. Redirect a success page
|
|
```
|
|
|
|
### Webhooks Críticos
|
|
|
|
| Evento | Acción |
|
|
|--------|--------|
|
|
| `checkout.session.completed` | Crear suscripción |
|
|
| `customer.subscription.updated` | Actualizar estado |
|
|
| `customer.subscription.deleted` | Marcar cancelada |
|
|
| `invoice.paid` | Registrar pago |
|
|
| `invoice.payment_failed` | Notificar, retry |
|
|
|
|
### Customer Portal
|
|
|
|
Stripe Customer Portal permite:
|
|
- Ver/descargar facturas
|
|
- Actualizar método de pago
|
|
- Cambiar plan
|
|
- Cancelar suscripción
|
|
|
|
```typescript
|
|
// Crear session del portal
|
|
const session = await stripe.billingPortal.sessions.create({
|
|
customer: tenant.stripe_customer_id,
|
|
return_url: 'https://app.example.com/billing',
|
|
});
|
|
```
|
|
|
|
### Plan Limits Enforcement
|
|
|
|
```typescript
|
|
// Verificar límite antes de acción
|
|
async canAddUser(tenantId: string): Promise<boolean> {
|
|
const subscription = await this.getSubscription(tenantId);
|
|
const plan = await this.getPlan(subscription.plan_id);
|
|
const currentUsers = await this.countUsers(tenantId);
|
|
|
|
return currentUsers < plan.limits.max_users;
|
|
}
|
|
```
|
|
|
|
## Consecuencias
|
|
|
|
### Positivas
|
|
- Time-to-market rápido
|
|
- Sin preocupaciones PCI
|
|
- Billing Portal listo para usar
|
|
- Webhooks confiables
|
|
- Soporte multi-moneda
|
|
|
|
### Negativas
|
|
- Comisiones por transacción
|
|
- Dependencia de Stripe
|
|
- Sincronización de datos necesaria
|
|
- Algunas features requieren Stripe Tax adicional
|
|
|
|
### Testing
|
|
- Stripe Test Mode para desarrollo
|
|
- Webhook testing con Stripe CLI
|
|
- Cards de prueba documentadas
|
|
|
|
## Referencias
|
|
|
|
- [Stripe Billing Docs](https://stripe.com/docs/billing)
|
|
- [Stripe Webhooks](https://stripe.com/docs/webhooks)
|
|
- [Test Cards](https://stripe.com/docs/testing)
|
|
- Implementación: `apps/backend/src/modules/billing/`
|
|
|
|
---
|
|
|
|
**Fecha decision:** 2026-01-10
|
|
**Autores:** Claude Code (Arquitectura)
|