Skip to content

About

Centralized payment gateway abstraction for Stripe, PayPal, Payoneer, SSLCommerz, bKash, and Nagad. One unified API for charge, refund, and retrieve.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

BDPayments

A unified payment gateway package for Node.js. One API for multiple payment providers.

Node Version License

Supports Stripe, SSLCommerz, bKash, and Nagad through a single, consistent interface.


πŸš€ Installation

npm install bdpayments

Then install only the gateway SDKs you need:

# For Stripe
npm install stripe

# SSLCommerz, bKash and Nagad need no extra SDK β€” they use the REST API via fetch

⚑ Quick Start

1. Configure credentials (once at startup)

import { configure } from 'bdpayments';

configure({
  stripe: {
    apiKey: process.env.STRIPE_API_KEY,
  },
  bkash: {
    appKey: process.env.BKASH_APP_KEY,
    appSecret: process.env.BKASH_APP_SECRET,
    username: process.env.BKASH_USERNAME,
    password: process.env.BKASH_PASSWORD,
    sandbox: true,
  },
});

Or skip configure() entirely and set environment variables β€” the package reads them automatically.

2. Charge

import { charge } from 'bdpayments';

// Stripe
const stripeResult = await charge({
  gateway: 'stripe',
  amount: 1000,       // $10.00 in cents
  currency: 'usd',
  paymentMethod: 'pm_card_visa',
  confirm: true,
});

// bKash
const bkashResult = await charge({
  gateway: 'bkash',
  amount: 500,
  invoiceNumber: 'INV-001',
  callbackURL: 'https://example.com/callback',
});

// SSLCommerz
const sslResult = await charge({
  gateway: 'sslcommerz',
  amount: 1000,
  currency: 'BDT',
  transactionId: 'TXN-001',
  successUrl: 'https://example.com/success',
  failUrl: 'https://example.com/fail',
  cancelUrl: 'https://example.com/cancel',
});

3. Refund

import { refund } from 'bdpayments';

const result = await refund({
  gateway: 'stripe',
  transactionId: 'pi_...',
  amount: 500,  // Partial refund
});

Check result.success. A refund that the gateway accepts but has not completed (SSLCommerz processing, for example) returns success: false with the real status in result.status. A refund the gateway rejected throws a PaymentError.

4. Retrieve

import { retrieve } from 'bdpayments';

// Retrieve Stripe payment details
const stripeResult = await retrieve({
  gateway: 'stripe',
  transactionId: 'pi_...',
});

// Validate SSLCommerz payment callback or IPN using validation ID
const sslcommerzResult = await retrieve({
  gateway: 'sslcommerz',
  valId: 'val_id_from_callback',
});

console.log(stripeResult.status);  // e.g. "succeeded"
console.log(sslcommerzResult.success); // true if validated successfully

5. Execute

import { execute } from 'bdpayments';

// bKash (execute tokenized payment)
const result = await execute({
  gateway: 'bkash',
  paymentID: 'TR0011...',
});

console.log(result.status);  // e.g. "Completed"

πŸ” Credentials Management

Three ways to provide API keys (highest priority wins):

Priority Method Example
1 (highest) Per-call options charge({ gateway: 'stripe', apiKey: 'sk_...', ... })
2 Global configure() configure({ stripe: { apiKey: 'sk_...' } })
3 (lowest) Environment variables STRIPE_API_KEY=sk_...

Only recognised credential and transport keys are read from per-call options β€” business fields such as amount never end up in the credential object. Resolved credentials also redact themselves when logged or JSON.stringify-ed.

Environment Variables

Gateway Variables
Stripe STRIPE_API_KEY
SSLCommerz SSLCOMMERZ_STORE_ID, SSLCOMMERZ_STORE_PASSWORD, SSLCOMMERZ_SANDBOX
bKash BKASH_APP_KEY, BKASH_APP_SECRET, BKASH_USERNAME, BKASH_PASSWORD, BKASH_SANDBOX
Nagad NAGAD_MERCHANT_ID, NAGAD_PUBLIC_KEY, NAGAD_PRIVATE_KEY, NAGAD_SANDBOX, NAGAD_CLIENT_IP

🌐 Supported Gateways

Gateway Charge Execute Refund Retrieve Auth Method
Stripe βœ… PaymentIntents ❌ βœ… βœ… API Key
SSLCommerz βœ… Session ❌ βœ… βœ… Store ID/Password
bKash βœ… Tokenized βœ… βœ… βœ… Token Grant
Nagad βœ… Checkout ❌ βœ… βœ… RSA Encrypted

Query this at runtime with getGatewayCapabilities('bkash').

πŸ” Idempotency

Retrying a charge is only safe with an idempotency key. The mechanism differs per gateway:

Gateway Key How
Stripe idempotencyKey option Sent as Stripe's Idempotency-Key header
SSLCommerz transactionId (tran_id) Natural key β€” reuse it when retrying
bKash invoiceNumber Natural key β€” reuse it when retrying
Nagad orderId Natural key β€” reuse it when retrying
await charge({
  gateway: 'stripe',
  amount: 1000,
  currency: 'usd',
  idempotencyKey: `order-${orderId}`,
});

Requests time out after 30 seconds by default (configure({ stripe: { timeoutMs: 10000 } }) to change it). A timeout throws PaymentError with code TIMEOUT β€” the payment may still have succeeded, so confirm with retrieve() rather than blindly retrying without a key.

πŸͺ Webhooks & Callbacks

import { verifySslcommerzIpn, retrieve } from 'bdpayments';

app.post('/ipn/sslcommerz', async (req, res) => {
  if (!verifySslcommerzIpn(req.body)) return res.sendStatus(400);

  // Signature proves the payload is authentic; retrieve() confirms the status.
  const payment = await retrieve({ gateway: 'sslcommerz', valId: req.body.val_id });
  if (payment.success) await markOrderPaid(payment.transactionId);

  res.sendStatus(200);
});

Nagad callbacks carry no signature, so parseNagadCallback() normalizes them but always reports verified: false β€” confirm with retrieve() before treating an order as paid.

πŸ“¦ Response Format

All functions return a normalized response:

{
  success: true,
  transactionId: 'pi_...',
  status: 'succeeded',
  amount: 1000,
  currency: 'usd',
  gatewayResponse: { /* raw gateway response */ },
}

πŸ— Internal Architecture

BDPayments uses a centralized architecture for making gateway requests, adhering to SOLID and DRY principles:

  • src/utils/http.js: Centralized httpClient wrapping the native fetch API, with timeouts and unified error mapping.
  • src/utils/wrapper.js: Higher-order function withErrorHandling that maps gateway responses to unified PaymentError instances.
  • src/utils/validate.js: Request validation shared by every adapter, so bad input fails before any network call.
  • src/utils/crypto.js: Cryptographic functions (RSA, signatures) decoupled from specific gateway modules.
  • src/utils/cache.js: Reset registry for auth-token and SDK-client caches, cleared by clearConfig().

πŸ›  Error Handling

import { charge, PaymentError, GatewayNotFoundError, ConfigurationError } from 'bdpayments';

try {
  await charge({ gateway: 'stripe', amount: 1000, currency: 'usd' });
} catch (error) {
  if (error instanceof ConfigurationError) {
    console.log('Missing credentials:', error.missingKeys);
  } else if (error instanceof GatewayNotFoundError) {
    console.log('Try one of:', error.supportedGateways);
  } else if (error instanceof PaymentError) {
    console.log(`${error.gateway} error [${error.code}]: ${error.message}`);
    console.log('HTTP status:', error.status);       // null for non-HTTP failures
    console.log('Original error:', error.originalError);
  }
}

Error codes

Code Meaning
CHARGE_FAILED / EXECUTE_FAILED / REFUND_FAILED / RETRIEVE_FAILED The gateway rejected the operation
INVALID_REQUEST / INVALID_AMOUNT Bad input β€” thrown before any network call
MISSING_CREDENTIALS Required credentials not found
MISSING_DEPENDENCY An optional peer SDK (e.g. stripe) is not installed
INVALID_CONFIG configure() was given an unknown gateway name
GATEWAY_NOT_FOUND Unknown gateway name
UNSUPPORTED_OPERATION The gateway does not implement that operation
AUTH_FAILED Gateway authentication failed (e.g. bKash token grant)
TIMEOUT / NETWORK_ERROR The request never completed

πŸ“„ TypeScript

Full TypeScript definitions are included. Options are narrowed by gateway name, so fields belonging to another gateway are rejected at compile time:

import { charge } from 'bdpayments';

const result = await charge({
  gateway: 'stripe',
  amount: 1000,
  currency: 'usd',
  paymentMethod: 'pm_card_visa',
});

await charge({
  gateway: 'stripe',
  amount: 1000,
  currency: 'usd',
  invoiceNumber: 'INV-1',  // ❌ Error: bKash-only field
});

Use extra to pass anything the types don't model β€” it is forwarded to the gateway untouched.

⚠️ Nagad on older Node.js patch releases

Nagad's protocol requires RSA PKCS#1 v1.5, which Node.js 18.19.1, 20.11.1 and 21.6.2 disabled for decryption (CVE-2023-46809). On those exact patch releases, Nagad charge() fails while decrypting the initialization response. Upgrade to a later patch release.

πŸ§ͺ Development

npm install
npm test          # unit tests, no network access required
npm run typecheck # type definitions
npm run check     # both

Released under the MIT License. Β© 2024–2026 Sabbir Mahmud

About

Centralized payment gateway abstraction for Stripe, PayPal, Payoneer, SSLCommerz, bKash, and Nagad. One unified API for charge, refund, and retrieve.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages