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

@paysmith/framework-nextjs

v0.0.4

Published

Next.js App Router templates for the Paysmith $1 unlock demo — installed by the Paysmith CLI, unlocked only by verified signed events, removable via a hash manifest.

Downloads

723

Readme

@paysmith/framework-nextjs

The Next.js App Router templates for Paysmith's fixed $1 unlock demo, plus the manifest shapes used to install and later cleanly remove them. This package is mostly consumed indirectly, through paysmith init in @paysmith/cli — most users will never import it directly. It is documented here for anyone building their own installer on top of it, or who wants to read the generated source before running the CLI.

The demo it generates funds a sandbox with a deterministic $100.00 fake balance, expires 72 hours after activation, and unlocks its one entitlement only after its own server verifies a signed Paysmith event — never from a checkout response.

Those signed events reach the app two ways, and the difference is transport only. Paysmith pushes them to app/api/paysmith/webhook/route.ts, and — because a control plane that is not on your machine cannot POST to an app on localhost — the app also pulls the same envelopes from the event feed while the demo page polls. Both run the identical verify → deduplicate → grant pipeline in lib/paysmith/process-event.ts, against the same pinned key, and processing is deduplicated by event_id, so an event that arrives both ways unlocks exactly once.

Install

pnpm add @paysmith/framework-nextjs
# or
npm i @paysmith/framework-nextjs

What it installs

Eleven files, all newly created — this manifest never edits an existing file:

| Target path | Purpose | | --- | --- | | app/paysmith-demo/page.tsx | The demo page: Pay $1 to unlock, plus a scenario picker for declined/delayed/duplicate-webhook/refund-after-success. | | app/paysmith-demo/paysmith-demo.module.css | Its styles. | | app/api/paysmith/checkout/route.ts | Creates a sandbox payment intent for the fixed $1 price. Never reports success itself. | | app/api/paysmith/checkout/confirm/route.ts | Forwards the developer's chosen sandbox outcome scenario. Also never reports success itself. | | app/api/paysmith/webhook/route.ts | Push transport: reads the raw delivery bytes and hands them to the pipeline, then maps the outcome onto the status codes Paysmith's retry logic expects. | | app/api/paysmith/entitlement/route.ts | Reads the local entitlement projection for the current browser session — after a best-effort pull of any signed events the app has not seen. | | app/api/paysmith/receipt/route.ts | Returns the receipt earned by the current session's entitlement, never an arbitrary id. | | lib/paysmith/server.ts | The singleton @paysmith/sdk/server client, plus the pinned public_key_id every event is verified against. | | lib/paysmith/process-event.ts | The only code that grants or revokes demo.premium: verify against the pinned key → deduplicate by event_id → project. Shared by both transports. | | lib/paysmith/entitlements.ts | File-backed demo storage (below). | | .env.example | Documents the four environment variables the templates read. |

The file-backed demo entitlement store

lib/paysmith/entitlements.ts persists everything the demo needs to run without a database into a single JSON file, .paysmith/entitlements.json:

  • the entitlement projection (locked / unlocked / revoked) that a verified event grants or revokes — never anything a client claims;
  • the ids of events already processed, adapting to @paysmith/webhook's EventDeduperStore via createEntitlementsDeduperStore(), so a re-delivered — or re-pulled — event can never grant or revoke twice;
  • the sandbox's signing public key, fetched once and cached;
  • the pull cursor (lastPulledEventId), so a restarted dev server does not re-fetch every event the sandbox ever emitted. It is a transport cursor only: it advances just past events that were actually processed, and losing it costs redundant work rather than a wrong entitlement.

Writes go to a sibling temp file and are published with a single rename, and every read-modify- write is serialized through an in-process queue, so concurrent deliveries (the duplicate_webhook demo scenario delivers the same event twice on purpose, and a pull can race a push) can't race each other. This store is intentionally minimal — a real integration should replace it with real persistence; the sandbox itself, not this file, remains the source of truth for what actually happened.

Environment variables (.env.example)

| Variable | Required | Purpose | | --- | --- | --- | | PAYSMITH_API_BASE | yes | Base URL of the Paysmith sandbox API — a loopback origin when you run the control plane yourself, or a remote https endpoint when you use a hosted one (the app only ever calls out to it, so pull mode works from localhost). Must be https unless loopback. | | PAYSMITH_SANDBOX_TOKEN | yes | The scoped token issued for the sandbox. | | PAYSMITH_PUBLIC_KEY_ID | yes | The public_key_id this sandbox was activated with. Signature verification is pinned to it, on both transports. | | PAYSMITH_WEBHOOK_URL | no | Loopback override. The sandbox only delivers webhooks to loopback origins; set this only when your app is hosted on a public domain while sharing a machine with the control plane (e.g. http://127.0.0.1:3000/api/paysmith/webhook). Leave unset for local dev, and for a remote control plane — that one cannot reach your app at all, which is what pull mode is for. |

All four are server-only: never prefix any of them with NEXT_PUBLIC_.

Quick start (programmatic use)

import {
  TEMPLATE_MANIFEST,
  TEMPLATE_RUNTIME_DEPENDENCIES,
  buildGeneratedManifest,
} from "@paysmith/framework-nextjs";

TEMPLATE_MANIFEST[0];
// { sourcePath: "templates/app/paysmith-demo/page.tsx", targetPath: "app/paysmith-demo/page.tsx", kind: "create" }

TEMPLATE_RUNTIME_DEPENDENCIES;
// ["@paysmith/contracts", "@paysmith/sdk", "@paysmith/webhook"]
// (next/react are deliberately excluded — a project this generator targets already has them.)

const manifest = buildGeneratedManifest([
  { path: "app/paysmith-demo/page.tsx", sha256: "sha256:6b2f...c91a" },
  // ...one entry per file actually written, hashed by the caller
]);
// { schema: "paysmith.generated/v1", created_at: "2026-08-08T12:00:00Z", files: [...] }

buildGeneratedManifest only shapes the paysmith.generated.json document — it never touches the filesystem or hashes anything itself. The caller (the CLI) writes the files, computes each sha256:<hex> digest, and passes the results in. That manifest is what makes paysmith remove possible: it deletes only files whose hash still matches what was generated, and preserves anything you've since edited.

API

  • TEMPLATE_MANIFEST: readonly TemplateManifestEntry[]{ sourcePath, targetPath, kind: "create" } for every file above; sourcePath is relative to this package's root, targetPath to the target app's root.
  • TEMPLATE_RUNTIME_DEPENDENCIES: readonly TemplateRuntimeDependency[] — the non-framework packages the generated files import by name (@paysmith/contracts, @paysmith/sdk, @paysmith/webhook).
  • buildGeneratedManifest(files: readonly GeneratedManifestFile[]): GeneratedManifest — wraps already-hashed files in the paysmith.generated/v1 envelope (GENERATED_MANIFEST_SCHEMA).
  • FRAMEWORK_NEXTJS_PACKAGE_NAME — the package's own name, as a literal.

How it fits

This package is the product and its surrounding UI, made concrete: a demo page that starts a payment intent, one verification pipeline that is the only place a signed event becomes an entitlement, and a receipt route that surfaces the resulting receipt. All five primitives, wired end to end, in files you can read.

License

MIT