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
oraccount_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 1 day ago