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

@7haven/havenpay

v0.2.3

Published

Official JavaScript SDK for HavenPay

Readme

HavenPay JavaScript SDK

Official JavaScript/TypeScript SDK for HavenPay - Type-safe payment processing for African markets.

npm version License: MIT

Features

  • Type-safe - Full TypeScript support with detailed types
  • Secure - Built-in request signing, response verification, and webhook validation (HMAC-SHA256, constant-time comparison)
  • Reliable - Automatic retry logic with exponential backoff (1s, 2s, 4s)
  • Modern - Uses native fetch() and Node.js crypto (zero dependencies, ES2020+)
  • Flexible - Both class-based and functional APIs
  • Production-ready - Follows industry best practices (DRY, KISS, single source of truth)
  • Well-tested - Comprehensive test coverage

Installation

npm install @7haven/havenpay
# or
yarn add @7haven/havenpay
# or
pnpm add @7haven/havenpay
# or
bun add @7haven/havenpay

Quick Start

Class-based API (Recommended)

import { HavenPay } from '@7haven/havenpay'

const havenpay = new HavenPay({
  apiKey: process.env.HAVENPAY_API_KEY!,
  signingSecret: process.env.HAVENPAY_SIGNING_SECRET!,
})

// Start a payment
const payment = await havenpay.payments.start({
  amount: 1000,
  currency: 'USD',
  providerId: 'omari', // or 'innbuck'
  cartId: 'cart-123',
  msisdn: '+263771234567',
})

console.log('Payment reference:', payment.reference)
console.log('OTP reference:', payment.otpReference)

// Verify OTP
const result = await havenpay.payments.verifyOtp(payment.reference, '1234')

if (result.status === 'SUCCESS') {
  console.log('Payment successful!')
}

Functional API

import { configure, start, verifyOtp } from '@7haven/havenpay'

// Configure once at app initialization
configure({
  apiKey: process.env.HAVENPAY_API_KEY!,
  signingSecret: process.env.HAVENPAY_SIGNING_SECRET!,
})

// Use anywhere in your app
const payment = await start({
  amount: 1000,
  currency: 'USD',
  providerId: 'omari',
  cartId: 'cart-123',
  msisdn: '+263771234567',
})

const _result = await verifyOtp(payment.reference, '1234')

Configuration

const _havenpay = new HavenPay({
  apiKey: 'your-api-key', // Required
  signingSecret: 'your-signing-secret-min-32-chars', // Required (min 32 chars, generate: openssl rand -base64 24)
  baseUrl: 'https://pay.7haven.online/api', // Optional (default: production API)
  timeout: 30000, // Optional (default: 30000ms = 30s)
  maxRetries: 3, // Optional (default: 3, exponential backoff: 1s, 2s, 4s)
  verifyResponses: true, // Optional (default: true, strongly recommended for security)
})

Generating Secrets:

# Generate signingSecret (32 chars minimum, 64+ recommended)
openssl rand -base64 24  # 32 chars
openssl rand -base64 48  # 64 chars (recommended)

API Reference

Payments

start(params)

Start a payment and send OTP to customer.

const _payment = await havenpay.payments.start({
  amount: 1000, // Amount in minor units (e.g., cents)
  currency: 'USD', // or 'ZWG'
  providerId: 'omari', // or 'innbuck'
  cartId: 'cart-123', // Your cart/order ID
  msisdn: '+263771234567', // Customer phone number
  webhookUrl: 'https://example.com/webhooks/havenpay', // Optional
})

Response:

{
  "reference": "pay_abc123",
  "otpReference": "otp_xyz789",
  "status": "AUTH_SENT",
  "message": "OTP sent to customer"
}

verifyOtp(reference, otp)

Verify customer OTP and complete payment.

const _result = await havenpay.payments.verifyOtp('pay_abc123', '1234')

Response:

{
  "reference": "pay_abc123",
  "status": "SUCCESS",
  "providerPaymentRef": "provider_ref_123",
  "message": "Payment completed successfully"
}

getStatus(reference)

Get current payment status (includes provider verification).

const _status = await havenpay.payments.getStatus('pay_abc123')

Response:

{
  "reference": "pay_abc123",
  "status": "SUCCESS",
  "amount": 1000,
  "currency": "USD",
  "providerId": "omari",
  "providerPaymentRef": "provider_ref_123",
  "verified": true,
  "verifiedAt": "2025-01-15T10:30:00Z",
  "verificationMethod": "query",
  "createdAt": "2025-01-15T10:25:00Z",
  "updatedAt": "2025-01-15T10:30:00Z"
}

getByCart(cartId)

Get latest payment for a cart (for recovery after page reload).

const _payment = await havenpay.payments.getByCart('cart-123')

if (_payment && _payment.status === 'AUTH_SENT') {
  // Show OTP input
}

cancel(reference)

Cancel/void a payment.

await havenpay.payments.cancel('pay_abc123')

resendOtp(reference)

Resend OTP for a payment.

await havenpay.payments.resendOtp('pay_abc123')

getProviders(currency?)

Get available payment providers.

const { providers } = await havenpay.payments.getProviders('USD')

providers.forEach((provider) => {
  console.log(`${provider.name} (${provider.id}) - ${provider.latencyMs}ms`)
})

Webhook Verification

📖 See PAYMENT_FLOW.md for complete webhook implementation guide

Verify webhooks sent from HavenPay to your backend.

import { HavenPay } from '@7haven/havenpay'
// or
import { verifyWebhook as _verifyWebhook } from 'havenpay'

