Práctica: Crear un Sistema de Facturación Automatizado

Lectura
30 min~5 min lectura

Concepto clave

Un sistema de facturación automatizado es como un asistente financiero digital que gestiona todo el ciclo de cobro sin intervención manual. Imagina que tienes un gimnasio con 500 miembros que pagan mensualmente: sin automatización, tendrías que generar 500 facturas manualmente cada mes, enviarlas, verificar pagos y seguir con los impagos. Con Stripe Billing, este proceso se convierte en un flujo automatizado donde las facturas se generan, envían y cobran automáticamente según las suscripciones activas.

El núcleo de este sistema son tres componentes: las suscripciones (que definen qué y cuándo cobrar), las facturas (documentos que detallan cada cobro) y los webhooks (notificaciones que te avisan cuando ocurren eventos importantes como pagos exitosos o fallidos). Es similar a cómo Netflix gestiona sus cobros mensuales: tú te suscribes una vez, y ellos automáticamente generan y cobran tu factura cada mes, notificándote por email sin que un humano intervenga.

Cómo funciona en la práctica

Veamos el flujo completo paso a paso para un servicio SaaS con plan mensual:

  1. Creación de suscripción: Cuando un cliente se registra, creas una suscripción en Stripe asociada a su método de pago y plan.
  2. Generación automática de factura: Al inicio de cada ciclo de facturación (ej. cada 30 días), Stripe genera automáticamente una factura con los ítems correspondientes.
  3. Intento de cobro: Stripe intenta cobrar la factura usando el método de pago guardado.
  4. Notificación vía webhook: Tu backend recibe un evento (ej. invoice.payment_succeeded) que puedes procesar para actualizar tu base de datos o enviar emails personalizados.
  5. Manejo de fallos: Si el pago falla, Stripe reintenta automáticamente según tus configuraciones y notifica con eventos como invoice.payment_failed.

Este flujo elimina el trabajo manual y reduce errores, permitiéndote escalar a miles de clientes con el mismo esfuerzo que para diez.

Código en acción

Aquí tienes un ejemplo funcional de cómo crear una suscripción y configurar webhooks en Node.js:

// Configuración inicial de Stripe
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// 1. Crear una suscripción para un cliente
async function createSubscription(customerId, priceId) {
    try {
        const subscription = await stripe.subscriptions.create({
            customer: customerId,
            items: [{
                price: priceId,
            }],
            payment_behavior: 'default_incomplete',
            expand: ['latest_invoice.payment_intent']
        });
        
        // Retornar datos para el frontend
        return {
            subscriptionId: subscription.id,
            clientSecret: subscription.latest_invoice.payment_intent.client_secret
        };
    } catch (error) {
        console.error('Error creando suscripción:', error);
        throw error;
    }
}

// 2. Webhook para procesar eventos de facturación
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;
    
    try {
        event = stripe.webhooks.constructEvent(
            req.body,
            sig,
            process.env.STRIPE_WEBHOOK_SECRET
        );
    } catch (err) {
        return res.status(400).send(`Webhook Error: ${err.message}`);
    }
    
    // Manejar diferentes tipos de eventos
    switch (event.type) {
        case 'invoice.payment_succeeded':
            const invoice = event.data.object;
            await handleSuccessfulPayment(invoice);
            break;
        case 'invoice.payment_failed':
            const failedInvoice = event.data.object;
            await handleFailedPayment(failedInvoice);
            break;
        default:
            console.log(`Evento no manejado: ${event.type}`);
    }
    
    res.json({received: true});
});

Ahora, veamos cómo mejorar este código añadiendo manejo robusto de errores y logging:

// Versión mejorada con manejo de errores y logging
async function createSubscription(customerId, priceId) {
    const metadata = {
        customerId: customerId,
        priceId: priceId,
        timestamp: new Date().toISOString()
    };
    
    try {
        const subscription = await stripe.subscriptions.create({
            customer: customerId,
            items: [{ price: priceId }],
            payment_behavior: 'default_incomplete',
            expand: ['latest_invoice.payment_intent'],
            metadata: metadata  // Añadimos metadata para tracking
        });
        
        // Log exitoso
        console.log(`Suscripción creada: ${subscription.id} para cliente ${customerId}`);
        
        return {
            subscriptionId: subscription.id,
            clientSecret: subscription.latest_invoice.payment_intent.client_secret,
            invoiceId: subscription.latest_invoice.id
        };
    } catch (error) {
        // Log detallado del error
        console.error(`Error en suscripción para ${customerId}:`, {
            error: error.message,
            type: error.type,
            metadata: metadata
        });
        
        // Lanzar error específico
        throw new Error(`No se pudo crear la suscripción: ${error.message}`);
    }
}

Errores comunes

  • No validar webhooks: Muchos desarrolladores olvidan verificar la firma de los webhooks, exponiéndose a ataques de inyección de eventos. Siempre usa stripe.webhooks.constructEvent() con tu secret.
  • Manejo inadecuado de pagos fallidos: No configurar reintentos automáticos o no notificar a los usuarios lleva a pérdida de ingresos. Configura retry rules en el Dashboard de Stripe y maneja el evento invoice.payment_failed.
  • Falta de idempotencia: Crear múltiples suscripciones o facturas por error al reintentar operaciones. Usa claves idempotentes en todas las llamadas a la API.
  • No expandir recursos necesarios: No usar el parámetro expand cuando necesitas datos anidados (como el payment_intent en una factura), resultando en llamadas adicionales innecesarias.
  • Olvidar actualizar estados locales: Confiar solo en los webhooks sin actualizar tu base de datos puede causar inconsistencias. Siempre sincroniza estados después de procesar eventos.

Checklist de dominio

  1. Puedo crear una suscripción con precios recurrentes y trial periods
  2. Sé configurar y verificar webhooks para eventos de facturación
  3. Implemento manejo robusto de pagos fallidos con reintentos automáticos
  4. Uso claves idempotentes en todas las operaciones críticas
  5. Genero facturas personalizadas con metadata y descripciones claras
  6. Proceso correctamente los eventos invoice.paid, invoice.payment_failed y customer.subscription.deleted
  7. Mantengo consistencia entre mi base de datos y el estado en Stripe

Implementa un sistema completo de facturación con reintentos automáticos

En este ejercicio, crearás un sistema de facturación automatizado para un servicio SaaS con plan mensual. Sigue estos pasos:

  1. Configura el entorno: Crea una cuenta de prueba en Stripe y obtén tus API keys. Configura un proyecto Node.js con Express y la librería Stripe.
  2. Crea recursos base:
    • Crea un producto y precio recurrente mensual en el Dashboard de Stripe o via API
    • Implementa un endpoint para crear clientes con método de pago
  3. Implementa suscripciones: Crea un endpoint POST /api/subscriptions que:
    • Acepte customerId y priceId
    • Cree una suscripción con trial de 7 días
    • Retorne el clientSecret para confirmar el pago en frontend
    • Incluya metadata para tracking
  4. Configura webhooks:
    • Usa el CLI de Stripe para forwardear eventos a tu localhost o despliega un endpoint público
    • Implementa un endpoint POST /webhooks que valide la firma y procese:
      • invoice.payment_succeeded: Actualiza el estado del usuario a activo en tu DB
      • invoice.payment_failed: Envía un email de notificación y registra el intento fallido
      • customer.subscription.deleted: Actualiza el estado a cancelado
  5. Añade reintentos automáticos: En el Dashboard de Stripe, configura retry rules para reintentar pagos fallidos a los 3 y 7 días.
  6. Implementa idempotencia: Modifica tu endpoint de suscripción para aceptar y usar una clave idempotente en el header.
  7. Prueba el flujo completo: Usa cards de prueba de Stripe para simular pagos exitosos, fallidos y cancelaciones.
Pistas
  • Usa stripe.subscriptions.create() con parametro 'payment_behavior' para controlar el primer pago
  • Para webhooks, recuerda usar express.raw() middleware para mantener el body sin parsear
  • Guarda los event.id de los webhooks procesados para evitar duplicados

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.