Docs navigation

Tutorials

Quickstart

How-to guides

OverviewWebhooks

Reference

API

Explanation

Architecture

API Reference

createStash

createStash(config: StashConfig): {
  payments: { create(input: PaymentCreateInput): Promise<Payment> }
  webhooks: { parse(input: WebhookParseInput): ParsedWebhook }
}

Creates a configured Stash client with capability surfaces for payments and webhooks.

Test mode

testMode: true routes requests to provider sandbox endpoints. Set it to false for live production traffic.

Structured logging

Stash can emit canonical, structured log events for each payment action. Provide a logger when you create the client:

const stash = createStash({
  provider: "payfast",
  credentials: {
    merchantId: process.env.PAYFAST_MERCHANT_ID,
    merchantKey: process.env.PAYFAST_MERCHANT_KEY,
  },
  testMode: true,
  logger: {
    log: (event) => {
      // Send to your logging pipeline
      console.log(event);
    },
  },
});

Each event includes a correlation_id, event name (for example, payments.create.request), and safe metadata such as amount, currency, and reference. Stash never logs credentials or raw webhook payloads.

payments.create

payments.create(input: PaymentCreateInput): Promise<Payment>

Creates a payment using the configured provider and returns a canonical Payment.

providerOptions mapping

Payfast

  • paymentMethodpayment_method
  • emailConfirmationemail_confirmation
  • confirmationAddressconfirmation_address
  • mPaymentIdm_payment_id
  • itemNameitem_name
  • itemDescriptionitem_description

Ozow

  • selectedBankIdSelectedBankId
  • customerIdentityNumberCustomerIdentityNumber
  • allowVariableAmountAllowVariableAmount
  • variableAmountMinVariableAmountMin
  • variableAmountMaxVariableAmountMax

Paystack

  • channelschannels

Amounts

Amounts are treated as major units by default (for example, "25.00"). For Paystack, Stash converts major units to minor units automatically. To send minor units explicitly, set amountUnit: "minor" in payments.create.

Currency values are normalized to uppercase and default to ZAR when omitted. Ozow and Payfast only accept ZAR.

Required fields

Paystack requires customer.email for payments.create.

webhooks.parse

webhooks.parse(input: WebhookParseInput): ParsedWebhook

Verifies and normalizes provider webhook payloads. Throws StashError with code: "invalid_signature" on verification failure.

payments.verify

payments.verify(input: PaymentVerifyInput): Promise<VerificationResult>

Verifies payment status by reference. Supported providers:

  • Ozow ✅
  • Paystack ✅
  • Payfast ❌ (unsupported_capability)

subscriptions.plans.create

subscriptions.plans.create(input: SubscriptionPlanCreateInput): Promise<SubscriptionPlan>

Creates a subscription plan for the configured provider.

Supported providers:

  • Paystack ✅

SubscriptionPlanCreateInput

type SubscriptionPlanCreateInput = {
  name: string
  interval: "hourly" | "daily" | "weekly" | "monthly" | "quarterly" | "biannually" | "annually"
  amount: string | number
  amountUnit?: "major" | "minor"
  currency?: string
  invoiceLimit?: number
  provider?: "paystack"
}

subscriptions.create

subscriptions.create(input: SubscriptionCreateInput): Promise<Subscription>

Creates a subscription for an existing customer and plan.

Supported providers:

  • Paystack ✅

SubscriptionCreateInput

type SubscriptionCreateInput = {
  customer: string
  plan: string
  authorization?: string
  startDate?: string
  provider?: "paystack"
}

providerCapabilities

providerCapabilities: Record<PaymentProvider, ProviderCapabilities>

Static metadata describing provider capabilities for validation and docs.

Provider Currencies Required fields payments.verify
Ozow ZAR -
Payfast ZAR -
Paystack Any customer.email

VerificationResult

type VerificationResult = {
  provider: "ozow" | "payfast" | "paystack"
  status: "pending" | "paid" | "failed" | "unknown"
  providerRef?: string
  correlationId?: string
  raw?: unknown
}

Payment (canonical)

type Payment = {
  id: string
  status: "pending" | "paid" | "failed"
  amount: number
  currency: string
  redirectUrl?: string
  provider: "ozow" | "payfast" | "paystack"
  providerRef?: string
  correlationId?: string
  raw?: unknown
}

WebhookEvent (canonical)

type WebhookEvent = {
  type: "payment.completed" | "payment.failed" | "payment.cancelled"
  data: {
    id?: string
    providerRef?: string
    reference: string
    amount?: number
    currency?: string
    provider: "ozow" | "payfast" | "paystack"
    raw: unknown
  }
}

SubscriptionWebhookEvent (canonical)

type SubscriptionWebhookEvent = {
  type:
    | "subscription.created"
    | "subscription.disabled"
    | "subscription.not_renewing"
    | "invoice.created"
    | "invoice.updated"
    | "invoice.payment_failed"
  data: {
    provider: "paystack"
    subscriptionCode?: string
    customerCode?: string
    planCode?: string
    invoiceCode?: string
    status?: string
    amount?: number
    currency?: string
    raw: unknown
  }
}

ParsedWebhook

type ParsedWebhook = {
  event: WebhookEvent | SubscriptionWebhookEvent
  provider: "ozow" | "payfast" | "paystack"
  correlationId?: string
  raw: Record<string, unknown>
}

makePayment (deprecated)

makePayment(input: PaymentRequest): Promise<PaymentResponse>

Creates a payment request for Ozow or Payfast using a unified payload.

Deprecated: use createStash().payments.create.

verifyWebhookSignature (deprecated)

verifyWebhookSignature(input: WebhookVerifyInput): WebhookVerifyResult

Verifies the provider webhook signature for Ozow or Payfast.

Deprecated: use createStash().webhooks.parse.

Webhook utilities

buildFormEncoded(payload: Record<string, string | number | boolean | null | undefined>): string
parseFormBody(rawBody: string | Buffer): [string, string][]
parseFormEncoded(raw: string): [string, string][]
pairsToRecord(pairs: [string, string][]): Record<string, string>

Stash unifies Ozow, Payfast, and Paystack for South African payments.

Docs stay in the repo for GitHub-first browsing.