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

stripe-simplifier

v0.4.1

Published

A developer-friendly SDK that simplifies common Stripe integration tasks.

Readme

stripe-simplifier

npm version license node

Add Stripe checkout to your Next.js app without becoming a Stripe expert.

stripe-simplifier is a thin layer over the official stripe SDK. It doesn't replace Stripe or reproduce its API. It collapses the handful of things every Checkout integration repeats (session shape, cents conversion, webhook signature verification, error triage) into four small functions.

Application
     |
     v
stripe-simplifier
     |
     v
Stripe

Table of contents

Status

Published on npm as [email protected], including Checkout, multi-product Cart, PaymentIntent, refunds, expanded webhooks, and a stripe-simplifier init CLI that installs a starter into an existing Next.js project. The full flow has been exercised end-to-end against a real Stripe test account (session creation, hosted payment, PaymentIntent confirmation, refunds, redirect confirmation, and webhook callbacks all firing correctly), and a fresh npm install from the public registry has been verified to work. See demo/ for the working Next.js app all of this was verified in, or Starter kit below for the CLI.

Install

npm install stripe-simplifier stripe

stripe is a peerDependency you already control the version of, and stripe-simplifier re-exports its types rather than hiding them.

Requires Node.js >= 20. The SDK uses the global crypto.randomUUID() (Web Crypto), only guaranteed available without flags from Node 19+; Node 20 (the current LTS) is the declared and tested minimum.

Quick start

This is the full golden path, matching what's actually running in demo/.

lib/payments.ts

import { createPayments } from "stripe-simplifier";

export const payments = createPayments({
  secretKey: process.env.STRIPE_SECRET_KEY!,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
});

app/api/checkout/route.ts

import { payments } from "@/lib/payments";

export async function POST() {
  const session = await payments.checkout.create({
    amount: 20,
    currency: "usd",
    successUrl: "https://app.example.com/success",
    cancelUrl: "https://app.example.com/cancel",
  });

  return Response.json({ url: session.url });
}

app/api/webhook/route.ts

import { payments } from "@/lib/payments";

export const POST = payments.webhooks.handler({
  onPaymentSucceeded: async (event) => {
    // your fulfillment logic, not the SDK's job
    await markOrderPaid(event.session.id);
  },
});

API reference

createPayments(config: {
  secretKey: string;
  webhookSecret?: string; // required only if you use webhooks.handler()
}): Payments

One call, one config object. Throws ConfigurationError immediately if secretKey is missing, before any request is ever made, not mid-checkout.

payments.checkout.create(options: {
  amount: number;        // major unit, e.g. dollars, not cents
  currency: string;       // ISO code, e.g. "usd"
  successUrl: string;
  cancelUrl: string;
  productName?: string;  // shown on Stripe's page, defaults to "Payment"
} | {
  items: Array<{
    name: string;
    unitAmount: number;   // major unit, per item
    quantity?: number;     // defaults to 1
  }>;
  currency: string;         // one currency for the whole session, Stripe requires it
  successUrl: string;
  cancelUrl: string;
}): Promise<{ id: string; url: string }>

Converts every amount to the integer cents Stripe expects internally, and builds the line_items/price_data shape for you. Pass amount for a single flat charge, or items for a real multi-product cart, each item becomes its own Stripe line item with its own quantity. Mixing amount and items in the same call isn't a valid shape, TypeScript rejects it at compile time.

payments.checkout.retrieve(sessionId: string): Promise<{
  id: string;
  status: "paid" | "unpaid" | "no_payment_required";
  raw: Stripe.Checkout.Session;
}>
payments.paymentIntents.create(options: {
  amount: number;              // major unit, e.g. dollars, not cents
  currency: string;
  orderId?: string;             // becomes the idempotency key basis and metadata.orderId
  idempotencyKey?: string;      // explicit override
  metadata?: Record<string, string>;
}): Promise<{
  id: string;
  clientSecret: string | null;
  status: "pending" | "processing" | "succeeded" | "failed" | "canceled";
  stripeStatus: Stripe.PaymentIntent.Status; // the exact original status, never collapsed away
  amount: number;
  currency: string;
  metadata: Record<string, string>;
  raw: Stripe.PaymentIntent;
}>

payments.paymentIntents.retrieve(paymentIntentId: string): Promise<PaymentIntentResult>

For the browser side (<PaymentElement />, stripe.confirmPayment()), use @stripe/react-stripe-js directly, this SDK doesn't wrap it. See demo/app/pay/page.tsx and demo/components/CheckoutForm.tsx for the full working example.

payments.refunds.create(options: {
  paymentIntentId: string;
  amount?: number;   // major unit; omit for a full refund, provide for a partial one
}): Promise<{ id: string; status: string | null; amount: number; raw: Stripe.Refund }>

One function for both full and partial refunds. Idempotency is handled for you, derived from paymentIntentId + amount, so retrying the same refund request never double-refunds.

