Virtual Account Creation

Summary

Create Virtual Accounts on demands, receive funds from customers via Virtual Account Number and get notification on Virtual Accounts usage.

Dedicated Virtual Accounts (DVAs) is a service that lets you create Nigerian virtual accounts for your customers. These accounts allow your customers to receive payment from their customers.

When you create a Dedicated Virtual Account (DVA) for a customer, all bank transfers to that account will automatically be recorded as transactions from that customer.

Coverage

  • NGN

Integration steps

You can generate a VA and start receiving money in 3 easy steps:

  1. Prepare payload (payload includes: first name, last name, BVN, and email)

  2. Initiate VA creation (encrypting payload)

  3. Get instant response


Before you begin!

  • Link to the Postman collection

  • Find your keys on the Klasha Dashboard → Settings → Generate API Keys (here)

  • Klasha Dashboard, Generate API Keys page

Token Generation
Encryption Algorithm

Encrypt the plain request body to get an encrypted request body. We use a Cipher Block Chaining encryption and you will use your entire encryption key size for this encryption mode.

Navigate to Dashboard->Settings->Generate API keys -- Encryption key

Below is a CBC encryption code snippet in Java:


public static String encryptCBC(String stringToEncrypt, String encryptionkey) {
    try {
        SecureRandom sr = new SecureRandom();
        byte[] salt = new byte[8];
        sr.nextBytes(salt);
        final byte[][] keyAndIV =GenerateKeyAndIV(32, 16, 1, salt, encryptionkey.getBytes(StandardCharsets.UTF_8),
                MessageDigest.getInstance("MD5"));
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyAndIV[0], "AES"), new IvParameterSpec(keyAndIV[1]));
        byte[] encryptedData = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
        byte[] prefixAndSaltAndEncryptedData = new byte[16 + encryptedData.length];
        // Copy prefix (0-th to 7-th bytes)
        System.arraycopy("Salted__".getBytes(StandardCharsets.UTF_8), 0, prefixAndSaltAndEncryptedData, 0, 8);
        // Copy salt (8-th to 15-th bytes)
        System.arraycopy(salt, 0, prefixAndSaltAndEncryptedData, 8, 8);
        // Copy encrypted data (16-th byte and onwards)
        System.arraycopy(encryptedData, 0, prefixAndSaltAndEncryptedData, 16, encryptedData.length);
        return Base64.getEncoder().encodeToString(prefixAndSaltAndEncryptedData);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;

try {
    md.reset();

    // Repeat process until sufficient data has been generated
    while (generatedLength < keyLength + ivLength) {

        // Digest data (last digest if available, password data, salt if available)
        if (generatedLength > 0)
            md.update(generatedData, generatedLength - digestLength, digestLength);
        md.update(password);
        if (salt != null)
            md.update(salt, 0, 8);
        md.digest(generatedData, generatedLength, digestLength);

        // additional rounds
        for (int i = 1; i < iterations; i++) {
            md.update(generatedData, generatedLength, digestLength);
            md.digest(generatedData, generatedLength, digestLength);
        }

        generatedLength += digestLength;
    }

    // Copy key and IV into separate byte arrays
    byte[][] result = new byte[2][];
    result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
    if (ivLength > 0)
        result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);

    return result;

} catch (DigestException e) {
    throw new RuntimeException(e);

} finally {
    // Clean out temporary data
    Arrays.fill(generatedData, (byte) 0);
}

}

Initiate VA creation

create a virtual account

POST {{env_url}}/wallet/virtual/v2/business/create/account

Encrypt the plain request body using the encryption algorithm specified above. Set the encrypted data to the message as shown below. You are required to set the x-auth-token and Authorization in the headers.

Headers

KeyValue

x-auth-token

Your merchant public key

Authorization

Bearer <token here>

Request Body (encrypted)

{
   "message": "encrypted-message"
}

The result of the encryption of the payload that we specified has to be used with the Create virtual account endpoint as shown below:

Request Body (plain)

}
   "firstName": "John",
   "lastName": "Doe",
   "bvn": "22222222222",
   "email": "test@klasha.com"
}
NameTypeDescription

firstName*

String

John

lastName*

String

Doe

bvn*

22222222222

email*

String

test@klasha.com

When a VA creation is initiated, processing could take a few seconds. These are sample responses you will get below:

{
    "id": 218,
    "walletId": null,
    "accountNumber": "8574578073",
    "bankName": "WEMA BANK",
    "orderRef": "fOeatZO40PEJzeiPvq8q",
    "txRef": "klasha-virtual-account-banktransfer-Steph and sons-133",
    "flwRef": "URF_G6Ie8ZP9GZ5hAZk8z6fZ",
    "userId": null,
    "businessId": 133,
    "bankCode": null,
    "enabled": true,
    "accountName": "Stephen Ojerinde",
    "email": "test@steph.com",
    "profileHash": "hwKpOI9+kZR/fFy7hLOpcQ==",
    "createdAt": "2023-07-07 00:42:45",
    "updatedAt": "2023-07-07 00:42:45"
}

Requery

GET {{env_url}}/wallet/virtual/v2/account/email

In case of network downtime or failures during VA creation, the virtual account can be re-queried with the user’s email. This can be fetched using the Requery with email API on the Postman collection.

Headers

KeyValue

Authorization

Bearer <token here>

[
    {
        "accountNumber": "8573777620",
        "bankName": "WEMA BANK",
        "orderRef": "HsKRPDlemp7XBKvgO1HE",
        "txRef": "klasha-fund-wallet-banktransfer-steph-AJDcN6app",
        "flwRef": "URF_1Qw8TF2ZN1KkIdsqyHYI",
        "businessId": 5,
        "enabled": true,
        "accountName": "Forrest Green",
        "email": "test@klasha.com",
        "createdAt": "2023-07-04 17:02:59",
        "updatedAt": "2023-07-04 17:02:59"
    }
]
Transaction Webhook

When payments are received from your users, we would send a webhook with the details of the transaction to your callback URL.

{
  "data": {
    "createdAt": "2023-07-14T19:28:18.585",
    "narration": "Bank Transfer",
    "destinationCurrency": "NGN",
    "sourceAmount": 2000,
    "sourceCurrency": "NGN",
    "tnxRef": "reference_322123243",
    "status": "successful",
    "destinationAmount": 2000,
    "originatorAccountName":"Stephen Oj",
    "bankName": "Access Bank",
    "originatorAccountNumber":"1234456211",
    "customer": {
      "id": 53603,
      "name": "Last Name",
      "email": "test@steph.com",
      "phone": null,
      "createdAt": "2023-07-04 20:11:05",
      "updatedAt": "2023-07-04 20:11:05"
    }
  },
  "event": "charge.completed"
}

Polling transaction status

POST {{env_url}}/nucleus/tnx/collection/status

To know the status of a transaction, you can fetch the transaction using the Transaction Status API on the Postman collection. Make use of the session id received from the bank. See an example below:

Request Body

NameTypeDescription

gateRef*

String

10043599483902847574821

{
    "destinationCurrency": "NGN",
    "sourceAmount": 2500.000000,
    "sourceCurrency": "NGN",
    "status": "successful",
    "destinationAmount": 2500.000000,
    "customer": {
        "id": 53603,
        "name": "Last Name",
        "email": "test@steph.com",
        "phone": null,
        "createdAt": "2023-07-04 20:11:05",
        "updatedAt": "2023-07-04 20:11:05"
    }
}

Last updated