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

@ingram-tech/nk-billing

v0.3.0

Published

The Ingram billing foundation: composable Stripe primitives (client, customers, prices, currency, checkout, subscriptions, webhooks) plus an injection-based Postgres credit ledger and event-dedup store.

Readme

@ingram-tech/nk-billing

The Ingram billing foundation: composable Stripe primitives every site was re-implementing, plus an optional Postgres credit ledger for usage metering. One account-agnostic toolkit, not a billing framework that owns your flows — you keep your routes, your schema, and your pricing; nk-billing removes the boilerplate in between.

Part of nextkit. Stateless Stripe helpers are the main entry; the database-backed credit ledger lives behind the /credits subpath so a subscription-only or wallet-only site never imports a DB concept.

Install

bun add @ingram-tech/nk-billing stripe

stripe is a direct dependency; bring your own Postgres (pg / nk-db) only if you use the credit ledger.

Env contract

Single-mode (most sites): STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET. Dual-mode (a merchant-of-record running test beside live, e.g. cloud): STRIPE_SECRET_KEY_{TEST,LIVE}, STRIPE_WEBHOOK_SECRET_{TEST,LIVE}. Optional STRIPE_API_VERSION to pin ahead of an SDK bump.

Nothing hardcodes a price ID — prices resolve at runtime by stable Stripe lookup_key, so the same code path works in test and live.

The three billing models

Pick the one that matches what you sell — they compose:

  • SubscriptionscreateCheckoutSession({ mode: "subscription" }), createPortalSession, summarizeSubscription, fetchSubscriptionSummary (the webhook-lag self-heal read).
  • Stripe-side wallet (money) — @ingram-tech/nk-billing readBalance / debitBalance: the balance lives in Stripe, no local table.
  • In-app credits (abstract units) — @ingram-tech/nk-billing/credits: atomic spendCredits / requireCredits, grantCredits, recordSubscriptionStatus, all idempotent; ships its own migration.

Quick start — subscription checkout

import { createCheckoutSession, resolveCurrencyFromHeaders } from "@ingram-tech/nk-billing";
import { headers } from "next/headers";

const url = await createCheckoutSession({
	customer: { metadataKey: "acme_org_id", id: orgId },
	customerDetails: { name: org.name, email: user.email },
	lookupKey: "acme_pro_monthly",
	mode: "subscription",
	currency: resolveCurrencyFromHeaders(await headers()),
	successUrl: `${base}/billing?ok=1`,
	cancelUrl: `${base}/billing`,
});
redirect(url);

Quick start — webhook (with credit-ledger dedup)

import { readStripeWebhook } from "@ingram-tech/nk-billing";
import { grantCredits, recordSubscriptionStatus } from "@ingram-tech/nk-billing/credits";

export async function POST(request: Request) {
	const res = await readStripeWebhook(request, process.env.STRIPE_WEBHOOK_SECRET ?? "");
	if (!res.ok) return new Response(res.message, { status: res.status });
	// dedupe + apply inside your tenant transaction; retries are no-ops.
	await withTenant(orgId, (db) => recordSubscriptionStatus(db, { ... }));
	return Response.json({ received: true });
}

Credit ledger & tenancy

The ledger takes the DB connection by injection and leaves isolation to you — wrap each call in withTenant(orgId, db => spendCredits(db, …)) under RLS, or a plain transaction under app-layer filtering. It owns two tables; apply the migrations/*.sql files in order (Drizzle-composable fragments) and add your own RLS policy if your stack uses one. See src/credits.ts.

Two semantics to know before wiring it up:

  • Entitlement gates the balance. spendCredits requires an active subscription or a live trial before it looks at credits: a one-time credit pack bought by a tenant with no subscription and an expired trial is not spendable. If your site sells packs standalone, gate pack checkout on entitlement — or pass activeStatuses semantics that match your model.
  • Webhook ordering. Pass event.created as eventCreated to recordSubscriptionStatus (and apply 0002_billing_status_order.sql) so a delayed, older customer.subscription.updated can't overwrite the status a later deleted event already recorded.

Full surface

The per-export reference is the JSDoc on each export — every entry point in src/index.ts and src/credits.ts is documented at the definition.

License

MIT © Ingram Technologies