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

@kilnjs/payments

v0.1.1

Published

The payments contract for KilnJS: one shape for a purchase whoever took the money, with each provider as a package of its own

Readme

@kilnjs/payments

One shape for a purchase, whoever took the money. The contract and the registry live here; each provider is a package of its own, the way the storage drivers are.

yarn add @kilnjs/payments @kilnjs/payments-stripe
// app.config.ts
import { payments } from "@kilnjs/payments";
import "@kilnjs/payments-stripe";

export default {
  modules: [payments()],
} satisfies AppConfig;
// src/payments.ts
import type { PaymentDelegate } from "@kilnjs/payments";

export default {
  handle: async (event) => {
    if (event.kind === "purchased") await grant(event);
    if (event.kind === "refunded") await revoke(event);
  },
} satisfies PaymentDelegate;

Importing a provider package is what installs it. Declaring the module mounts one notification route per installed provider, verifies what arrives, and hands the delegate a normalised event.

| | | | --- | --- | | @kilnjs/payments-stripe | /payments/stripe | | @kilnjs/payments-play | /payments/play | | @kilnjs/payments-appstore | /payments/appstore |

Why normalise at all

Because the three disagree about everything. A renewal is invoice.payment_succeeded, SUBSCRIPTION_RENEWED as the integer 2, and DID_RENEW. A cancellation is a deleted subscription, a 3, and a type whose meaning depends on its subtype. An application that branches on those directly has the same logic three times, and the third copy is the one with the bug in it.

So a provider answers two questions in one vocabulary:

What just happened is a PaymentEvent: a kind out of nine - purchased, renewed, cancelled, expired, refunded, paused, recovered, billing_failed, other - with the identifier the provider uses, what was bought, who bought it as far as the provider knows, and the provider's own event kept whole in raw.

What is true now is an Entitlement, asked of the provider rather than worked out from events. That distinction is the one worth keeping: events are missed, delivered twice and delivered out of order, and the provider is the only thing that knows the current answer.

const held = await Payments.stripe.entitlement("sub_1");

if (held.state === "active") serve();

What this contract deliberately does not cover

Creating a checkout session, refunding, changing a plan, reading an invoice. Each of those is one provider's own surface, reached through its own package: a contract spanning all three would be the lowest common denominator of three unrelated businesses, and the first thing anybody needed would be underneath it.

Two decisions worth knowing about

A notification is answered only once the delegate is done. All three providers retry on a failure, which is what makes an acknowledgement sent before the work a quiet way to lose events. Raising inside handle is therefore how an application says "try me again"; returning is how it says "I have this, even if I did nothing with it".

The body is verified as it arrived. A signature covers bytes, so these routes keep the raw body while every other route in the application keeps the parsed one. Verifying against a re-serialised object fails for reasons nobody can debug: whitespace, key order and number formatting all differ.

Verifying

yarn verify   # ts:check, lint, build, exports:check, test

No provider is reached. What each provider's own package proves about verification is in its own suite, and those are the tests worth reading: every way a forged notification can be wrong has a case.