> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usepaykit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Bachs

> Bachs provider for PayKit.

```bash theme={null}
npm install @paykit-sdk/core @paykit-sdk/bachs
```

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

## Setup

<Tabs>
  <Tab title="Environment variables">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { bachs } from '@paykit-sdk/bachs';

    export const paykit = new PayKit(bachs());
    ```

    Required env vars:

    ```bash theme={null}
    BACHS_API_KEY=sk_sandbox_...
    BACHS_SANDBOX=true
    ```

    The key's prefix decides sandbox vs live, and overrides `isSandbox` if the two disagree.
  </Tab>

  <Tab title="Direct config">
    ```typescript theme={null}
    import { PayKit } from '@paykit-sdk/core';
    import { createBachs } from '@paykit-sdk/bachs';

    export const paykit = new PayKit(
      createBachs({
        apiKey: 'sk_sandbox_...',
        isSandbox: true,
      }),
    );
    ```
  </Tab>
</Tabs>

## 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:

```typescript theme={null}
const checkout = await paykit.checkouts.create({
  customer: { email: 'jane@example.com' }, // 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.

## 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:

```typescript theme={null}
const payment = await paykit.payments.create({
  customer: { email: 'jane@example.com' },
  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`.

## 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:

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

`paykit.refunds.create` supports the same option.

## Customers

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

```typescript theme={null}
const customer = await paykit.customers.create({
  email: 'jane@example.com',
  name: 'Jane Doe',
  billing: null,
});

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

## 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:

```typescript theme={null}
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

```typescript theme={null}
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`:

```typescript theme={null}
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

Opt into any native Bachs event type, typed against Bachs' payload:

```typescript theme={null}
paykit.webhooks
  .setup({ webhookSecret: process.env.BACHS_WEBHOOK_SECRET! })
  .on('bachs.payout.paid', async event => {
    // event.data is the raw Bachs payload
  });
```

All available raw events and their PayKit mappings:

| Raw event                             | PayKit event            |
| ------------------------------------- | ----------------------- |
| `bachs.collection.succeeded`          | `payment.succeeded`     |
| `bachs.collection.failed`             | `payment.failed`        |
| `bachs.collection.abandoned`          | `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.payout.*`                      | *(raw only)*            |
| `bachs.invoice.*`                     | *(raw only)*            |
| `bachs.dispute.*`                     | *(raw only)*            |
| `bachs.conversion.*`                  | *(raw only)*            |
