@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=trueThe 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_urlIf 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 immediatelyRefunds
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.
