Saltar al contenido

Lección 17 de 25 · Lectura y práctica

Manejar Eventos Comunes como payment_intent.succeeded

Concepto claveLos webhooks de Stripe son notificaciones HTTP que tu servidor recibe cuando ocurren eventos importantes en tu cuenta de Stripe. Imagina que son como mensajes de texto automáticos que Stripe te envía cuando algo.

Del concepto a la práctica

Taller de esta lección

payment_intent.succeeded confirma que un intento de pago terminó correctamente, pero la acción de negocio debe ser idempotente y contextual. Relaciona el intento con Cliente, Factura o Suscripción antes de conceder algo.

Ejemplo resuelto

Evento evt_31 de tipo payment_intent.succeeded contiene en data.object el PaymentIntent pi_31. El servidor lo enlaza con una intención de negocio ya guardada para Ana; los metadatos no son autorización por sí solos. Registra el pago exitoso con deduplicación, pero no marques una factura o suscripción pagada solo por copiar invoice_id de los metadatos.

Pruébalo

  1. Verifica firma y tipo.

  2. Extrae identificadores, no datos innecesarios.

  3. Comprueba si el evento ya existe.

  4. Aplica la transición y registra el resultado.

Error frecuente: enviar el curso cada vez que llega el evento. Corrección: usa el identificador del evento o del intento como clave única.

Comprueba tu resultado

El primer procesamiento registra pi_31 exitoso una vez; el segundo evt_31 devuelve “ya procesado”. Para facturación recurrente, invoice.paid y la suscripción reconciliada determinan la concesión bajo la política elegida. Un pago único utiliza la orden verificada y su regla propia.

Proveedor de pago que envía un webhook verificado al servidor; registro idempotente antes de conceder acceso.
CursaloMapa de práctica
Confirmar el pago en el servidor

El servidor concede acceso a partir de un evento verificado del proveedor y un registro persistente. La pantalla de éxito del navegador es una notificación, no una prueba suficiente de pago. La idempotencia evita duplicar la operación cuando el evento llega más de una vez.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Concepto clave

Los webhooks de Stripe son notificaciones HTTP que tu servidor recibe cuando ocurren eventos importantes en tu cuenta de Stripe. Imagina que son como mensajes de texto automáticos que Stripe te envía cuando algo relevante sucede, como cuando un pago se completa exitosamente. El evento payment_intent.succeeded es uno de los más críticos: indica que un cliente ha pagado correctamente y tú debes entregar el producto o servicio.

En el mundo real, esto es similar a cuando compras algo en línea y recibes un correo de confirmación automático después del pago. Stripe actúa como el sistema de notificaciones que desencadena acciones en tu backend, como actualizar la base de datos o enviar un email al cliente. Sin webhooks, tendrías que consultar constantemente a Stripe para ver si hubo cambios, lo que es ineficiente y poco confiable.

Cómo funciona en la práctica

Cuando un cliente completa un pago, Stripe genera un evento payment_intent.succeeded y lo envía a la URL de webhook que configuraste. Tu servidor debe:

  1. Recibir la solicitud HTTP POST con los datos del evento.
  2. Verificar la firma del webhook para asegurar que viene de Stripe.
  3. Procesar el evento según el tipo (en este caso, payment_intent.succeeded).
  4. Actualizar tu sistema, por ejemplo, marcando el pedido como pagado.
  5. Responder con un código HTTP 200 para confirmar la recepción.

Ejemplo de flujo: Un usuario paga una suscripción mensual de $10. Stripe crea un PaymentIntent, procesa el pago, y si es exitoso, dispara el webhook. Tu backend recibe el evento, extrae el ID del cliente y la cantidad, actualiza la base de datos para activar la suscripción, y envía un recibo por email.

Código en acción

Aquí tienes un ejemplo básico en Node.js usando Express y la librería oficial de Stripe:

const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.json());

app.post('/webhook', 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) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Manejar el evento payment_intent.succeeded
  if (event.type === 'payment_intent.succeeded') {
    const paymentIntent = event.data.object;
    console.log(`PaymentIntent ${paymentIntent.id} succeeded for ${paymentIntent.amount} cents.`);
    
    // Aquí actualizas tu base de datos
    // Ejemplo: marcar orden como pagada
    await updateOrderStatus(paymentIntent.metadata.orderId, 'paid');
  }

  res.json({received: true});
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Ahora, mejoremos el código para manejar errores y logging de forma más robusta:

app.post('/webhook', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody || req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    // Registrar el error en un servicio como Sentry
    captureError(err);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  try {
    switch (event.type) {
      case 'payment_intent.succeeded':
        await handlePaymentIntentSucceeded(event.data.object);
        break;
      default:
        console.log(`Unhandled event type: ${event.type}`);
    }
    res.json({received: true});
  } catch (error) {
    console.error('Error processing webhook:', error);
    res.status(500).send('Internal Server Error');
  }
});

async function handlePaymentIntentSucceeded(paymentIntent) {
  console.log(`Processing payment ${paymentIntent.id} for order ${paymentIntent.metadata.orderId}`);
  // Lógica de negocio: actualizar base de datos, enviar email, etc.
  await updateOrder(paymentIntent.metadata.orderId, {
    status: 'paid',
    paymentId: paymentIntent.id,
    amount: paymentIntent.amount / 100 // Convertir de centavos a unidades
  });
  await sendReceiptEmail(paymentIntent.receipt_email, paymentIntent.amount);
}

Errores comunes

  • No verificar la firma del webhook: Esto expone tu sistema a ataques donde alguien podría enviar eventos falsos. Siempre usa stripe.webhooks.constructEvent con tu secreto de webhook.
  • No manejar eventos duplicados: Stripe puede reenviar webhooks si no recibe una respuesta HTTP 200. Implementa idempotencia guardando los IDs de evento procesados para evitar acciones repetidas.
  • Procesar eventos de forma síncrona: Si tu lógica de negocio tarda mucho (como enviar emails), el webhook podría timeout. Usa colas de mensajes (como RabbitMQ o AWS SQS) para procesar de forma asíncrona.
  • No probar webhooks en entorno local: Usa la CLI de Stripe para reenviar eventos a tu localhost durante el desarrollo. Ejecuta stripe listen --forward-to localhost:3000/webhook.
  • Olvidar el raw body: En algunos frameworks, necesitas acceder al cuerpo crudo de la solicitud para la verificación. En Express, usa middleware como body-parser con verify para preservar el raw body.

Checklist de dominio

  1. Configurar un endpoint HTTPS para recibir webhooks en tu servidor.
  2. Verificar la firma de cada webhook usando el secreto de Stripe.
  3. Manejar el evento payment_intent.succeeded actualizando el estado del pedido en tu base de datos.
  4. Implementar idempotencia para evitar procesar eventos duplicados.
  5. Probar webhooks en desarrollo con la CLI de Stripe o el dashboard.
  6. Manejar errores graciosamente y registrar logs para depuración.
  7. Usar procesamiento asíncrono para tareas largas como envío de emails.

Implementar un webhook para payment_intent.succeeded en un proyecto existente

En este ejercicio, integrarás el manejo de webhooks en una aplicación backend simulada. Sigue estos pasos:

  1. Configura el entorno: Crea un nuevo directorio para el proyecto y ejecuta npm init -y. Instala las dependencias necesarias: npm install express stripe body-parser.
  2. Crea un servidor básico: En un archivo server.js, configura un servidor Express que escuche en el puerto 3000. Asegúrate de usar body-parser para manejar JSON y preservar el raw body para la verificación de webhooks.
  3. Implementa el endpoint de webhook: Agrega una ruta POST en /webhook que:
    • Verifique la firma del webhook usando stripe.webhooks.constructEvent.
    • Maneje el evento payment_intent.succeeded extrayendo el ID del pago y la cantidad.
    • Simule una actualización de base de datos imprimiendo un mensaje en consola.
    • Responda con HTTP 200 si todo sale bien, o con un error apropiado en caso de fallo.
  4. Prueba con la CLI de Stripe: Instala la CLI de Stripe y ejecuta stripe listen --forward-to localhost:3000/webhook. En otra terminal, dispara un evento de prueba con stripe trigger payment_intent.succeeded y verifica que tu servidor lo procese correctamente.
  5. Agrega idempotencia: Modifica el código para almacenar los IDs de eventos procesados en un array en memoria (o en una base de datos simple) y evita procesar eventos duplicados.
Pistas
  • Usa body-parser con la opción verify para preservar el raw body necesario para la verificación de la firma.
  • Recuerda que el secreto del webhook es diferente a tu clave secreta de Stripe; lo encuentras en el dashboard de Stripe en la sección de webhooks.
  • Para simular la base de datos, puedes usar un objeto JavaScript simple como let processedEvents = {} para almacenar los IDs.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con Implementación Profesional de Suscripciones y Facturación con Stripe

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.