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

@rutiva/widget

v0.1.3

Published

Widget oficial de Rutiva para integrar pagos C2P en Venezuela. Stripe-style, zero deps, shadow DOM.

Readme

@rutiva/widget

npm bundle provenance license

Widget oficial de Rutiva para aceptar pagos C2P en Venezuela. Estilo Stripe.js — un <script>, una línea de JS, UI lista. Zero dependencias. Shadow DOM. ~6 KB gzipped.


¿Qué es esto?

@rutiva/widget es el SDK de navegador de Rutiva. Embeber pagos C2P en tu sitio sin construir UI propia, sin manejar tokens sensibles en el cliente, sin redirigir al usuario fuera de tu página.

Cómo funciona (igual que Stripe):

┌──────────┐   1. POST /v1/payments       ┌─────────────┐
│ Browser  │ ──────────────────────────▶  │ Tu backend  │
│ (cliente)│                              │   (sk_)     │
│          │ ◀───── client_secret ─────── │             │
│          │   2. mount widget            └─────────────┘
│          │
│  widget  │   3. cliente elige banco + OTP
│          │ ──────────────────────────▶  ┌─────────────┐
│          │                              │ Rutiva API  │
│          │ ◀──── succeeded / failed ─── │             │
└──────────┘                              └─────────────┘

Tu backend crea el payment_intent con tu sk_ (server-side). El widget recibe solo el client_secret y confirma el pago. La sk_ jamás toca el navegador.


Instalación

Vía CDN (más rápido)

<script src="https://cdn.jsdelivr.net/npm/@rutiva/[email protected]/dist/rutiva.iife.js"></script>

Vía npm / pnpm / yarn

npm install @rutiva/widget
# o
pnpm add @rutiva/widget
# o
yarn add @rutiva/widget

Quick start (30 segundos)

HTML vanilla

<!doctype html>
<html>
  <body>
    <div id="rutiva-pago"></div>

    <script src="https://cdn.jsdelivr.net/npm/@rutiva/[email protected]/dist/rutiva.iife.js"></script>
    <script>
      const rutiva = Rutiva.Rutiva({ publishableKey: "pk_test_xxx" });

      const widget = rutiva.elements({
        clientSecret: "pi_abc_secret_xxx",   // viene de tu backend
        appearance: { theme: "auto", primaryColor: "#1A56DB" },
      });

      widget.mount("#rutiva-pago");

      widget.on("success", (intent) => {
        console.log("Pago aprobado:", intent.bank_reference);
      });
      widget.on("error", (err) => console.error(err));
    </script>
  </body>
</html>

React / Vue / Svelte / TypeScript

import { Rutiva } from "@rutiva/widget";

const rutiva = Rutiva({ publishableKey: "pk_test_xxx" });
const widget = rutiva.elements({ clientSecret });
widget.mount("#rutiva-pago");

widget.on("success", (intent) => navigate("/gracias?ref=" + intent.bank_reference));

Ejemplo completo React → examples/react/


Crear el client_secret en tu backend

El paso server-side. Una vez por intento de pago.

Node.js

const resp = await fetch("https://rutiva-api.onrender.com/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RUTIVA_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    amount: 50000,             // céntimos: Bs. 500,00
    currency: "VES",
    customer_phone: "04141234567",
    customer_id_document: "V12345678",
    customer_bank_code: "0114",
  }),
});
const { id, client_secret } = await resp.json();
// Enviar SOLO { id, client_secret } al navegador. Nunca sk_.

Python

import httpx, os, uuid

resp = httpx.post(
    "https://rutiva-api.onrender.com/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['RUTIVA_SECRET_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "amount": 50000,
        "currency": "VES",
        "customer_phone": "04141234567",
        "customer_id_document": "V12345678",
        "customer_bank_code": "0114",
    },
)
intent = resp.json()

Docs completos integración: https://rutiva-api.onrender.com/docs


API

Rutiva(options) — factory

type RutivaOptions = {
  publishableKey: string;          // "pk_test_..." o "pk_live_..."
  apiBaseUrl?: string;             // default "https://rutiva-api.onrender.com"
  locale?: "es-VE" | "es" | "en";  // default "es-VE"
};

Devuelve { elements, banks }.

rutiva.elements(config) — crear widget

type ElementsConfig = {
  clientSecret: string;
  appearance?: {
    theme?: "light" | "dark" | "auto";   // "auto" sigue prefers-color-scheme
    primaryColor?: string;                // default "#0F172A"
    borderRadius?: string;                // default "8px"
    fontFamily?: string;                  // default system font stack
  };
  locale?: "es-VE" | "es" | "en";
};

Devuelve un RutivaWidget:

type RutivaWidget = {
  mount: (target: string | HTMLElement) => void;   // CSS selector o nodo
  unmount: () => void;                             // remueve UI, conserva listeners
  on: (event, handler) => () => void;              // retorna off()
  off: (event, handler) => void;
  destroy: () => void;                             // unmount + clear listeners
};

