Moneybag

Node.js SDK

Official Node.js SDK for Moneybag Payment API

Node.js SDK

Official Node.js SDK for Moneybag Payment Gateway with full TypeScript support. Integrate Moneybag payments into your Node.js applications with ease.


Installation

Using npm

npm install @moneybag/sdk

Using yarn

yarn add @moneybag/sdk

Requirements

  • Node.js >= 14.0.0
  • npm >= 6.0.0

Features

  • ✅ Full TypeScript support with type definitions
  • ✅ Automatic request retry with exponential backoff
  • ✅ Comprehensive error handling
  • ✅ Built-in request/response validation
  • ✅ Redirect URL parsing utilities
  • ✅ Promise-based API

Quick Start

Initialize the SDK

const { MoneybagSdk } = require('@moneybag/sdk');
// or
import { MoneybagSdk } from '@moneybag/sdk';

// Initialize the SDK
const moneybag = new MoneybagSdk({
  apiKey: 'your-merchant-api-key',
  baseUrl: 'https://sandbox.api.moneybag.com.bd/api/v2'  // Use sandbox for testing
});

Configuration Options

  • apiKey (required): Your merchant API key
  • baseUrl (required): The API base URL
    • Sandbox: https://sandbox.api.moneybag.com.bd/api/v2
    • Production: https://api.moneybag.com.bd/api/v2
  • timeout (optional): Request timeout in milliseconds (default: 30000)
  • retryAttempts (optional): Number of retry attempts for failed requests (default: 3)

Usage Examples

Create a Payment Checkout

const checkoutRequest = {
  order_id: 'order123',
  currency: 'BDT',
  order_amount: '100.00',
  order_description: 'Purchase from my store',
  success_url: 'https://yourdomain.com/payment/success',
  cancel_url: 'https://yourdomain.com/payment/cancel',
  fail_url: 'https://yourdomain.com/payment/fail',
  ipn_url: 'https://yourdomain.com/payment/ipn',  // Optional IPN URL
  customer: {
    name: 'John Doe',
    email: 'john@example.com',
    address: '123 Main Street',
    city: 'Dhaka',
    postcode: '1000',
    country: 'Bangladesh',
    phone: '+8801700000000'
  },
  // Optional fields
  order_items: [
    {
      product_name: 'Product 1',
      quantity: 1,
      unit_price: '100.00'
    }
  ],
  shipping: {
    name: 'John Doe',
    address: '123 Main Street',
    city: 'Dhaka',
    postcode: '1000',
    country: 'Bangladesh'
  },
  payment_info: {
    allowed_payment_methods: ['card', 'mobile_banking'],
    is_recurring: false,
    requires_emi: false
  }
};

try {
  const response = await moneybag.checkout(checkoutRequest);
  console.log('Checkout URL:', response.data.checkout_url);
  // Redirect customer to response.data.checkout_url
} catch (error) {
  console.error('Checkout failed:', error);
}

Verify Payment

try {
  const response = await moneybag.verify('transaction_id_here');
  
  if (response.data.verified && response.data.status === 'SUCCESS') {
    console.log('Payment verified successfully');
    // Update order status in your database
  }
} catch (error) {
  console.error('Verification failed:', error);
}

Handle Redirect URLs

import { RedirectHandler } from '@moneybag/sdk';

// In your success/fail/cancel callback handler
app.get('/payment/success', async (req, res) => {
  try {
    // Parse redirect parameters
    const params = RedirectHandler.parseRedirectUrl(req.url);
    // params = { transaction_id: 'txn123...', status: 'SUCCESS' }
    
    // Verify the payment
    const verification = await moneybag.verify(params.transaction_id);
    
    if (verification.data.verified && params.status === 'SUCCESS') {
      // Payment successful
    }
  } catch (error) {
    console.error('Error handling redirect:', error);
  }
});

Framework Integration

Express.js Integration

const express = require('express');
const { MoneybagSdk, RedirectHandler } = require('@moneybag/sdk');

const app = express();
app.use(express.json());

const moneybag = new MoneybagSdk({
  apiKey: process.env.MONEYBAG_API_KEY,
  baseUrl: process.env.MONEYBAG_BASE_URL || 'https://sandbox.api.moneybag.com.bd/api/v2'
});