payments.webhooks.handler(handlers: {
  onPaymentSucceeded?: (event: CheckoutWebhookEvent | PaymentWebhookEvent) => Promise<void> | void;
  onPaymentFailed?: (event: CheckoutWebhookEvent | PaymentWebhookEvent) => Promise<void> | void;
  onRefundCreated?: (event: RefundWebhookEvent) => Promise<void> | void;
  onDisputeCreated?: (event: DisputeWebhookEvent) => Promise<void> | void;
  onUnhandledEvent?: (event: Stripe.Event) => Promise<void> | void;
}): (req: Request) => Promise<Response>

Returns a plain (Request) => Promise<Response>. Drop it straight into a Next.js Route Handler. It reads the raw body, verifies the Stripe signature, and routes checkout.session.completed / checkout.session.async_payment_failed / payment_intent.succeeded / payment_intent.payment_failed / charge.refunded / charge.dispute.created to your callbacks. An invalid or missing signature returns 400 automatically, so you never write that check yourself.

onPaymentSucceeded/onPaymentFailed fire for both Checkout and PaymentIntent events, discriminate with "session" in event:

onPaymentSucceeded: async (event) => {
  if ("session" in event) {
    // a Checkout Session completed
  } else {
    // a PaymentIntent succeeded
  }
}

No automatic deduplication. Stripe can deliver the same event more than once. This SDK does not dedupe for you: that would mean owning storage, which is more than a checkout helper should take on. Every event exposes event.raw.id so your app can dedupe if it needs to.

Thrown by checkout.create(), checkout.retrieve(), paymentIntents.create(), paymentIntents.retrieve(), refunds.create(), and webhooks.handler():

| Class | Thrown when | |---|---| | ConfigurationError | Missing/invalid secretKey or webhookSecret, or Stripe rejects your API key | | ValidationError | Bad input: non-positive amount, missing currency, missing paymentIntentId, or Stripe rejects the request shape | | WebhookError | Signature verification fails inside webhooks.handler() | | PaymentError | A wrapped call receives a card decline from Stripe |

Every error keeps the original Stripe error on .raw. Nothing is discarded, only re-labeled into something you can branch on without knowing Stripe's internal error taxonomy.

Note on PaymentError: card declines happen at confirmation time (stripe.confirmPayment() in the browser), and confirmation isn't wrapped by this SDK (see paymentIntents.create() above), so in normal usage a decline surfaces as Stripe's own error client-side, not PaymentError. The class exists and is tested for when a wrapped call does receive one.

payments.raw: Stripe // the underlying official Stripe client

Nothing about your Stripe account, your API key, or Stripe's full API is hidden. Anything this SDK doesn't cover, payments.raw does.

Scope & limitations

Written down on purpose, not left for you to discover:

  • No wrapped <PaymentElement />. Card/Apple Pay/Google Pay/SEPA UI stays a direct @stripe/react-stripe-js integration you control, this SDK only wraps the backend (session/PaymentIntent creation, refunds, webhooks).
  • No webhook deduplication storage. See the note under webhooks.handler() above.
  • Next.js App Router only. The webhook handler assumes the Fetch API Request/Response shape.
  • One-time payments only. No subscriptions, invoicing, or tax handling.
  • No paymentIntents.confirm() wrapper. Confirmation is designed to stay client-side (Stripe.js) or, if you need it server-side, via payments.raw.
  • paymentIntents.create() always uses automatic_payment_methods: { enabled: true }. Not configurable: no way to disable redirect-based methods or opt into capture_method: "manual" (pre-authorization) yet. A deliberate decision to keep the surface small, not an oversight.
  • refunds.create() accepts a paymentIntentId only, not a chargeId, even though Stripe's own API accepts either. This SDK is built around PaymentIntents throughout; add a chargeId path only if you have a real need Stripe's paymentIntentId-based refund can't cover.

Starter kit

npx create-next-app@latest my-app   # if you don't have a Next.js project yet
cd my-app
npx stripe-simplifier init

Installs a ready-to-customize Next.js starter (Checkout, multi-product Cart, PaymentIntent + PaymentElement, Refunds) into your project, and installs its dependencies automatically (detects npm/yarn/pnpm/bun from your lockfile, --skip-install to do it yourself). It never creates a Next.js project itself, and it never overwrites an existing file without asking first (--dry-run to preview, --yes to skip prompts). Source lives in starter/, see starter/README.md.

Demo

A working Next.js app using this exact API also lives in demo/: Checkout (/checkout), the PaymentIntent + PaymentElement flow (/pay), and a webhook handler covering both, all verified against a real Stripe test account. demo/ stays the minimal technical reference; starter/ above is the polished, ready-to-customize version, both call the same API. See demo/README.md for setup.

Design documents

How this scope was decided, for anyone who wants the reasoning, not just the API:

Development approach

Discovery              done
    |
    v
MVP Definition          done
    |
    v
API Design               done
    |
    v
Architecture               done
    |
    v
Implementation               done
    |
    v
Demo Application               done
    |
    v
Testing                          done (unit tests + verified live demo)
    |
    v
Documentation                     this file
    |
    v
Publication                          done (npm + GitHub) for v0.1.1
    |
    v
v-next (PaymentIntent/refunds/webhooks)   done, published as v0.2.0

Product principle

The SDK should not simply be a wrapper around the official Stripe SDK. Every abstraction should provide a clear benefit to the developer. The main question is:

What can this SDK make significantly easier than integrating Stripe directly?

License

MIT