Accept Bank Transfers with Virtual Accounts

📘 Use Case

A utility or subscription service wants to assign unique virtual account numbers to users so payments via bank transfer can be automatically reconciled.


Why Use Virtual Accounts?

  • Customer pays to a dedicated account (e.g. Wema/Providus)
  • Easy auto-matching of payments to customers/orders
  • Ideal for recurring billing or pay-per-use systems

Prerequisites

  • Live or Test TransactPay account
  • API credentials (Secret key and RSA public key)
  • Active virtual account service on your merchant profile
  • Customer ID or reference in your DB

Step-by-Step Implementation


Step 1: Setup Backend

Node.js

Install dependencies:

npm install axios node-rsa dotenv

Create .env:

TRANSACTPAY_SECRET_KEY=your_secret_key

Step 2: Prepare Request Payload

Virtual account requests may require:

{
  "customer_name": "John Doe",
  "reference": "INV-123456",
  "currency": "NGN"
}

Step 3: Node.js Code to Generate Virtual Account

const axios = require("axios");
const NodeRSA = require("node-rsa");
const fs = require("fs");
require("dotenv").config();

const pubKey = fs.readFileSync("./transactpay_pub.pem", "utf8");
const rsa = new NodeRSA(pubKey);
rsa.setOptions({ encryptionScheme: "pkcs1" });

const payload = {
  customer_name: "John Doe",
  reference: "INV-123456",
  currency: "NGN",
};

const encrypted = rsa.encrypt(JSON.stringify(payload), "base64");

axios
  .post("https://api.transactpay.io/virtual-account/create", {
    data: encrypted,
  }, {
    headers: {
      Authorization: `Bearer ${process.env.TRANSACTPAY_SECRET_KEY}`,
    },
  })
  .then((res) => {
    console.log("Virtual Account:", res.data.data);
  })
  .catch((err) => {
    console.error("Error:", err.response?.data || err.message);
  });
import json, requests, os
from dotenv import load_dotenv
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding

load_dotenv()
key = os.getenv("TRANSACTPAY_SECRET_KEY")

with open("transactpay_pub.pem", "rb") as key_file:
    pub_key = serialization.load_pem_public_key(key_file.read())

payload = {
    "customer_name": "John Doe",
    "reference": "INV-123456",
    "currency": "NGN"
}

encrypted = pub_key.encrypt(json.dumps(payload).encode(), padding.PKCS1v15())

res = requests.post(
    "https://api.transactpay.io/virtual-account/create",
    headers={"Authorization": f"Bearer {key}"},
    json={"data": encrypted.hex()}
)

print("Response:", res.json())


Sample Response

{
  "status": true,
  "message": "Account created",
  "data": {
    "account_number": "1234567890",
    "bank_name": "Wema Bank",
    "reference": "INV-123456"
  }
}

Step 4: Display Account Details to Customer

On your frontend or dashboard:

“Pay NGN 25,000 to the Wema Bank account below. Your invoice will be confirmed automatically.”


Step 5: Listen for Webhook Updates

TransactPay sends webhook events like virtual_account.payment_received to notify you when a deposit is made.

Sample webhook handling logic:

  • Match reference or account_number
  • Mark invoice/order as “Paid”
  • Send confirmation email or SMS

Security Notes

  • Never expose your secret key on frontend
  • Store reference IDs securely
  • Log unexpected webhooks

Use Case Variants

  • Create 1 account per customer (for subscriptions)
  • Create 1 account per order (for pay-per-use)
  • Expire accounts after X days if needed