app.post('/webhooks/havenpay', (req, res) => {
  const signature = req.headers['x-haven-signature'] as string
  const timestamp = req.headers['x-haven-timestamp'] as string
  const body = req.rawBody // Raw body string (not parsed JSON)

  // Verify signature
  const isValid = HavenPay.verifyWebhook(
    body,
    signature,
    timestamp,
    process.env.HAVENPAY_WEBHOOK_SECRET!
  )

  if (!isValid) {
    return res.status(401).send('Invalid signature')
  }

  // Parse and handle event
  const _event = JSON.parse(body)

  switch (_event.event) {
    case 'payment.success':
      // Handle successful payment
      break
    case 'payment.failed':
      // Handle failed payment
      break
    case 'payment.pending':
      // Handle pending payment
      break
  }

  res.status(200).send('OK')
})

Or use constructWebhookEvent for automatic parsing:

import { HavenPay } from '@7haven/havenpay'

app.post('/webhooks/havenpay', (req, res) => {
  try {
    const _event = HavenPay.constructWebhookEvent<WebhookPayload>(
      req.rawBody,
      req.headers['x-haven-signature'] as string,
      req.headers['x-haven-timestamp'] as string,
      process.env.HAVENPAY_WEBHOOK_SECRET!
    )

    // Event is verified and parsed
    console.log(_event.reference, _event.status)

    res.status(200).send('OK')
  }
  catch (_error) {
    console.error('Invalid webhook:', _error)
    res.status(401).send('Invalid signature')
  }
})

Error Handling

The SDK throws specific error types for different failure scenarios:

import {
  HavenPayConfigError as _HavenPayConfigError,
  HavenPaySignatureError as _HavenPaySignatureError,
  HavenPayAPIError,
  HavenPayNetworkError,
  HavenPayTimeoutError,
} from '@7haven/havenpay'

try {
  const _payment = await havenpay.payments.start({ })
} catch (error) {
  if (error instanceof HavenPayAPIError) {
    console.error('API Error:', error.message)
    console.error('Status:', error.statusCode)
    console.error('Code:', error.code)
    console.error('Details:', error.details)
  } else if (error instanceof HavenPayNetworkError) {
    console.error('Network Error:', error.message)
  } else if (error instanceof HavenPayTimeoutError) {
    console.error('Timeout:', error.message)
  }
}

TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

import type {
  Currency as _Currency,
  PaymentStatus as _PaymentStatus,
  ProviderId as _ProviderId,
  StartPaymentParams as _StartPaymentParams,
  StartPaymentResponse as _StartPaymentResponse,
  // ... all other types
} from '@7haven/havenpay'

Security Best Practices

  1. Never expose your API key or signing secret in client-side code - Server-side only
  2. Always verify webhook signatures before processing events (HMAC-SHA256, constant-time comparison)
  3. Use HTTPS for all API requests (enforced by default)
  4. Store secrets in environment variables, not in code
  5. Rotate your secrets regularly (industry best practice: every 90 days)
  6. Enable response verification (enabled by default, protects against MITM attacks)
  7. Use minimum 32-char secrets (64+ chars recommended for production)

Environment Variables

# Required
HAVENPAY_API_KEY=your-api-key
HAVENPAY_SIGNING_SECRET=<32-char-secret>        # Min 32 chars (openssl rand -base64 24)

# Optional
HAVENPAY_WEBHOOK_SECRET=<64-char-secret>        # Min 32, recommend 64 chars (openssl rand -base64 48)
HAVENPAY_BASE_URL=https://pay.7haven.online/api

Generate Secrets:

# Signing secret for API requests/responses (32 chars)
openssl rand -base64 24

# Webhook secret for receiving webhooks (64 chars recommended)
openssl rand -base64 48

Examples

Full Payment Flow

import { HavenPay } from '@7haven/havenpay'

const havenpay = new HavenPay({
  apiKey: process.env.HAVENPAY_API_KEY!,
  signingSecret: process.env.HAVENPAY_SIGNING_SECRET!,
})

async function _processPayment(amount: number, phone: string, cartId: string) {
  try {
    // 1. Get available providers
    const { providers } = await havenpay.payments.getProviders('USD')
    const _ecocash = providers.find(p => p.id === 'omari')

    if (!_ecocash) {
      throw new Error('EcoCash not available')
    }

    // 2. Start payment
    const payment = await havenpay.payments.start({
      amount,
      currency: 'USD',
      providerId: 'omari',
      cartId,
      msisdn: phone,
    })

    console.log('OTP sent to customer')

    // 3. Customer enters OTP (from your UI)
    const otp = await promptForOTP() // Your UI logic

    // 4. Verify OTP
    const result = await havenpay.payments.verifyOtp(payment.reference, otp)

    if (result.status === 'SUCCESS') {
      console.log('Payment successful!')
      return result
    }
    else if (result.status === 'PENDING') {
      // Check status later or wait for webhook
      console.log('Payment pending...')
      return result
    }
    else {
      throw new Error('Payment failed')
    }
  }
  catch (error) {
    console.error('Payment error:', error)
    throw error
  }
}

Recovery After Page Reload

async function _recoverPayment(cartId: string) {
  const payment = await havenpay.payments.getByCart(cartId)

  if (!payment) {
    // No existing payment, start new one
    return null
  }

  if (payment.status === 'AUTH_SENT') {
    // OTP was sent but not verified, show OTP input
    return { showOtpInput: true, reference: payment.reference }
  }

  if (payment.status === 'PENDING') {
    // Payment is being processed
    return { status: 'processing', reference: payment.reference }
  }

  if (payment.status === 'SUCCESS') {
    // Payment completed
    return { status: 'success', reference: payment.reference }
  }

  return null
}

Support

License

MIT © 7Haven