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

@klappay/checkout-kit

v1.9.0

Published

Build a custom Klappay checkout — turn a Charge into what a payment UI needs, and talk to an injected wallet, without reimplementing either.

Readme

@klappay/checkout-kit

Build your own Klappay checkout UI, without reimplementing the two hard parts: turning a Charge into what a payment UI needs, and talking to an injected wallet. See CLAUDE.md for the full rationale.

Full docs (guides + a full checkout-flow walkthrough) live at node-checkout-sdk.klappay.com, built from docs/ — run pnpm docs:dev to browse them locally instead. The site also publishes llms.txt and llms-full.txt — plain-text, LLM-friendly versions of these docs, regenerated on every deploy, for feeding an agent or MCP server.

Want a runnable app instead of a doc page? See examples/ — Hono (no bundler at all), Next.js, SvelteKit, and Nuxt, each a real pnpm install && pnpm dev away from running.

Install

pnpm add @klappay/checkout-kit @klappay/types

Node — your backend

import { createCheckoutKit } from '@klappay/checkout-kit/node'

const checkout = createCheckoutKit({
  apiKey: process.env.KLAP_API_KEY!,
  baseUrl: process.env.KLAP_BASE_URL!,
})

// Route your frontend calls to build its own UI from:
app.get('/api/checkout/:id', async (c) => {
  const payload = await checkout.getCheckoutPayload(c.req.param('id'))
  return c.json(payload)
})

// Optional: relay live status to your own frontend (SSE shown here,
// any transport your frontend already speaks works the same way)
app.get('/api/checkout/:id/events', async (c) => {
  return streamSSE(c, async (stream) => {
    for await (const payload of checkout.watchCheckout(c.req.param('id'))) {
      await stream.writeSSE({ event: 'charge', data: JSON.stringify(payload) })
    }
  })
})

apiKey/baseUrl are both optional as of @klappay/[email protected] — omit either and it falls back to process.env.KLAP_API_KEY/ process.env.KLAP_BASE_URL, an explicit argument always winning:

const checkout = createCheckoutKit() // reads KLAP_API_KEY / KLAP_BASE_URL

See docs/node.md for the full fallback order.

Want a different response shape than getCheckoutPayload()'s default? Compose it yourself from the same pieces it's built from:

import { resolvePaymentOptions, toCheckoutPayload } from '@klappay/checkout-kit/node'

const charge = await checkout.getCharge(chargeId) // full raw Charge
const options = resolvePaymentOptions(charge) // one PaymentOption per accepted pair

Client — your frontend

Headless — no DOM/framework assumptions, drops into React, Vue, Svelte, or plain JS the same way. See docs/frameworks.md for hook/composable/store examples per framework, or docs/examples.md for complete server + client integrations (Hono, Next.js).

import { createWalletPayment, buildPaymentUri, isWalletPayable } from '@klappay/checkout-kit/client'

// Not every accepted (token, network) pair is wallet-payable — this
// package may have no chain-id/contract mapping for one (chainId and
// contractAddress are null in that case). It's still payable by QR/
// manual address, just gate the wallet button on isWalletPayable():
const [option] = payload.paymentOptions.filter(isWalletPayable)
const wallet = createWalletPayment(option, payload.address)

wallet.on('sent', (txHash) => console.log('sent', txHash))
await wallet.connect()
await wallet.pay()

// On reload, restore an already-authorized wallet without a popup:
await wallet.reconnect()

// Or render a QR instead of using an injected wallet — no extra network
// call, everything needed is already in `payload`:
const uri = buildPaymentUri(option, payload.address)

Both createWalletPayment() and buildPaymentUri() throw immediately if handed an option with no wallet mapping — filter with isWalletPayable() first. For a pair that isn't wallet-payable, render payload.address directly (as a static "send to this address" QR/text) instead of calling buildPaymentUri() on it.

With more than one wallet extension installed, discoverProviders() (EIP-6963) lets the payer pick instead of createWalletPayment() guessing at window.ethereum:

import { discoverProviders } from '@klappay/checkout-kit/client'

const providers = await discoverProviders() // [{ info: { name, icon, rdns, uuid }, provider }, ...]
const wallet = createWalletPayment(option, payload.address, providers[0]?.provider)

Tracking "confirming" state across a reload, before your own status route reflects it:

import { saveConfirming, getConfirming, clearConfirming } from '@klappay/checkout-kit/client'

Live status from your own backend route:

import { watchCheckoutEvents } from '@klappay/checkout-kit/client'

const stop = watchCheckoutEvents(`/api/checkout/${id}/events`, (payload) => {
  // re-render with payload.status — isOpenStatus(payload.status) tells
  // you whether it's still payable ('pending'/'partially_paid') or
  // terminal ('confirmed'/'expired'/'underpaid')
})

Sending the payer back to your own site once payment confirms:

import { resolveRedirectUrl } from '@klappay/checkout-kit/client'

