Catalog npm
Back to packageRELEASE DETAILS

1.0.5

npm

9bridge

Aggregates native Nigerian payment gateways, handles local currency webhooks, and outputs Stellar x402 payment challenge layouts

fintechflutterwavemonnifynigeriapayment-gatewaypaystacksorobanstellarx402
Published19 Aug 2026
Direct dependencies7
Transitive dependenciesNot calculated

README

Current package documentation

9bridge

Nigerian payment gateway aggregator with Stellar x402 payment challenge output.

Aggregates Paystack, Monnify, and Flutterwave behind a single interface, normalizes webhooks into a unified event schema, and converts verified fiat payments into Stellar USDC via Soroban smart contracts following the x402 protocol.

Install

npm install 9bridge

Quick Start

import { create9bridge } from '9bridge';
import express from 'express';

const app = express();

// Capture raw body for signature verification
app.use(express.json({
  verify: (req, _res, buf) => { (req as any).rawBody = buf.toString(); }
}));

const { gatewayFactory, webhookRouter } = create9bridge(
  {
    paystack:    { apiKey: 'sk_live_...' },
    monnify:     { apiKey: '...', secretKey: '...', contractCode: '...' },
    flutterwave: { apiKey: 'FLWSECK-...' },
  },
  {
    paystackSecret:    'whsec_...',
    flutterwaveSecret: 'FLWSECK-...',
    stellarPayTo:      'G...YOUR_STELLAR_PUBLIC_KEY',
    sorobanContractId: 'C...YOUR_CONTRACT_ID',
    stellarNetwork:    'testnet',
  }
);

// Mount the webhook endpoint
app.use(webhookRouter);

// Use adapters directly
const paystack = gatewayFactory.resolve('card');
const { data } = await paystack.initializePayment({
  amount: 5000,
  currency: 'NGN',
  email: 'buyer@example.com',
});

app.listen(3000);

Payment Channels

ChannelGatewayTrigger Methods
cardPaystackcard, cards, debit_card, checkout
transferMonnifytransfer, bank_transfer, virtual_account
ussd_walletFlutterwaveussd, wallet, opay, palmpay, mobile_money

API Reference

create9bridge(gatewayConfig, webhookConfig)

Entry point. Returns { gatewayFactory, webhookRouter }.

GatewayFactory

import { GatewayFactory } from '9bridge';

const factory = new GatewayFactory(config);
factory.initialize();

// Resolve by channel name
const adapter = factory.resolve('card');

// Resolve by any supported alias
const adapter = factory.resolveByMethod('checkout');

// List configured channels
factory.getAvailableChannels(); // ['card', 'transfer', 'ussd_wallet']

PaystackAdapter

const paystack = factory.resolve('card');

// Initialize
const init = await paystack.initializePayment({
  amount: 5000,
  currency: 'NGN',
  email: 'buyer@example.com',
  reference: 'custom-ref-123',
  metadata: { orderId: '42' },
});
// init.data.authorization_url → redirect user here

// Verify
const verification = await paystack.verifyPayment('custom-ref-123');
// verification.data.status === 'success'

// List banks (optional)
const banks = await paystack.listBanks();

MonnifyAdapter

const monnify = factory.resolve('transfer');

// Initialize bank transfer
const init = await monnify.initializePayment({
  amount: 10000,
  currency: 'NGN',
  email: 'buyer@example.com',
});

// Create virtual account for a customer
const account = await monnify.createVirtualAccount('customer-123');
// account.data.accounts[0].account_number → share with customer

// Initiate outbound transfer
const transfer = await monnify.initiateTransfer({
  amount: 5000,
  currency: 'NGN',
  destination_bank_code: '044',
  destination_account_number: '1234567890',
  reference: 'disb-001',
});

FlutterwaveAdapter

const flutterwave = factory.resolve('ussd_wallet');

// Initialize payment
const init = await flutterwave.initializePayment({
  amount: 2000,
  currency: 'NGN',
  email: 'buyer@example.com',
});

// Initiate USSD
const ussd = await flutterwave.initiateUSSD({
  amount: 2000,
  currency: 'NGN',
  email: 'buyer@example.com',
  phone_number: '+2348012345678',
  ussd_type: 'generic',
});

// Verify
const verification = await flutterwave.verifyPayment('FW-txref-123');

Webhook Endpoint

POST /api/v1/webhooks/payment-listener

The router handles four steps:

  1. Detects gateway from headers (x-paystack-signature, flutterwave-signature, monnify-signature)
  2. Verifies signature using the configured secret
  3. Normalizes payload into IVerifiedPaymentEvent
  4. Builds x402 response with Soroban swap simulation

Response

Returns HTTP 402 with the x402 payment challenge:

{
  "requiresPayment": true,
  "paymentRequirements": {
    "network": "testnet",
    "x402Version": 1,
    "accepts": ["stellar:usdc@testnet", "soroban:C..."],
    "maxAmountRequired": "3.125000",
    "resource": "/payments/ref-123",
    "description": "9bridge payment: NGN 5000.00 from paystack via card",
    "mimeType": "application/json",
    "payTo": "G...",
    "extraHeaders": {
      "X-9bridge-Tx-Reference": "ref-123",
      "X-9bridge-Gateway": "paystack",
      "X-9bridge-Channel": "card",
      "X-9bridge-Original-Amount": "NGN 5000.00"
    }
  },
  "paymentReceipt": {
    "success": true,
    "transactionHash": "a1b2c3...",
    "payer": "buyer@example.com",
    "paymentId": "12345",
    "network": "testnet",
    "amount": "3.125000"
  }
}