// Create payment endpoint
app.post('/api/create-payment', async (req, res) => {
  try {
    const { amount, customer, products } = req.body;
    
    const checkoutRequest = {
      order_id: `ORDER_${Date.now()}`,
      order_amount: amount.toString(),
      currency: 'BDT',
      order_description: 'Purchase from Express Shop',
      success_url: `${req.protocol}://${req.get('host')}/payment/success`,
      cancel_url: `${req.protocol}://${req.get('host')}/payment/cancel`,
      fail_url: `${req.protocol}://${req.get('host')}/payment/fail`,
      customer: customer,
      order_items: products
    };

    const response = await moneybag.checkout(checkoutRequest);

    res.json({ 
      success: true,
      checkout_url: response.data.checkout_url,
      session_id: response.data.session_id 
    });
  } catch (error) {
    console.error('Payment error:', error);
    res.status(error.status || 500).json({ 
      success: false,
      error: error.message 
    });
  }
});

// Payment success callback
app.get('/payment/success', async (req, res) => {
  try {
    const params = RedirectHandler.parseRedirectUrl(req.url);
    const verification = await moneybag.verify(params.transaction_id);
    
    if (verification.data.verified && params.status === 'SUCCESS') {
      // Update order status in database
      // Send confirmation email
      // Clear cart
      
      res.render('payment-success', { 
        transaction: verification.data 
      });
    } else {
      res.render('payment-failed', { 
        message: 'Payment verification failed' 
      });
    }
  } catch (error) {
    res.status(500).render('error', { error });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Next.js Integration

API Route (App Router)

// app/api/checkout/route.js
import { NextResponse } from 'next/server';
import MoneybagClient from '@/lib/moneybag';

const moneybag = new MoneybagClient(
  process.env.MONEYBAG_API_KEY,
  process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
);

export async function POST(request) {
  try {
    const body = await request.json();
    
    const checkout = await moneybag.createCheckout({
      order_id: `ORDER_${Date.now()}`,
      order_amount: body.amount,
      currency: 'BDT',
      order_description: body.description,
      success_url: `${process.env.NEXT_PUBLIC_URL}/payment/success`,
      cancel_url: `${process.env.NEXT_PUBLIC_URL}/payment/cancel`,
      fail_url: `${process.env.NEXT_PUBLIC_URL}/payment/fail`,
      customer: body.customer
    });

    return NextResponse.json({
      success: true,
      payment_url: checkout.data.payment_url
    });
  } catch (error) {
    return NextResponse.json(
      { success: false, error: error.message },
      { status: error.status || 500 }
    );
  }
}

Client Component

// app/checkout/page.js
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function CheckoutPage() {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
  
  const handlePayment = async () => {
    setLoading(true);
    
    try {
      const response = await fetch('/api/checkout', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          amount: 1000,
          description: 'Product purchase',
          customer: {
            name: 'John Doe',
            email: 'john@example.com',
            phone: '+8801700000000'
          }
        })
      });
      
      const data = await response.json();
      
      if (data.success) {
        window.location.href = data.payment_url;
      } else {
        alert('Payment failed: ' + data.error);
      }
    } catch (error) {
      alert('Error: ' + error.message);
    } finally {
      setLoading(false);
    }
  };
  
  return (
    <button onClick={handlePayment} disabled={loading}>
      {loading ? 'Processing...' : 'Pay with Moneybag'}
    </button>
  );
}

NestJS Integration

// moneybag.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, { AxiosInstance } from 'axios';

@Injectable()
export class MoneybagService {
  private client: AxiosInstance;

  constructor(private configService: ConfigService) {
    const apiKey = this.configService.get<string>('MONEYBAG_API_KEY');
    const environment = this.configService.get<string>('NODE_ENV');
    
    this.client = axios.create({
      baseURL: environment === 'production'
        ? 'https://api.moneybag.com.bd/api/v2'
        : 'https://sandbox.api.moneybag.com.bd/api/v2',
      headers: {
        'X-Merchant-API-Key': apiKey,
        'Content-Type': 'application/json',
      },
    });
  }

  async createCheckout(data: any) {
    const response = await this.client.post('/payments/checkout', data);
    return response.data;
  }

  async verifyPayment(transactionId: string) {
    const response = await this.client.get(`/payments/verify/${transactionId}`);
    return response.data;
  }
}

// payment.controller.ts
import { Controller, Post, Get, Body, Query } from '@nestjs/common';
import { MoneybagService } from './moneybag.service';

