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

@paykit-sdk/bachs

v2.0.4

Published

Bachs provider for PayKit

Readme

@paykit-sdk/bachs

Bachs provider for PayKit.

Bachs is a hosted-checkout-first payments and billing platform for African internet businesses selling globally (docs.bachs.io).

Quick Start

import { createEndpointHandlers, PayKit } from '@paykit-sdk/core';
import { bachs, createBachs } from '@paykit-sdk/bachs';

// Method 1: Using environment variables
const provider = bachs(); // Ensure required environment variables are set

// Method 2: Direct configuration
const provider = createBachs({
  apiKey: process.env.BACHS_API_KEY, // sk_sandbox_... or sk_live_...
  isSandbox: true,
  debug: true,
});

export const paykit = new PayKit(provider);
export const endpoints = createEndpointHandlers(paykit);

Required env vars for bachs():

BACHS_API_KEY=sk_sandbox_...
BACHS_SANDBOX=true

The key's prefix decides sandbox vs live, and overrides isSandbox if the two disagree.

Creating a checkout

Products (and their prices) live in your Bachs product catalog. item_id is a Bachs product_id. Bachs resolves the amount/currency from the product itself, so you don't pass either:

const checkout = await paykit.checkouts.create({
  customer: { email: '[email protected]' }, // or { id: 'cust_...' } for an existing customer
  item_id: 'prod_abc123',
  quantity: 1,
  session_type: 'one_time',
  success_url: 'https://shop.example.com/thanks',
  cancel_url: 'https://shop.example.com/cart',
  metadata: { order_id: 'ORD-9876' },
});

// Redirect the customer to checkout.payment_url

If prod_abc123 has a billing_cycle configured on Bachs, completing this same checkout creates a subscription automatically. You'll get both a payment.succeeded and a subscription.created webhook.

Safe retries

paykit.checkouts.create and paykit.payments.create each make two calls under the hood: create the session, then fetch it back to resolve pricing. If your own code might retry the whole call, pass a stable idempotencyKey (your own order ID works well) so a retry returns the original session instead of creating a duplicate:

await paykit.checkouts.create({
  // ...
  provider_metadata: { idempotencyKey: `order-${order.id}` },
});

paykit.refunds.create supports the same option.

Creating a payment directly

Same underlying flow, mapped onto Payment. Needs success_url in provider_metadata since Bachs still redirects the customer even for a direct payment:

const payment = await paykit.payments.create({
  customer: { email: '[email protected]' },
  amount: 50, // informational only - Bachs resolves the real amount from the product
  currency: 'USD',
  item_id: 'prod_abc123',
  capture_method: 'automatic', // Bachs captures automatically, no manual step
  provider_metadata: {
    success_url: 'https://shop.example.com/thanks',
    cancel_url: 'https://shop.example.com/cart', // optional
  },
});

payment.id (and checkout.id) is Bachs' checkout_id for the lifetime of the payment. Keep using this same checkout_id for paykit.payments.retrieve and paykit.refunds.create.

Customers

Full support except delete, Bachs has no endpoint for it:

const customer = await paykit.customers.create({
  email: '[email protected]',
  name: 'Jane Doe',
  billing: null,
});

await paykit.customers.update(customer.id, { name: 'Jane D.' });
await paykit.customers.retrieve(customer.id);

Subscriptions

Bachs has no direct way to create a subscription. Create one by calling paykit.checkouts.create with a recurring-configured product instead. Retrieve, update, and cancel work directly:

const subscription =
  await paykit.subscriptions.retrieve('sub_1a2b3c4d5e');

await paykit.subscriptions.update('sub_1a2b3c4d5e', {
  metadata: {},
  provider_metadata: { product_id: 'prod_xyz456' }, // move to a different plan
});

await paykit.subscriptions.cancel('sub_1a2b3c4d5e'); // cancels immediately

Refunds

await paykit.refunds.create({
  payment_id: 'chk_1a2b3c4d5e', // the checkout_id
  amount: 29,
  reason: 'Customer request',
  metadata: null,
});

The payment must have actually succeeded. Refunding a checkout that hasn't been paid throws ResourceNotFoundError.

Webhooks

Add a webhook endpoint from your Bachs Developer Portal and pass its signing secret to webhookSecret:

const webhook = paykit.webhooks
  .setup({ webhookSecret: process.env.BACHS_WEBHOOK_SECRET! }) // whsec_...
  .on('payment.succeeded', async event => {})
  .on('payment.failed', async event => {})
  .on('payment.updated', async event => {})
  .on('subscription.created', async event => {})
  .on('subscription.updated', async event => {})
  .on('subscription.canceled', async event => {})
  .on('refund.created', async event => {})
  .on('customer.created', async event => {})
  .on('customer.updated', async event => {});

await webhook.handle({
  body: await request.text(),
  headersAsObject: Object.fromEntries(request.headers),
  fullUrl: request.url,
});

Raw Bachs events

paykit.webhooks
  .setup({ webhookSecret: process.env.BACHS_WEBHOOK_SECRET! })
  .on('bachs.payout.paid', async payout => {
    // payout is the raw Bachs payload, typed as BachsPayoutEventData
    console.log(payout.withdrawal_id, payout.status);
  });

A raw handler receives the Bachs payload itself, not a PayKit event wrapper. Every payload type is exported from @paykit-sdk/bachs, along with the rest of the Bachs API types generated from their OpenAPI spec.

All available raw events and their PayKit mappings:

| Raw event | PayKit event | | ------------------------------------- | ----------------------- | | bachs.collection.succeeded | payment.succeeded | | bachs.collection.failed | payment.failed | | bachs.collection.underpaid | payment.updated | | bachs.refund.created | refund.created | | bachs.refund.paid | refund.created | | bachs.refund.failed | refund.created | | bachs.customer.subscription.created | subscription.created | | bachs.customer.subscription.updated | subscription.updated | | bachs.customer.subscription.deleted | subscription.canceled | | bachs.customer.created | customer.created | | bachs.customer.updated | customer.updated | | bachs.checkout.* | (raw only) | | bachs.invoice.* | (raw only) | | bachs.payout.* | (raw only) | | bachs.dispute.* | (raw only) | | bachs.conversion.* | (raw only) | | bachs.account.updated | (raw only) | | bachs.capability.updated | (raw only) | | bachs.transfer.created | (raw only) |

Unsupported operations

paykit.checkouts.update, paykit.checkouts.delete, paykit.customers.delete, paykit.subscriptions.create, paykit.subscriptions.delete, paykit.payments.update, paykit.payments.delete, paykit.payments.capture, and paykit.payments.cancel all throw ProviderNotSupportedError. Bachs has no endpoints for any of them.