rutiva.banks() — listar bancos

const banks = await rutiva.banks();
// [{ code: "0114", name: "Bancaribe" }, ...]

Útil si querés renderizar el dropdown de bancos en tu UI (el widget ya lo hace internamente).

Eventos

widget.on("ready", () => { /* widget montado + bancos cargados */ });
widget.on("loading", () => { /* submit en curso */ });
widget.on("success", (intent) => { /* status === "succeeded" */ });
widget.on("declined", (intent) => { /* status === "failed" */ });
widget.on("expired", (err) => { /* 15+ min desde create */ });
widget.on("error", (err) => { /* red, validación, otro */ });

| Evento | Payload | Cuándo | | ---------- | -------------------------------- | ------------------------------------------- | | ready | — | Widget montado + bancos cargados | | loading | — | Submit en curso | | success | PaymentIntent | Pago aprobado | | declined | PaymentIntent | Banco rechazó (failure_code) | | expired | { code, message } | payment_intent expiró (>15 min) | | error | { code, message } | Red, validación, o respuesta inesperada |

on() devuelve una función off() para desuscribir.


Theming

rutiva.elements({
  clientSecret,
  appearance: {
    theme: "dark",
    primaryColor: "#1A56DB",
    borderRadius: "12px",
    fontFamily: "Inter, system-ui, sans-serif",
  },
});

Todo va dentro de Shadow DOM. Cero CSS leak al sitio host. Tu site puede tener cualquier reset/framework — el widget se ve igual.

Modo "auto" respeta @media (prefers-color-scheme: dark).


i18n

Locales incluidos: es-VE (default), es, en.

Rutiva({ publishableKey, locale: "en" });
// o por widget
rutiva.elements({ clientSecret, locale: "en" });

¿Faltan otros? Abrí un issue.


Browser support

| Navegador | Versión mínima | | -------------- | -------------- | | Chrome / Edge | últimas 2 | | Firefox | últimas 2 | | Safari | 14+ | | iOS Safari | 14+ | | IE | ❌ no soportado |


Seguridad

  • CSP-friendly: corre con
    script-src 'self' cdn.jsdelivr.net;
    connect-src 'self' rutiva-api.onrender.com;
    Sin unsafe-inline, sin unsafe-eval.
  • SRI (Subresource Integrity) soportado:
    <script
      src="https://cdn.jsdelivr.net/npm/@rutiva/[email protected]/dist/rutiva.iife.js"
      integrity="sha384-..."
      crossorigin="anonymous"
    ></script>
    Generá el hash con:
    curl -s https://cdn.jsdelivr.net/npm/@rutiva/[email protected]/dist/rutiva.iife.js \
      | openssl dgst -sha384 -binary | openssl base64 -A
  • sk_ rechazado en cliente: si pasás una secret key por error, el widget lanza RutivaError.
  • OTP nunca se loggea ni se filtra en eventos emitidos.
  • Provenance verificado: cada release npm está firmado con sigstore (ver badge arriba).

Troubleshooting

| Síntoma | Causa probable | Solución | | ---------------------------------------- | --------------------------------------- | ---------------------------------------------- | | RutivaError: invalid_publishable_key | Usaste sk_ en frontend | Pasar pk_test_... o pk_live_... | | RutivaError: invalid_client_secret | clientSecret malformado o tipeado mal | Verificar shape pi_<id>_secret_<token> | | Evento expired | >15 min desde POST /v1/payments | Crear nuevo intent server-side | | Widget no aparece | Selector mount inválido | Verificar #id existe o pasar HTMLElement | | Estilos del site afectan widget | (no debería pasar) | Reportar issue — Shadow DOM debería aislar | | status === "failed" siempre | Backend en modo test devuelve mock | Verificar credenciales sk_live_ vs sk_test_ |


Ejemplos

Probar local:

pnpm dlx http-server examples/
# abrir http://localhost:8080/vanilla.html

Roadmap

  • [x] 0.1.0 — flujo C2P básico (banks + OTP + confirm)
  • [ ] 0.2.0 — theming extendido (fuentes custom, dark mode tuneable)
  • [ ] 0.3.0 — paquete @rutiva/react con hook useRutiva()
  • [ ] 1.0.0 — API estable, semver garantizado

Contribuir

git clone https://github.com/ppsbcontest/rutiva_widget.git
cd rutiva_widget
pnpm install
pnpm test
pnpm build

Ver docs/CONTRIBUTING.md + docs/ARCHITECTURE.md.

Issues y PRs bienvenidos: https://github.com/ppsbcontest/rutiva_widget/issues


Soporte

  • API docs: https://rutiva-api.onrender.com/docs
  • Email: [email protected]
  • Issues: https://github.com/ppsbcontest/rutiva_widget/issues

License

MIT © Rutiva