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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@alexasomba/better-auth-paystack

v0.1.1

Published

Community Better Auth plugin for Paystack (by alexasomba)

Readme

Better Auth Paystack Plugin

Better Auth plugin that integrates Paystack for customer creation, checkout, and Paystack-native subscription flows.

Features

  • Optional Paystack customer creation on sign up (createCustomerOnSignUp)
  • Paystack checkout via transaction initialize + verify (redirect-first)
  • Paystack webhook signature verification (x-paystack-signature, HMAC-SHA512)
  • Local subscription records stored in your Better Auth database
  • Subscription management endpoints using Paystack’s email-token flows (/subscription/enable + /subscription/disable)
  • Reference ID support (user by default; org/team via referenceId + authorizeReference)

Installation

Install packages

npm install better-auth @alexasomba/better-auth-paystack

Install from GitHub Packages (optional)

If you want to install this package from GitHub Packages (npm.pkg.github.com) instead of npmjs, configure a project-level .npmrc (or your user ~/.npmrc) to route the @alexasomba scope:

@alexasomba:registry=https://npm.pkg.github.com

Then authenticate and install:

# npm v9+ may require legacy auth prompts for private registries
npm login --scope=@alexasomba --auth-type=legacy --registry=https://npm.pkg.github.com

npm install @alexasomba/better-auth-paystack

Development (pnpm workspace)

This repo is set up as a pnpm workspace so you can install once at the repo root and run/build any example via --filter.

pnpm install

Build the library:

pnpm --filter "@alexasomba/better-auth-paystack" build

Run an example:

# Cloudflare Workers + Hono
pnpm --filter hono dev

# Next.js (OpenNext / Cloudflare)
pnpm --filter my-next-app dev

# TanStack Start
pnpm --filter tanstack-start dev

Build all workspace packages (library + examples):

pnpm -r build

If you want strict typing and the recommended server SDK client:

npm install @alexasomba/paystack-node

If your app has separate client + server bundles, install the plugin in both.

Configure the server plugin

import { betterAuth } from "better-auth";
import { paystack } from "@alexasomba/better-auth-paystack";
import { createPaystack } from "@alexasomba/paystack-node";

const paystackClient = createPaystack({
  secretKey: process.env.PAYSTACK_SECRET_KEY!,
});

export const auth = betterAuth({
  plugins: [
    paystack({
      paystackClient,
      // Paystack signs webhooks with an HMAC SHA-512 using your Paystack secret key.
      // Use the same secret key you configured in `createPaystack({ secretKey })`.
      paystackWebhookSecret: process.env.PAYSTACK_SECRET_KEY!,
      createCustomerOnSignUp: true,
      subscription: {
        enabled: true,
        plans: [
          {
            name: "starter",
            amount: 500000,
            currency: "NGN",
            // If you use Paystack Plans, prefer planCode + (optional) invoiceLimit.
            // planCode: "PLN_...",
            // invoiceLimit: 12,
          },
        ],
        authorizeReference: async ({ user, referenceId, action }, ctx) => {
          // Allow only the current user by default; authorize org/team IDs here.
          // return await canUserManageOrg(user.id, referenceId)
          return referenceId === user.id;
        },
      },
    }),
  ],
});

Configure the client plugin

import { createAuthClient } from "better-auth/client";
import { paystackClient } from "@alexasomba/better-auth-paystack/client";

export const client = createAuthClient({
  plugins: [paystackClient({ subscription: true })],
});

Migrate / generate schema

The plugin adds fields/tables to your Better Auth database. Run the Better Auth CLI migration/generate step you already use in your project.

Webhooks

Endpoint URL

The plugin exposes a webhook endpoint at:

{AUTH_BASE}/paystack/webhook

Where {AUTH_BASE} is your Better Auth server base path (commonly /api/auth).

Signature verification

Paystack sends x-paystack-signature which is an HMAC-SHA512 of the raw payload signed with your secret key. The plugin verifies this using paystackWebhookSecret.

Recommended events

At minimum, enable the events your app depends on. For subscription flows, Paystack documents these as relevant:

  • charge.success
  • subscription.create
  • subscription.disable
  • subscription.not_renew

The plugin forwards all webhook payloads to onEvent (if provided) after signature verification.

Usage

Defining plans

Plans are referenced by their name (stored lowercased). For Paystack-native subscriptions you can either:

  • Use planCode (Paystack plan code). When planCode is provided, Paystack invalidates amount during transaction initialization.
  • Or use amount (smallest currency unit) for simple payments.

Frontend checkout (redirect)