The transactionHash in the receipt is a simulated payment ID derived from the verified payment event and contract parameters. It is deterministic and does not represent an actual on-chain Stellar transaction. The real on-chain hash is produced when the Soroban contract is invoked by the recipient wallet.

The simulated payment ID is set in the X-PAYMENT-RESPONSE header.

IVerifiedPaymentEvent

All gateway payloads are normalized to this shape:

interface IVerifiedPaymentEvent {
  transactionId: string;
  reference: string;
  amount: number;
  currency: string;
  status: 'success' | 'failed' | 'pending' | 'reversed';
  channel: 'card' | 'transfer' | 'ussd_wallet';
  gateway: 'paystack' | 'monnify' | 'flutterwave';
  paidAt: string;
  createdAt: string;
  customer: { id?: string; email: string; name?: string; phone?: string };
  fees: { gateway_fee: number; processing_fee: number; total_fee: number; currency: string };
  metadata: Record<string, unknown>;
  rawPayload: Record<string, unknown>;
}

Exchange Rate Provider

The x402 pipeline converts NGN to USDC using a pluggable exchange rate provider. By default it uses a static rate of 1600 NGN = 1 USD.

Built-in providers

import {
  StaticExchangeRateProvider,
  CoinGeckoExchangeRateProvider,
  CompositeExchangeRateProvider,
} from '9bridge';
ProviderSourceUse case
StaticExchangeRateProviderHardcoded rateDefault fallback, testing
CoinGeckoExchangeRateProviderCoinGecko APILive market rates
CompositeExchangeRateProviderTries providers in orderResilience, production

Using a live rate provider

import { create9bridge, CompositeExchangeRateProvider, CoinGeckoExchangeRateProvider } from '9bridge';

const { gatewayFactory, webhookRouter } = create9bridge(
  gatewayConfig,
  {
    ...webhookConfig,
    exchangeRateProvider: new CompositeExchangeRateProvider([
      new CoinGeckoExchangeRateProvider(),
    ]),
  }
);

Custom provider

Implement the IExchangeRateProvider interface:

import { IExchangeRateProvider, IExchangeRateQuote } from '9bridge';

class MyProvider implements IExchangeRateProvider {
  async getRate(from: string, to: string): Promise<IExchangeRateQuote> {
    const rate = await fetchMyRate(from, to);
    return {
      rate,
      from,
      to,
      timestamp: new Date().toISOString(),
      source: 'my-source',
      quoteId: 'optional-id',
      expiresAt: 'optional-expiry',
    };
  }
}

x402 Pipeline

import { buildX402PaymentChallenge, simulateSorobanSwap } from '9bridge';

// Swap simulation (now async — fetches live exchange rate)
const result = await simulateSorobanSwap(verifiedEvent, {
  payTo: 'G...',
  sorobanContractId: 'C...',
  network: 'testnet',
});
// result.simulatedPaymentId  — deterministic ID, NOT an on-chain tx hash
// result.toAmount            — USDC amount after conversion
// result.exchangeRate        — rate source, timestamp, quote ID

// Full x402 challenge
const challenge = await buildX402PaymentChallenge(verifiedEvent, {
  payTo: 'G...',
  sorobanContractId: 'C...',
  network: 'testnet',
});

Standalone Usage (no Express)

import { PaystackAdapter, parseWebhookEvent } from '9bridge';

const paystack = new PaystackAdapter('sk_live_...');
const event = parseWebhookEvent('paystack', rawPayload);

if (event.status === 'success') {
  // handle confirmed payment
}

License

MIT

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 7

Native requirements
7 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
axios
Environment
{
  "local_name": "axios",
  "alias": false,
  "peer_metadata": null
}
^1.7.0runtime
crypto
Environment
{
  "local_name": "crypto",
  "alias": false,
  "peer_metadata": null
}
^1.0.1runtime
express
Environment
{
  "local_name": "express",
  "alias": false,
  "peer_metadata": null
}
^4.21.0runtime
stellar-sdk
Environment
{
  "local_name": "stellar-sdk",
  "alias": false,
  "peer_metadata": null
}
^12.3.0runtime
@types/express
Environment
{
  "local_name": "@types/express",
  "alias": false,
  "peer_metadata": null
}
^4.17.21development
@types/node
Environment
{
  "local_name": "@types/node",
  "alias": false,
  "peer_metadata": null
}
^20.14.0development
typescript
Environment
{
  "local_name": "typescript",
  "alias": false,
  "peer_metadata": null
}
^5.5.0development

Distribution files

1 collected
Distribution 1

Source distribution

Source file
Checksums from source
{
  "integrity": "sha512-Vu82DeD99Z5qEfwR6L5EfXQodwsW8+/fgLQkxcInx5gcIkGZv2WoaX0eVg80tkz7hdqOpRYm0W6vN01kzeVN2Q==",
  "shasum": "b63c93b19dbf265e2c47cdff067d302bd6f255fc",
  "tarball": "https://registry.npmjs.org/9bridge/-/9bridge-1.0.5.tgz",
  "fileCount": 46,
  "unpackedSize": 94955,
  "signatures": [
    {
      "keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U",
      "sig": "MEYCIQD9/qzr1jo2bzwB5riSPbY3K16fGNmhpTv1mnWiB/4eCAIhAMGCE1MGkfdkjYI5gew8+nJ84076LuAAhhBImbFcInsq"
    }
  ]
}

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 21:36 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:722768e53ee433bf5ab8ad78377fd7682e4b6a9efb7b67c921ff3d2416987ee3

Other collected versions

1.0.419 Aug1.0.319 Aug1.0.219 Aug1.0.119 Aug1.0.019 AugView all collected versions →