npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rodmarzavala/recurrente-sdk

v1.2.0

Published

TypeScript SDK para la API de Recurrente — la pasarela de pagos y suscripciones de Guatemala. Zero deps, Edge-first, compatible con Next.js, Astro y Cloudflare Workers.

Readme

@rodmarzavala/recurrente-sdk

SDK no oficial de TypeScript para la API REST de Recurrente 🇬🇹
Hecho en Guatemala para developers guatemaltecos — y para cualquiera que quiera cobrar en quetzales.

npm version npm downloads License: MIT CI TypeScript Hecho en Guatemala

🇵🇬 Documentación en español e inglés: rodmarzavala.github.io/recurrente-sdk


¿Por qué este SDK?

Recurrente es la plataforma de pagos y suscripciones líder en Guatemala. Este SDK te da acceso a toda su API desde TypeScript/JavaScript con una experiencia de desarrollo de primer nivel — sin peleas con fetch crudo, sin cobros dobles, sin webhooks inseguros.

| Feature | Detalle | |---------|---------| | ⚡️ Edge-first | Usa solo Web APIs estándar — funciona en Cloudflare Workers, Vercel Edge, Deno, Bun y Node.js ≥ 18 sin cambios | | 📦 Zero dependencias | fetch nativo + Web Crypto API — nada en dependencies | | 🛡️ Seguro por defecto | Verificación de webhooks con crypto.subtle.verify (tiempo constante) + protección contra replay attacks (ventana 5 min) | | 💪 Resiliente | Reintentos con exponential backoff para 429 & 5xx · soporte Retry-After · timeout de 30s via AbortController | | 🎯 100% tipado | TypeScript estricto en todo — noImplicitAny, sin any | | 🔑 Idempotente | Idempotency-Key generado automáticamente y reutilizado en reintentos — sin cobros dobles | | 📋 Paginación | Todos los endpoints de lista retornan Page<T> con helpers pageIterator() y autoPagingToArray() |


Instalación

npm install @rodmarzavala/recurrente-sdk
# o
pnpm add @rodmarzavala/recurrente-sdk
# o
yarn add @rodmarzavala/recurrente-sdk

Requisito mínimo: Node.js ≥ 18, Deno ≥ 1.38, Bun ≥ 1.0, o cualquier runtime con Fetch API y Web Crypto API.


🪄 Asistente de Configuración (CLI)

El SDK incluye una herramienta de línea de comandos (CLI) interactiva para configurar tu proyecto en segundos. ¡Genera tus variables de entorno y tu ruta de webhooks (ej. para Next.js o Express) automáticamente!

npx @rodmarzavala/recurrente-sdk init

🚀 Webhook Forwarder Local (Efecto Stripe CLI)

Desarrollar webhooks en local no debería ser doloroso. No instales ngrok ni pagues por túneles. La CLI del SDK incluye un forwarder que envía los eventos de Recurrente directo a tu localhost, ¡y refirma criptográficamente el payload para que tu código local no falle al verificar las firmas!

npx @rodmarzavala/recurrente-sdk listen --forward-to http://localhost:3000/api/webhooks/recurrente

Quick Start

import { Recurrente } from "@rodmarzavala/recurrente-sdk";

const recurrente = new Recurrente({
  publicKey: process.env.RECURRENTE_PUBLIC_KEY!,
  secretKey: process.env.RECURRENTE_SECRET_KEY!,
});

// Crea un checkout y redirige al cliente
const checkout = await recurrente.checkouts.create({
  items: [
    {
      name: "Plan Pro",
      amount_in_cents: 29900, // Q299.00
      currency: "GTQ",
      quantity: 1,
    },
  ],
  success_url: "https://tudominio.com/gracias",
  cancel_url: "https://tudominio.com/cancelar",
});

redirect(checkout.checkout_url);

Módulos disponibles

Checkouts

const checkout = await recurrente.checkouts.create({ ... });
const checkout = await recurrente.checkouts.retrieve("ch_abc123");
const page     = await recurrente.checkouts.list({ page: 1, items: 20 });

Subscriptions

const { subscription, checkout_url } = await recurrente.subscriptions.create({ ... });
const sub  = await recurrente.subscriptions.retrieve("su_abc123");
const page = await recurrente.subscriptions.list();
await recurrente.subscriptions.cancel("su_abc123");

Refunds

// Reembolso total
const refund = await recurrente.refunds.create({ checkout_id: "ch_abc123" });
// Reembolso parcial
const partial = await recurrente.refunds.create({ checkout_id: "ch_abc123", amount_in_cents: 5000 });
const page = await recurrente.refunds.list({ checkout_id: "ch_abc123" });

Products

