npm package: @mrlectus/paystack
Description: An sdk for interacting with the paystack api
Author: mrlectus
Latest version: 0.1.24

# Paystack SDK

A fully typed, robust, and easy-to-use TypeScript SDK for interacting with the [Paystack API](https://paystack.com/docs/api/v1).

## Installation

```bash
npm install @mrlectus/paystack
# or
pnpm add @mrlectus/paystack
# or
yarn add @mrlectus/paystack
```

## Usage

### Initialization

You can initialize the `PaystackClient` in two ways:

**1. Using Environment Variables (Recommended)**

Set `PAYSTACK_SECRET` in your environment variables, then initialize without arguments.

```typescript
import { PaystackClient } from "@mrlectus/paystack";

const client = PaystackClient();
```

**2. Passing the Secret Key Directly**

```typescript
import { PaystackClient } from "@mrlectus/paystack";

const client = PaystackClient("YOUR_SECRET_KEY");
```

### Configuration

You can pass an optional configuration object to customize the client's behavior.

```typescript
const client = PaystackClient("YOUR_SECRET_KEY", {
  timeout: 20000, // Request timeout in milliseconds (default: 10000)
  debug: true,    // Enable debug logging for requests (default: false)
});
```

## Error Handling

The SDK throws `PaystackApiError` when the API returns a non-success status code (e.g., 4xx, 5xx) or when the API response indicates failure (`status: false`). You should wrap your API calls in a `try-catch` block to handle these errors gracefully.

```typescript
import { PaystackClient, PaystackApiError } from "@mrlectus/paystack";

const client = PaystackClient();

try {
  const response = await client.transactions.verify({ reference: "invalid-ref" });
  console.log(response.data);
} catch (error) {
  if (error instanceof PaystackApiError) {
    console.error("Paystack API Error:", error.message);
    console.error("Status Code:", error.statusCode); // e.g., 404
    console.error("Error Details:", error.toJSON()); // Full error response from Paystack
  } else {
    console.error("Unexpected Error:", error);
  }
}
```

## Features

The SDK covers the entire Paystack API surface area:

- **Transactions**: Initialize, verify, list, and manage payments.
- **Transaction Splits**: Split settlement for a transaction across multiple accounts.
- **Customers**: Create, validate, and manage customers.
- **Transfers**: Automate sending money to customers.
- **Transfers Control**: Manage settings of your transfers.
- **Transfer Recipients**: Manage beneficiaries for transfers.
- **Plans & Subscriptions**: Handle recurring billing and subscription plans.
- **Payment Pages**: Create and manage payment pages.
- **Payment Requests**: Manage payment requests and invoices.
- **Refunds**: Initiate and manage transaction refunds.
- **Disputes**: Handle transaction disputes.
- **Subaccounts**: Manage split payment accounts.
- **Settlements**: Insight into payouts.
- **Dedicated Virtual Accounts**: Manage virtual accounts for customers.
- **Apple Pay**: Manage Apple Pay domain registration.
- **Bulk Charges**: Process batched transactions.
- **Direct Debit**: Manage recurring debits.
- **Terminal & Virtual Terminal**: Manage POS and virtual terminal payments.
- **Verification**: Perform KYC/identity verification (BVN, Account Match, etc).
- **Miscellaneous**: List banks, countries, and states.
- **Webhooks**: Type-safe handling of webhook events using `WebhookEventData`.

## Examples

### Initialize a Transaction

```typescript
import { PaystackClient, PaystackApiError } from "@mrlectus/paystack";

const client = PaystackClient();

try {
  const response = await client.transactions.initialize({
    email: "customer@example.com",
    amount: "5000", // Amount in kobo
    channels: ["card", "bank"],
  });

  console.log("Authorization URL:", response.data.authorization_url);
} catch (error) {
  if (error instanceof PaystackApiError) {
    console.error("Initialization Failed:", error.message);
  }
}
```

### Create a Customer

```typescript
try {
  const customer = await client.customers.create({
    email: "new.user@example.com",
    first_name: "John",
    last_name: "Doe",
    phone: "+2348012345678",
  });

  console.log("Customer ID:", customer.data.id);
} catch (error) {
  if (error instanceof PaystackApiError) {
    console.error("Customer Creation Failed:", error.message);
  }
}
```

### Initiate a Transfer

```typescript
try {
  // 1. Create a recipient
  const recipient = await client.transferRecipients.create({
    type: "nuban",
    name: "Zombie Recipient",
    account_number: "0123456789",
    bank_code: "058",
    currency: "NGN",
  });

  // 2. Initiate transfer
  const transfer = await client.transfers.initiate({
    source: "balance",
    amount: 5000,
    recipient: recipient.data.recipient_code,
    reference: `ref-${Date.now()}`,
    reason: "Holiday Bonus",
  });

  console.log("Transfer status:", transfer.data.status);
} catch (error) {
  if (error instanceof PaystackApiError) {
    console.error("Transfer Failed:", error.message);
  }
}
```

### List Banks

```typescript
try {
  const banks = await client.miscellaneous.listBanks({
    country: "nigeria",
    use_cursor: true,
    perPage: 10,
  });

  console.log("Banks retrieved:", banks.data.length);
} catch (error) {
  console.error("Failed to list banks", error);
}
```

## Handling Webhooks

The SDK provides a discriminated union type `WebhookEventData` for all supported webhook events. This ensures robust type safety when processing events.

### Using `switch` Statement (Express.js Example)

```typescript
import express from "express";
import { WebhookEventData } from "@mrlectus/paystack";

const app = express();

app.post("/webhook", express.json(), (req, res) => {
  // 1. Verify the signature (recommended)
  // Paystack sends a HMAC SHA512 signature in the 'x-paystack-signature' header.
  // You should verify this signature using your secret key.
  const signature = req.headers["x-paystack-signature"] as string;
  
  // 2. Cast the body to WebhookEventData
  const event = req.body as WebhookEventData;

  switch (event.event) {
    case "charge.success":
      // TypeScript automatically narrows 'event.data' to the correct payload
      console.log("Payment Reference:", event.data.reference);
      break;
    case "transfer.success":
      // TypeScript knows this is a Transfer event
      console.log("Transfer Recipient:", event.data.recipient);
      break;
    default:
      console.log("Unhandled event:", event.event);
  }

  res.sendStatus(200);
});
```

### Using `ts-pattern`

If you prefer a functional approach with pattern matching, you can use [ts-pattern](https://github.com/gvergnaud/ts-pattern).

```typescript
import { match } from "ts-pattern";
import { WebhookEventData } from "@mrlectus/paystack";

// ... inside your express route handler
const event = req.body as WebhookEventData;

match(event)
  .with({ event: "charge.success" }, (evt) => {
    console.log("Success:", evt.data.amount);
  })
  .with({ event: "refund.processed" }, (evt) => {
    console.log("Refund:", evt.data.refund_reference);
  })
  .otherwise((evt) => {
    console.log("Ignored event:", evt.event);
  });
```

## Runtime Schema Validation

This SDK uses [Zod](https://zod.dev) for strict runtime schema validation. Every API request is validated against its expected schema *before* it is sent to Paystack. This prevents malformed requests and ensures your application fails fast with clear error messages if the input data is invalid.

## TypeScript Support

The package is built with TypeScript and provides comprehensive type definitions for all API modules. This gives you:
- **Autocompletion**: Quickly discover available methods and payload properties.
- **Type Safety**: Catch potential bugs during development with strict typing for both requests and responses.
- **Improved DX**: Get inline documentation and hints directly in your IDE.

Together with runtime validation, this ensures a robust integration with the Paystack ecosystem.

## Performance & Tree Shaking

The SDK is designed to be lightweight and performant. It avoids barrel files internally to ensure fast load times and optimal tree-shaking when used in modern build pipelines.

## License

MIT