promise-olajide / override-mvc-framework
A lightweight PHP MVC framework
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Type:project
Requires
- php: >=7.4
README
// --------------------------------------------------------------------------------
// @version 1.0.4.2 - README.md
// --------------------------------------------------------------------------------
// Confidence Pay Mobile App - November 2024
// @author Olajide Promise - Confidence Bank
// ola.promise10@gmail.com
// --------------------------------------------------------------------------------
// You will keep getting updated versions of this file till the whole thing is completed
//
// __ 5.9
BVN Validation endpoint
//
// --------------------------------------------------------------------------------
//
// --------------------------------------------------------------------------------
0 - Summary
1 - Introduction
2 - Data Security
3 - Encryption Handler Function
4 - Code Samples - App Pages and Routines
4.1 - Account Creation
4.2 - Registration OTP Verification
4.3 - Set User Password
4.4 - Account Login
4.5 - Fetch User Data
4.6 - Set Transaction Pin
4.7 - Verify Transaction Pin
4.8 - Store BVN
4.9 - Store NIN
4.10 - Update BVN Verification Status
4.11 - Update NIN Verification Status
4.12 - Upload Avatar - OBSOLETE
4.13 - Delete User Account
4.14 - Resend Registration OTP
4.15 - Upload Utility Bill
4.16 - Password Reset Endpoint
4.17 - Verify Reset OTP
4.18 - Resend Password Reset OTP
4.19 - Verify Device Hash
4.20 - Logout User
4.21 - Verify BVN
4.22 - Store Avatar
5 - Code Samples - Core Banking (CONFIDENCE)
5.1 - (CONFIDENCE) Create Bank Account
5.2 - (CONFIDENCE) Validate Confidence NUBAN Account
5.3 - (CONFIDENCE) Check Account Balance
5.4 - (CONFIDENCE) Retrieve Account History
5.5 - (CONFIDENCE) Get Bank List
5.6 - (CONFIDENCE) Validate Other Bank NUBAN
5.7 - (CONFIDENCE) Funds Transfer - Confidence Bank Accounts
5.8 - (CONFIDENCE) Interbank Transfers
5.9 - (NIBSS) BVN Validation
6 - Code Samples - VAS (DATA)
6.1 - (VAS) Get Data Plans
6.2 - (VAS) Data Purchase
6.3 - (VAS) Airtime Purchase
6.4 - (VAS) Fetch Electricity Plans
6.5 - (VAS) Verify Meter Number
6.6 - (VAS) Purchase Electricity
6.7 - (VAS) Get CableTV Providers
6.8 - (VAS) Get CableTV Plans (specific provider)
6.9 - (VAS) CableTV Purchase
7 - Author
1 - Introduction
Confidence Pay Mobile App REST APIs
The Confidence Pay Mobile App REST APIs provide the mobile app developer with relevant API methods to create and manage user accounts, as well as perform relevant operations on the user's Confidence Bank accounts. This file and project details are confidential.
2 - Data Security
Encryption Details
The encryption standard used is AES 64 bit encryption to secure data transfer between the backend code and client.
Your API request body has to be encrypted using the secret key and secret IV provided you. Also note that API responses will also be encrypted using the same key pairs.
API Keys
You'll be provided with an api key to authenticate api requests. Please store your keys securely.
3 - Encryption Handler Function
Encryption and Decryption
It is crucial to define a function to handle data encryption. You will need to encrypt data before sending to the API, as well as decrypting API responses.
You will need to install the crypto-js
, as well as the base-64
library in your project
npm install crypto-js npm install base-64
Or using yarn
yarn add crypto-js yarn add base-64
Here's a complete example for react native
import CryptoJS from 'crypto-js'; import { encode, decode } from 'base-64'; const encryptionHandler = (action, string) => { const Sha256 = CryptoJS.SHA256; const Hex = CryptoJS.enc.Hex; const Utf8 = CryptoJS.enc.Utf8; const Base64 = CryptoJS.enc.Base64; const AES = CryptoJS.AES; const secretKey = '<YOUR_SECRET_KEY>'; const secretIV = '<YOUR_SECRET_IV>'; // We're using the first 32 bytes const key = Sha256(secretKey).toString(Hex).substr(0, 32); const initializationVector = Sha256(secretIV).toString(Hex).substr(0, 16); let output = ''; if (string !== '') { if (action === 'encrypt') { // Encryption - first base 64 encoding - by default const temp = AES.encrypt(string, Utf8.parse(key), { iv: Utf8.parse(initializationVector), }).toString(); // Second Base64 encoding output = Utf8.parse(temp).toString(Base64); } if (action === 'decrypt') { // Decrypt // Roll back 2nd level base64 encoding try { string = decode(string); } catch (error) { return string; } output = AES.decrypt(string, Utf8.parse(key), { iv: Utf8.parse(initializationVector), }).toString(Utf8); } } return output; }; export default encryptionHandler;
4 - Code Samples
4.1 User - Registration
Here's a complete code example for User Registration
Use this template for other requests. Subsequent examples will contain only request method
, action
, and options
.
You have been provided with the API base URL(represented as websiteURL
) in a separate document or email. You need that in order to use this API and we will not include it in this file.
View the full code example below. It's written using axios
. Hence, you would need to install axios in your project.
import axios from 'axios'; import encryptionHandler from './encryptionHandler'; // Assuming encryptionHandler is in the same directory // User registration function userRegistration() { const method = 'user'; const action = 'registration'; const firstName = ''; const lastName = ''; const phone = ''; const password = ''; const websiteURL = ''; const apiKey = ''; const bodyData = { method: method, action: action, options: { firstName: firstName, lastName: lastName, phone: phone, password: password } }; axios.get(`${websiteURL}/app/index.aspx`, { params: { body: encryptionHandler('encrypt', JSON.stringify(bodyData)) }, headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } }) .then(response => { const result = encryptionHandler('decrypt', response.data); const parsedResult = JSON.parse(result); console.log(parsedResult); if (parsedResult.status === 200) { // User registration success } else if (parsedResult.status === 401) { // Handle error } else { // Unknown error } }) .catch(error => { // Error with request console.error(error); }); }
4.2 - Registration OTP Verification
Verify OTP entered by user after registration
NOTE - when you have verified the user OTP, the api will return a deviceHash {data.currentDeviceHash}
to you. Think of this as a session token to authenticate api requests for this user on this current login session.
We will require the device hash in subsequent requests.
Always store the device hash returned
const method = 'user'; const action = 'verifyRegistrationOTP'; const options = { 'phone': '', 'otp': '' }
4.3 - Set User Password
Store the user password - this endpoint can be called from the settings page, or after password reset success.
NOTE - The use of the device hash here to authenticate this request has been disabled.
Also, this endpoint returns the current device hash as this user is now logged in.
const method = 'user'; const action = 'setUserPassword'; const options = { 'phone': '', 'password': '' }
4.4 - Account Login
User account login
NOTE - when a user is successfully logged in,, the api will return a deviceHash {data.currentDeviceHash}
to you. Think of this as a session token to authenticate api requests for this user on this current login session.
We will require the device hash in subsequent requests.
Always store the device hash returned
const method = 'user'; const action = 'login'; const options = { 'phone': '', 'password': '' }
4.5 - Fetch User Data
Get User Data
const method = 'user'; const action = 'getData'; const options = { 'phone': '', 'deviceHash': '' }
4.6 - Set Trasaction Pin
Store the user's four digit transaction pin
const method = 'user'; const action = 'setTransactionPin'; const options = { 'phone': '', 'transactionPin': '', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
4.7 - Verify Trasaction Pin
Store the user's four digit transaction pin
const method = 'user'; const action = 'verifyTransactionPin'; const options = { 'phone': '', 'transactionPin': '' }
4.8 - Store BVN
Store the user's BVN.
const method = 'user'; const action = 'storeBVN'; const options = { 'phone': '', 'bvn': '', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
4.9 - Store NIN
Store the user's NIN.
const method = 'user'; const action = 'storeNIN'; const options = { 'phone': '', 'nin': '', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
4.10 - Update BVN Verification Status
Use this endpoint to update the user's BVN verification status in the database after the BVN verification process is complete.
Note that if the bvn verification process is not passed, you wouldn't need to use this endpoint. Use only when verification is successful.
const method = 'user'; const action = 'updateBVNVerificationStatus'; const options = { 'phone': '', 'bvnVerificationStatus': 'verified', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
4.11 - Store NIN
Use this endpoint to update the user's BVN verification status in the database after the BVN verification process is complete.
Note that if the bvn verification process is not passed, you wouldn't need to use this endpoint. Use only when verification is successful.
const method = 'user'; const action = 'updateNINVerificationStatus'; const options = { 'phone': '', 'ninVerificationStatus': '', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
4.12 - Upload Avatar - OBSOLETE
Upload user profile pic as a file.
NOTE - Since we are uploading a file, this api is to be called via an axios POST
method.
Also, method and action, and options will be encrypted individually, with the imageFile
appended to the request.
You will need to use the react-native-image-picker
package to select images from the device.
Also, ensure you have set up the proper permissions required in your AndroidManifest.xml
and Info.plist
to enable access to the device's media library.
The api would return the complete url to the uploaded image.
I'll provide a full example in react native, tweak as necessary.
import React, { useState } from 'react'; import { View, Button, Alert } from 'react-native'; import { launchImageLibrary } from 'react-native-image-picker'; import axios from 'axios'; // Function to encrypt and decrypt data (Assume these are defined elsewhere) const encryptionHandler = (action, data) => { // Implement encryption and decryption logic here return data; // Placeholder }; const UploadAvatar = () => { const [image, setImage] = useState(null); const uploadAvatar = () => { const method = 'user'; const action = 'uploadAvatar'; const phone = ''; const deviceHash = ''; const websiteURL = ''; const apiKey = ''; // Prepare form data let formData = new FormData(); let options = new URLSearchParams({ phone, deviceHash }).toString(); // Encrypt method, action, and options INDIVIDUALLY const encryptedMethod = encryptionHandler('encrypt', method); const encryptedAction = encryptionHandler('encrypt', action); const encryptedOptions = encryptionHandler('encrypt', options); formData.append('action', encryptedAction); formData.append('method', encryptedMethod); formData.append('options', encryptedOptions); if (image) { formData.append('image', { uri: image.uri, type: image.type, name: image.fileName }); axios.post(`${websiteURL}/app/index.aspx`, formData, { headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${apiKey}`, } }) .then(response => { let result = encryptionHandler('decrypt', response.data); result = JSON.parse(result); console.log(result); if (result.status === 200) { // Avatar uploaded successfully Alert.alert('Success', 'Avatar uploaded successfully'); } else if (result.status === 401) { // Handle error Alert.alert('Error', 'Unauthorized'); } else { // Unknown error Alert.alert('Error', 'An unknown error occurred'); } }) .catch(error => { console.error(error); Alert.alert('Error', 'Failed to upload avatar'); }); } else { Alert.alert('Error', 'No image selected'); } }; const selectImage = () => { launchImageLibrary({ mediaType: 'photo' }, response => { if (response.didCancel) { console.log('User cancelled image picker'); } else if (response.error) { console.log('ImagePicker Error: ', response.error); } else { setImage(response.assets[0]); } }); }; return ( <View> <Button title="Select Image" onPress={selectImage} /> <Button title="Upload Avatar" onPress={uploadAvatar} /> </View> ); }; export default UploadAvatar;
4.13 - Delete User Account
API method to delete any user account - will be discontinued in production. This is only to aid tests.
const method = 'user'; const action= 'deleteAccount'; var options = { 'phone': '' }
4.14 - Resend Registration OTP
One of the very first API methods implemented, just noticed today that it wasn't part of the readme document.
My apologies.
Resend registration otp
to user, in the event the otp isn't received.
const method = 'user'; const action= 'resendRegistrationOTP'; var options = { 'phone': '' }
4.15 - Upload Utility Bill
Upload user utility bill as a file.
Same logic with uploading avatar you might have to make more adjustments to the code based on how you get your file from the user's device.
Please encrypt the method and action in the same manner as in the upload avatar code sample.
const method = 'user'; const action= 'uploadUtilityBill'; const phone = ''; const deviceHash = ''; // Prepare form data let formData = new FormData(); // Remember to add the phone number and device hash let options = new URLSearchParams({ phone, deviceHash }).toString(); // Encrypt method, action, and options INDIVIDUALLY const encryptedMethod = encryptionHandler('encrypt', method); const encryptedAction = encryptionHandler('encrypt', action); const encryptedOptions = encryptionHandler('encrypt', options); // Append to your form data formData.append('action', encryptedAction); formData.append('method', encryptedMethod); formData.append('options', encryptedOptions);
4.16 - Password Reset Endpoint
API method to reset user password. Sends a 4 digit otp to a registered phone number.
const method = 'user'; const action= 'forgotPassword'; var options = { 'phone': '' }
4.17 - Verify Reset OTP
Password reset OTP verification
const method = 'user'; const action= 'verifyResetOTP'; var options = { 'phone': '', 'otp': '' }
4.18 - Resend Password Reset OTP
Endpoint to resend password reset otp - in the event the user does not receive the otp
const method = 'user'; const action= 'resendResetOTP'; var options = { 'phone': '' }
4.19 - Verify Device Hash
USE AT WILL. This endpoint is to verify that the user's login session on this device is still active.
NOTE - The device hash doesn't expire with time, but when a user is logged in again, it is reset and the new one is sent to you. So if the user is logged in on another device B
, you can log them out on device A
if device hash verificaion fails.
const method = 'user'; const action= 'verifyDeviceHash'; var options = { 'phone': '', 'deviceHash': '' }
4.20 - Logout User
Use this endpoint to logout a user. The device hash is needed to validate this request.
Please note that once logged out, the device hash is reset so you can delete it from your storage. A new one is generated upon login.
const method = 'user'; const action= 'logout'; var options = { 'phone': '', 'deviceHash': '' }
4.21 - Verify BVN
Use this endpoint to verify a logged in user's BVN. The device hash is needed to validate this request.
Please note that this api endpoint removes the need to use endpoints 4.8
, 4.10
, and 5.1
as the BVN passed will be stored when verified. Also note that an account number will be generated and returned to you.
I've also handled instances where the account number is in use already - which would mean that for whatever reason, this is not a new customer.
const method = 'accounts'; const action= 'verifyBVN'; var options = { 'phone': '', 'bvn': '', 'deviceHash': '' }
4.22 - Store Avatar
Store user's avatar in base64 format. The device hash is needed to validate this request.
Please note that this api endpoint removes the need to use the endpoint 4.12
as the avatar is no longer uploaded as a file, rather stored as a base64 image.
Here's a complete example for react native. I modified the code you sent earlier ( depends on who's reading this at this time ) to encode the image file as a base64 string.
NOTE - We are not sending the image separately, we're passing the avatar as a part of the options
, like we do for other endpoints.
As expected, this is a POST request.
const uploadImage = async (assets, action) => { assets = ImagePickerAsset[] || null; action = action || "storeAvatar"; const token = userData?.token; const phone = userData?.phone || ""; const deviceHash = "yourDeviceHashHere"; // Current device hash goes here try { const fileUri = assets[0]?.uri; // Convert the selected image to base64 const base64Image = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => { const base64String = reader.result.replace("data:", "").replace(/^.+,/, ""); resolve(base64String); }; reader.onerror = reject; reader.readAsDataURL({ uri: fileUri }); }); // We're encrypting method, action, and options individually const method = "user"; const encryptedMethod = encryptionHandler("encrypt", method); // method = user const encryptedAction = encryptionHandler("encrypt", action); // action = storeAvatar // Include base64 Image in the options - not separately const options = new URLSearchParams({ phone, avatar: base64Image, deviceHash, }).toString(); const encryptedOptions = encryptionHandler("encrypt", options); // Create formData const formData = new FormData(); // Let's add our fields formData.append("action", encryptedAction); formData.append("method", encryptedMethod); formData.append("options", encryptedOptions); // Send POST request to the server const res = await axios.post( "https://mobileapp.confidencemp.ng/app/index.aspx", formData, { headers: { Authorization: `Bearer ${apiKey}`, }, } ); const data = await res?.data; if (data) { const result = encryptionHandler("decrypt", data); const parsedResult = JSON.parse(result); // You can adjust this part, in your code it was >= . . . if (parsedResult.status == 200) { setUploadSuccess(parsedResult.data.filePath); showMessage({ message: parsedResult.details, type: "success", }); } else { showMessage({ message: parsedResult.details, type: "danger", }); } } } catch (error) { if (axios.isAxiosError(error)) { console.log("Axios error", { code: error.code, message: error.message, config: error.config, }); } else { console.log("Unexpected error", error); } } };
5 - Code Samples - Core Banking (CONFIDENCE)
5.1 (CONFIDENCE) Create Bank Account
Use this (UPDATED) endpoint to create a Confidence Bank account for users on the platform, and store bvn information.
Notice we use the accounts
method here, and not user
. The account name will be the user's first and last names stored in the db.
NOTE - We will require the deviceHash to authenticate these requests as well.
VERY IMPORTANT - If ths user's phone number has been used to create a confidence bank account with another name, the account number and bank name will be returned with you without hassle.
Company policy can be updated on this, but your code very likely would not need to be.
bvnAvatar
is of course the base64 image returned. You have 100gb worth of storage for that, so we should be fine.
const method = 'accounts'; const action = 'createNew'; const options = { 'userTitle': '', 'bvnSurname': '', 'bvnFirstName': '', 'bvnMiddleName': '', 'bvnAvatar': '', 'enrollName': '', 'dob': '', 'maritalStatus': '', 'gender': '', 'nationality': '', 'stateOfOrigin': '', 'lga': '', 'nin': '', 'bvn': '', 'phone': '', 'deviceHash': '<DEVICE_HASH_IS_RETURNED_AFTER_OTP_VERIFICATION_AND_LOGIN_AS_WELL>' }
5.2 (CONFIDENCE) Validate Confidence NUBAN Account
Use this endpoint to verify bank information for any Confidence Bank account numbers used at any point within the user journey in the application. This endpoint does not require a deviceHash
. Think of it as a generic api for verifying bank information for Confidence Bank Accounts.
NOTE: The method here is updated to misc
.
const method = 'misc'; const action = 'verifyConfidenceNUBAN'; const options = { 'accountNumber': '' }
5.3 (CONFIDENCE) Check Account Balance
Retrieve current account balance for a user's bank account. This request requires authentication with the deviceHash
.
const method = 'accounts'; const action = 'balance'; const options = { 'accountNumber': '', 'phone': '', 'deviceHash': '' }
5.4 (CONFIDENCE) Retrieve Account History
API Endpoint to retrieve user account history. You can choose to specify a start and end date, or not.
NOTE - You can choose to specify start date alone, or both, or none, but you can't specify the end date alone.
This request requires authentication with the deviceHash
.
const method = 'accounts'; const action = 'history'; const options = { 'accountNumber': '', 'phone': '', 'deviceHash': '', 'startDate': '<OPTIONAL>', 'endDate': '<OPTIONAL - NEEDS START DATE>' }
5.5 (CONFIDENCE) Get Bank List
API Endpoint to retrieve list of banks. The endpoint returns both bank name and cbn code.
This request does not require authentication with the deviceHash
. Also, no options need be passed.
const method = 'misc'; const action = 'listBanks'; const options = { }
5.6 (CONFIDENCE) Validate Other Bank NUBAN
API Endpoint to validate a bank account from another bank. Bank codes are returned from the bank list api endpoint.
This request does not require authentication with the deviceHash
.
const method = 'misc'; const action = 'verifyNUBAN'; const options = { 'accountNumber': '', 'bankCode': '' }
5.7 (CONFIDENCE) Funds Transfer - Confidence Bank Accounts
API Endpoint to initiate bank transfers from a Confidence Bank Account to another, also of Confidence Bank.
This request requires authentication with the deviceHash
.
const method = 'funds'; const action = 'transfer'; const options = { 'debitAccount': '', 'creditAccount': '', 'creditAccountName': '', 'amount': '', 'narration': '', 'deviceHash': '', 'phone': '' }
5.8 (CONFIDENCE) Interbank Transfers
API Endpoint to initiate bank transfers from a Confidence Bank Account to another bank account from a separate bank.
This request requires authentication with the deviceHash
.
const method = 'funds'; const action = 'interBankTransfer'; const options = { 'debitAccount': '', 'creditAccount': '', 'creditAccountName': '', 'amount': '', 'bankCode': '', 'narration': '', 'deviceHash': '', 'phone': '' }
5.9 (NIBSS) BVN Validation
This endpoint aspires to continue the bvn validation from the point left off by the mobile app
This request requires authentication with the deviceHash
.
const method = 'accounts'; const action = 'bvnValidation'; const options = { 'bvn': '', 'code': '', 'deviceHash': '', 'phone': '' }
6 - Code Samples - VAS (DATA)
6.1 (VAS) Get Data Plans
Use this endpoint to retreive data plans. Please note the method here is also misc
.
NOTE - Providers can be MTNDATA
, AIRTELDATA
, 9MOBILEDATA
, GLODATA
.
Also, this request does not require authentication with the deviceHash
.
const method = 'misc'; const action = 'dataList'; const options = { 'provider': '' }
6.2 (VAS) Data Purchase
Use this endpoint to make data purchase. Please note the method here is also misc
.
NOTE - The data required in the options here are returned from the data plans endpoint 6.1
above.
Also, this request requires authentication with the deviceHash
, for obvious reasons.
const method = 'misc'; const action = 'dataPurchase'; const options = { 'amountCharged': '', 'dataCode': '', 'dataType': '', 'provider': '', 'network': '', 'recipientPhone': '', 'phone': '', 'deviceHash': '' }
6.3 (VAS) Airtime Purchase
Use this endpoint to purchase airtime.
Network can be MTN
, AIRTEL
, GLO
, 9MOBILE
Service is the format: MTNVTU
, etc.
Lastly, this request requires authentication with the deviceHash
.
const method = 'misc'; const action = 'airtimePurchase'; const options = { 'amountCharged': '', 'service': '', 'airtimeAmount': '', 'network': '', 'recipientPhone': '', 'phone': '', 'deviceHash': '' }
6.4 (VAS) Fetch Electricity Plans
Use this endpoint to fetch electricity plans.
No options are needed for this endpoint
This request requires no authentication with the deviceHash
.
const method = 'misc'; const action = 'electricityPlans'; const options = { }
6.5 (VAS) Verify Meter Number
Use this endpoint to verify a user's meter number and provider.
The point here is to get the user's name. I have provided you with test data.
No authentication needed.
const method = 'misc'; const action = 'verifyMeter'; const options = { 'type': 'enugu_electric_prepaid', 'meterNumber': '45028635618' }
6.6 (VAS) Purchase Electricity
Use this endpoint to purchase electricity.
Required data is gotten from 6.4
const method = 'misc'; const action = 'electricityPurchase'; const options = { 'amountCharged': '', 'type': '', 'disco': '', 'meterNumber': '', 'electricityAmount': '', 'recipientPhone': '', 'phone': '', 'deviceHash': '' }
6.7 (VAS) Get CableTV Providers
Use this endpoint to get cable tv providers. You need to get the providers first, then use the data here to fetch plans from each provider.
const method = 'misc'; const action = 'cableTVProviders'; const options = { }
6.8 (VAS) Get CableTV Plans (specific provider)
Use this endpoint to get cable tv packages from a specific provider.
You are expected to have gotten the providers from 6.7
above.
const method = 'misc'; const action = 'cableTVPlans'; const options = { 'type': 'startimes' }
6.9 (VAS) CableTV Purchase
Use this endpoint to get purchase a cable tv subscription.
const method = 'misc'; const action = 'cableTVPurchase'; const options = { 'amountCharged': '', 'type': '', 'code': '', 'smartCardNo': '', 'phone': '', 'deviceHash': '' }
7 - Author
This software was written by Olajide Promise Olakunle for Confidence Bank, Nigeria. Currently managed by Olajide Promise Olakunle - Confidence Bank, Nigeria.