if (payload.status === 'confirmed') {
  const url = resolveRedirectUrl(payload.redirectUrl) // null unless http(s)
  if (url) window.location.href = url
}

resolveRedirectUrl() is also exported from /node, for validating redirectUrl server-side before it ever reaches the browser.

No bundler on the frontend? Use the script-tag build

/client also ships as a self-contained IIFE — everything resolved and inlined, no import needed — for a frontend with no bundler at all (plain <script src="...">, no build step):

<script src="/vendor/klap-checkout-kit/index.global.js"></script>
<script>
  const wallet = KlapCheckoutKit.createWalletPayment(option, payload.address)
  wallet.on('sent', (txHash) => console.log('sent', txHash))
  await wallet.connect()
  await wallet.pay()
</script>

Every named export above (createWalletPayment, buildPaymentUri, isWalletPayable, resolveRedirectUrl, etc.) is a property of the KlapCheckoutKit global — same functions, same behavior as the ESM import, just reachable without a bundler. Serve node_modules/@klappay/checkout-kit/dist/client/index.global.js directly (e.g. a second static-file route pointed at that path) rather than copying it — that way it always matches whatever version is actually installed.

Payer has a wallet app, not a browser extension? Use WalletConnect

createWalletPayment()/createSwapPayment() accept any object shaped like an injected wallet as their optional third argument — not just window.ethereum. @klappay/checkout-kit/client/walletconnect is a second, optional way to get one, for a payer on a mobile browser tab (or any desktop browser with no wallet extension) who only has a wallet app to pair with via QR/deep link:

pnpm add @walletconnect/universal-provider # peer dependency, only needed for this subpath
import { createWalletConnectProvider } from '@klappay/checkout-kit/client/walletconnect'
import { createWalletPayment } from '@klappay/checkout-kit/client'

const wc = await createWalletConnectProvider({
  projectId: 'YOUR_REOWN_CLOUD_PROJECT_ID', // cloud.reown.com — free, your own domain
  chainIds: [option.chainId],
  metadata: { name: 'Your Store', description: '...', url: 'https://...', icons: ['...'] },
})
wc.on('uri', (uri) => showYourOwnQrCodeOrDeepLink(uri)) // no modal shipped — bring your own UI

const provider = await wc.connect() // resolves once the payer approves on their phone
const wallet = createWalletPayment(option, payload.address, provider) // everything else unchanged
await wallet.connect()
await wallet.pay()

A projectId is your own (free) cloud.reown.com registration — it can't be baked into this package, since usage is metered/rate-limited per project and tied to your domain for the "verified" badge shown in the payer's wallet. No modal/QR-rendering code ships here either, same "bring your own" stance as buildPaymentUri(). This subpath is a separate peerDependency (@walletconnect/universal-provider, several MB) specifically so nobody using only injected-wallet payments pays for it — the main /client bundle is completely unaffected.

Types

Every type this package's own API surface uses is importable from either subpath — no separate @klappay/types import needed just to type a payload or an option:

import type { CheckoutPayload, PaymentOption } from '@klappay/checkout-kit/node' // or /client

// re-exported straight from @klappay/types, for typing fields of the above:
import type {
  AcceptedPayment,
  Charge,
  ChargeStatus,
  Environment,
  Network,
  SettlementStatus,
  Token,
} from '@klappay/checkout-kit/node' // or /client

CheckoutPayload/PaymentOption are this package's own types — the curated shape toCheckoutPayload()/resolvePaymentOptions() produce (see Node or Client for every field). Charge is the full raw type from @klappay/types, useful if you called checkout.getCharge() directly instead of getCheckoutPayload(). @klappay/types itself is still worth installing directly (pnpm add @klappay/types) if you need anything outside this package's own surface — request/response types for @klappay/node's other resources, Zod schemas, etc.

What this doesn't do

  • Doesn't render any UI — pick your own framework, your own styling, your own QR-rendering library (e.g. the qrcode npm package renders an SVG/canvas from any string, including buildPaymentUri()'s output).
  • Doesn't proxy Core's SSE stream for you — that must go through your own backend (the API key can't reach a browser), watchCheckout() is the server-side half of that relay.
  • Doesn't add a public/unauthenticated charge-read surface — you need your own backend with your own Klappay API key, same as any @klappay/node integration.
  • Doesn't ship a WalletConnect modal/QR-renderer — client/walletconnect gives you the raw pairing URI via a 'uri' event, same "bring your own" stance as buildPaymentUri().
  • Doesn't classify wallet errors for you — error.code === 4001 on the 'error' event means the payer rejected the transaction; anything else is provider-specific.
  • Doesn't set up a webhook endpoint for you — Core also supports signed, server-to-server webhooks (charge.confirmed, charge.expired, etc.) as an alternative/complement to polling getCharge()/watchCheckout(). verifyWebhookSignature()/constructWebhookEvent() (re-exported from @klappay/node, node subpath) validate the X-Klappay-Signature header on whatever route you wire up to receive them.

License

MIT — see LICENSE.