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 dotenvCreate .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 
referenceoraccount_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
 
Updated 4 months ago