@Controller('payment')
export class PaymentController {
  constructor(private moneybagService: MoneybagService) {}

  @Post('checkout')
  async createCheckout(@Body() body: any) {
    return await this.moneybagService.createCheckout({
      order_id: `ORDER_${Date.now()}`,
      order_amount: body.amount,
      currency: 'BDT',
      customer: body.customer,
      // ... other fields
    });
  }

  @Get('verify')
  async verifyPayment(@Query('transaction_id') transactionId: string) {
    return await this.moneybagService.verifyPayment(transactionId);
  }
}

Error Handling

The SDK throws typed exceptions for different error scenarios:

import { 
  ValidationException, 
  ApiException, 
  NetworkException 
} from '@moneybag/sdk';

try {
  await moneybag.checkout(request);
} catch (error) {
  if (error instanceof ValidationException) {
    // Handle validation errors
    console.error('Invalid request:', error.message);
  } else if (error instanceof ApiException) {
    // Handle API errors
    console.error('API error:', error.statusCode, error.message);
  } else if (error instanceof NetworkException) {
    // Handle network errors
    console.error('Network error:', error.message);
  }
}

Constants

The SDK exports useful constants:

import { PAYMENT_STATUS, PAYMENT_METHODS, CURRENCY_CODES } from '@moneybag/sdk';

// Payment statuses
PAYMENT_STATUS.SUCCESS    // 'SUCCESS'
PAYMENT_STATUS.FAILED     // 'FAILED'
PAYMENT_STATUS.PENDING    // 'PENDING'
PAYMENT_STATUS.CANCELLED  // 'CANCELLED'

// Payment methods
PAYMENT_METHODS.CARD           // 'card'
PAYMENT_METHODS.MOBILE_BANKING // 'mobile_banking'

// Currency codes
CURRENCY_CODES.BDT // 'BDT'
CURRENCY_CODES.USD // 'USD'

Webhook Signature Verification

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  return `sha256=${expectedSignature}` === signature;
}

// Express middleware for webhook verification
const webhookMiddleware = (req, res, next) => {
  const signature = req.headers['x-webhook-signature'];
  const payload = JSON.stringify(req.body);
  
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  next();
};

Testing

// test/moneybag.test.js
const MoneybagClient = require('../lib/moneybag');

describe('Moneybag Payment Integration', () => {
  let moneybag;
  
  beforeAll(() => {
    moneybag = new MoneybagClient(process.env.TEST_API_KEY, 'sandbox');
  });
  
  test('should create checkout successfully', async () => {
    const checkout = await moneybag.createCheckout({
      order_id: `TEST_${Date.now()}`,
      order_amount: 100,
      currency: 'BDT',
      customer: {
        name: 'Test User',
        email: 'test@example.com',
        phone: '+8801700000000'
      }
    });
    
    expect(checkout.success).toBe(true);
    expect(checkout.data.payment_url).toBeDefined();
  });
  
  test('should handle invalid API key', async () => {
    const invalidClient = new MoneybagClient('invalid_key', 'sandbox');
    
    await expect(
      invalidClient.createCheckout({ /* data */ })
    ).rejects.toThrow('Invalid API key');
  });
});

Environment Variables

# .env
MONEYBAG_API_KEY=sk_test_your_api_key_here
MONEYBAG_WEBHOOK_SECRET=whsec_your_webhook_secret
MONEYBAG_ENVIRONMENT=sandbox
NEXT_PUBLIC_URL=http://localhost:3000

TypeScript Support

// types/moneybag.d.ts
export interface Customer {
  name: string;
  email: string;
  phone: string;
  address?: string;
  city?: string;
  postcode?: string;
  country?: string;
}

export interface CheckoutRequest {
  order_id: string;
  order_amount: number;
  currency: string;
  order_description?: string;
  success_url: string;
  cancel_url: string;
  fail_url: string;
  customer: Customer;
  metadata?: Record<string, any>;
}

export interface CheckoutResponse {
  success: boolean;
  data: {
    payment_url: string;
    transaction_id: string;
    expires_at: string;
  };
}

export interface VerificationResponse {
  success: boolean;
  data: {
    transaction_id: string;
    order_id: string;
    amount: number;
    status: 'SUCCESS' | 'FAILED' | 'CANCELLED' | 'PROCESSING';
    payment_method: string;
    paid_at: string;
  };
}

Resources

Support