const page    = await recurrente.products.list();
const product = await recurrente.products.retrieve("prod_abc123");

// Puedes pasar `RequestOptions` (idempotencyKey, timeout) como último parámetro en cualquier método
const created = await recurrente.products.create(
  { name: "Plan Pro", ... },
  { idempotencyKey: "req_xyz_123", timeout: 15000 }
);

const updated = await recurrente.products.update("prod_abc123", { name: "Plan Pro v2" });
await recurrente.products.archive("prod_abc123");

Customers

const page     = await recurrente.customers.list();
const customer = await recurrente.customers.retrieve("cus_abc123");
const created  = await recurrente.customers.create({ email: "[email protected]" });

Webhook Endpoints

const endpoint = await recurrente.webhookEndpoints.create({
  url: "https://myapp.com/webhooks/recurrente",
});
console.log(endpoint.signing_secret); // ¡guárdalo — solo se muestra una vez!
await recurrente.webhookEndpoints.delete(endpoint.id);

Paginación

import { pageIterator, autoPagingToArray } from "@rodmarzavala/recurrente-sdk";

// Iterar página por página
for await (const page of pageIterator((p) => recurrente.products.list(p))) {
  page.data.forEach((p) => console.log(p.name));
}

// Obtener todos los registros de una sola vez
const all = await autoPagingToArray((p) => recurrente.customers.list(p));

Verificación de Webhooks

Verifica que los webhooks entrantes son auténticos y obtén tipado fuerte (Discriminated Union) para el evento.

import { RecurrenteWebhooks } from "@rodmarzavala/recurrente-sdk";

try {
  // `constructEvent` verifica la firma y retorna un `RecurrenteEvent` tipado
  const event = await RecurrenteWebhooks.constructEvent(
    rawBody,   // ⚠️ string crudo — NO JSON parseado
    {
      "svix-id":        req.headers["svix-id"],
      "svix-timestamp": req.headers["svix-timestamp"],
      "svix-signature": req.headers["svix-signature"],
    },
    process.env.RECURRENTE_WEBHOOK_SECRET! // "whsec_..."
  );

  switch (event.type) {
    case "checkout.succeeded":
      console.log(`Pagado: ${event.data.amount_in_cents}`); // event.data es CheckoutResponse
      break;
    case "subscription.canceled":
      console.log(`Cancelada: ${event.data.id}`); // event.data es SubscriptionResponse
      break;
  }
} catch (err) {
  return res.status(401).send("Unauthorized");
}

Manejo de errores

import { isRecurrenteError } from "@rodmarzavala/recurrente-sdk";

try {
  await recurrente.checkouts.retrieve("ch_nonexistent");
} catch (err) {
  if (isRecurrenteError(err)) {
    console.error(err.statusCode); // 404
    console.error(err.message);    // mensaje de error de la API
    console.error(err.body);       // body completo del error
  }
}

Reintentos automáticos

El cliente reintenta 429 (rate limit) y 5xx automáticamente con backoff exponencial (max 3 reintentos, cap 30s). Configurable:

const recurrente = new Recurrente({
  publicKey: "...",
  secretKey: "...",
  maxRetries: 5, // o 0 para deshabilitar
});

Documentación

📖 rodmarzavala.github.io/recurrente-sdk — Documentación completa en español e inglés.

| Guía | Descripción | |------|-------------| | Inicio Rápido | Instalación, primera request, sandbox vs producción | | API Reference | Todos los métodos, parámetros e interfaces TypeScript | | Webhooks | Verificación, tipos de eventos, ejemplos por framework | | Frameworks | Next.js, Astro, React | | Docs de Recurrente | Documentación oficial de la API de Recurrente |


Compatibilidad

| Runtime | Versión mínima | Estado | |---------|---------------|--------| | Node.js | 18.0.0 | ✅ Soportado | | Cloudflare Workers | Cualquiera | ✅ Soportado | | Vercel Edge Functions | Cualquiera | ✅ Soportado | | Deno | 1.38.0 | ✅ Soportado | | Bun | 1.0.0 | ✅ Soportado | | Browser | Moderno (ES2022+) | ✅ Soportado |


Contribuir

¡Todas las contribuciones son bienvenidas! Ya sea un fix de bug, un módulo nuevo, o una typo en los docs.

👉 Lee CONTRIBUTING.md para empezar.

git clone https://github.com/rodmarzavala/recurrente-sdk.git
cd recurrente-sdk
npm install
npm test          # 31 tests, todos deben pasar
npm run typecheck # cero errores

License

MIT — ver LICENSE.


Disclaimer: Este es un proyecto open-source independiente y no está oficialmente afiliado ni respaldado por Recurrente.