This flow matches Paystack’s transaction initialize/verify APIs:

  1. Call POST {AUTH_BASE}/paystack/transaction/initialize
  2. Redirect the user to the returned Paystack url
  3. On your callback route/page, call POST {AUTH_BASE}/paystack/transaction/verify (this updates local subscription state)

Example (typed via Better Auth client plugin):

import { createAuthClient } from "better-auth/client";
import { paystackClient } from "@alexasomba/better-auth-paystack/client";

const plugins = [paystackClient({ subscription: true })];

const authClient = createAuthClient({
  // Your Better Auth base URL (commonly "/api/auth" in Next.js)
  baseURL: "/api/auth",
  plugins,
});

// Start checkout
const init = await authClient.paystack.transaction.initialize(
  {
    plan: "starter",
    callbackURL: `${window.location.origin}/billing/paystack/callback`,
    // Optional for org/team billing (requires authorizeReference)
    // referenceId: "org_123",
  },
  { throw: true },
);
// { url, reference, accessCode, redirect: true }
if (init?.url) window.location.href = init.url;

// On your callback page/route
const reference = new URLSearchParams(window.location.search).get("reference");
if (reference) {
  await authClient.paystack.transaction.verify({ reference }, { throw: true });
}

Server-side (no HTTP fetch needed):

// On the server you can call the endpoints directly:
// const init = await auth.api.initializeTransaction({ headers: req.headers, body: { plan: "starter" } })
// const verify = await auth.api.verifyTransaction({ headers: req.headers, body: { reference } })

Inline modal checkout (optional)

If you prefer an inline checkout experience, initialize the transaction the same way and use @alexasomba/paystack-browser in your UI. This plugin does not render UI — it only provides server endpoints.

Listing local subscriptions

List subscription rows stored by this plugin:

GET {AUTH_BASE}/paystack/subscription/list-local

You can optionally pass referenceId as a query param (requires authorizeReference when it’s not the current user):

GET {AUTH_BASE}/paystack/subscription/list-local?referenceId=org_123

Enabling / disabling a subscription

Paystack requires both the subscription code and the email token.

For convenience, the plugin lets you omit emailToken and will attempt to fetch it from Paystack using the subscription code (via Subscription fetch, with a fallback to Manage Link).

  • POST {AUTH_BASE}/paystack/subscription/enable with { subscriptionCode, emailToken? }
  • POST {AUTH_BASE}/paystack/subscription/disable with { subscriptionCode, emailToken? }

Paystack documents these as code + token. If the server cannot fetch emailToken, you can still provide it explicitly (e.g., from the Subscription API or your Paystack dashboard).

Schema

The plugin adds the following to your Better Auth database schema.

user

| Field | Type | Required | Default | | ---------------------- | -------- | -------- | ------- | | paystackCustomerCode | string | no | — |

subscription (only when subscription.enabled: true)

| Field | Type | Required | Default | | ------------------------------ | --------- | -------- | -------------- | | plan | string | yes | — | | referenceId | string | yes | — | | paystackCustomerCode | string | no | — | | paystackSubscriptionCode | string | no | — | | paystackTransactionReference | string | no | — | | status | string | no | "incomplete" | | periodStart | date | no | — | | periodEnd | date | no | — | | trialStart | date | no | — | | trialEnd | date | no | — | | cancelAtPeriodEnd | boolean | no | false | | groupId | string | no | — | | seats | number | no | — |

Options

Main options:

  • paystackClient (recommended: createPaystack({ secretKey }))
  • paystackWebhookSecret
  • createCustomerOnSignUp?
  • onCustomerCreate?, getCustomerCreateParams?
  • onEvent?
  • schema? (override/mapping)

Subscription options (when subscription.enabled: true):

  • plans (array or async function)
  • requireEmailVerification?
  • authorizeReference?
  • onSubscriptionComplete?, onSubscriptionUpdate?, onSubscriptionDelete?

Troubleshooting

  • Webhook signature mismatch: ensure your server receives the raw body, and PAYSTACK_WEBHOOK_SECRET matches the secret key used by Paystack to sign events.
  • Subscription list returns empty: verify you’re passing the correct referenceId, and that authorizeReference allows it.
  • Transaction initializes but verify doesn’t update: ensure you call the verify endpoint after redirect, and confirm Paystack returns status: "success" for the reference.

Links

  • Paystack Webhooks: https://paystack.com/docs/payments/webhooks/
  • Paystack Transaction API: https://paystack.com/docs/api/transaction/
  • Paystack Subscription API: https://paystack.com/docs/api/subscription/
  • Paystack Plan API: https://paystack.com/docs/api/plan/