By: James Olaogun
Remote work is becoming the new norm, and businesses and their stakeholders now have the flexibility to operate from virtually any corner of the globe. However, this advancement comes with its challenges, such as effectively managing staff compensation and payroll, considering the diverse regulations and currencies in play across different locations. For example, have you ever wondered how platforms like remote.com solved this challenge and effortlessly continue to handle the payroll for their distributed workforce? The answer lies in cutting-edge technology, and one such awesome technology is the Rapyd Disburse API.
This article explores the world of payroll innovation and explains how to develop a basic payroll web application that can add an employee profile and set up their salary details. The article also demonstrates how to integrate the Rapyd Disburse API, a tool that empowers developers to create seamless payroll applications, into the payroll web application.
Implementing Payroll Payment Processing with Rapyd Disburse
The following diagram is an overview of the basic architecture of the payroll web application you're going to build.
The app has a user interface that displays the employee's profile, bank details, and salary and the employee status toggle. You'll develop the user interface using Next.js, a frontend technology framework. Additionally, you'll leverage the Next.js server API capability as the backend to handle the application logic and Rapyd Disburse API. Finally, there is the database, which will handle all relevant data storage. For simplicity, you can make use of the browser's local storage.
Prerequisites
To get started with the step-by-step instructions, kindly ensure you have the necessary prerequisites in place:
Next.Js: This tutorial uses Next.js and the browser's local storage as the database.
Rapyd Account: If you don't already have one, create an account on the Rapyd platform to access the Disburse API documentation and API keys.
NPM: Ensure you have NPM installed as you'll use it to install the necessary packages for the application.
Set Up a New Next.js Application
Let's get started by creating a new Next.js project. To do that, open your terminal, change the directory to your preferred directory, and run the following commands in your terminal:
npx create-next-app rapyd-basic-payroll-app && cd rapyd-basic-payroll-app
You should see a few prompts. Select the default option by punching the enter key command until the package starts downloading. After a successful download, you should get a response starting with "Success!", which indicates where the app was created and displays some other notices (new versions available, etc.).
Create the Necessary Pages and Components
The next step is to create the necessary components in the /src/components
directory. To do that, head over to your terminal and run the following command:
mkdir src/components
touch src/components/Header.js
touch src/components/EmployeeForm.js
touch src/components/EmployeeStatusToggle.js
The command above creates the following files:
- Header.js for a header view of the application
- EmployeeForm.js for the employee profile form
- EmployeeStatusToggle.js for the employee status toggle
You'll now create the necessary pages for routing in the /src/pages
directory by running the following command in your terminal:
mkdir src/pages
touch src/pages/add-employee.js
touch src/pages/list-employees.js
This command creates the following files:
- add-employee.js for adding new employees
- list-employees.js for listing all employees
Code the Components
You've now created some necessary frontend files, and you can start writing their code and styling them appropriately.
Starting with the Header.js component, copy and paste the following code snippet into the Header.js file to create the global header section of the application and style it:
import React from 'react';
const headerStyle = {
backgroundColor: '#cacaca',
color: '#171717',
padding: '20px 0',
textAlign: 'center',
};
const Header = () => {
return (
<header style={headerStyle}>
<h1>Payroll Application</h1>
</header>
);
};
export default Header;
You'll now create and style the form that will be used to create employees. Copy and paste the following code snippet into the EmployeeForm.js file. The code snippet collects the employee's details and stores them in the local storage (see the comments within the code for more information):
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
const EmployeeSalaryForm = () => {
const router = useRouter();
// List of countries for test purposes
const countryCodes = [
{ code: 'US', name: 'United States (US)' },
{ code: 'GB', name: 'United Kingdom (GB)' },
{ code: 'PH', name: 'Philippines (PH)' },
{ code: 'CA', name: 'Canada (CA)' },
{ code: 'AU', name: 'Australia (AU)' },
{ code: 'DE', name: 'Germany (DE)' },
{ code: 'FR', name: 'France (FR)' },
{ code: 'JP', name: 'Japan (JP)' },
{ code: 'BR', name: 'Brazil (BR)' },
];
// The form field data before submitting the form
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
address: '',
email: '',
phoneNumber: '',
countryCode: '',
company: '',
role: '',
employmentDate: '',
idCardType: '',
idCardNumber: '',
bankName: '',
bankAccountNumber: '',
salaryAmount: '',
status: 'active',
});
const [AvailableBanks, setAvailableBanks] = useState([]);
// Function to get the list of Rapyd-supported banks based on the selected country
const getCountryCodeBanks = async (value) => {
try {
fetch('/api/get-banks', {
method: 'POST',
body: JSON.stringify({
country: value,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
var resData = resJson.data.body.data
var resDataArr = [];
resData.forEach(eachData => {
resDataArr.push({code: eachData.payout_method_type, name: eachData.name});
});
setAvailableBanks(resDataArr)
});
} catch (error) {
console.error("Error in API route");
}
};
// Function to update the form data when data is inputted into any of the fields
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
if (name == 'countryCode') {
getCountryCodeBanks(value);
}
};
// Function to handle form submit
const handleSubmit = (e) => {
e.preventDefault();
// Retrieve existing employee data from localStorage
const storedEmployeeData = JSON.parse(localStorage.getItem('employeeData')) || [];
// Append the new employee data to the existing array
const updatedEmployeeData = [...storedEmployeeData, formData];
// Save the updated data back to localStorage
localStorage.setItem('employeeData', JSON.stringify(updatedEmployeeData));
// Clear the form after submission
setFormData({
firstName: '',
lastName: '',
address: '',
email: '',
phoneNumber: '',
countryCode: '',
company: '',
role: '',
employmentDate: '',
idCardType: '',
idCardNumber: '',
bankName: '',
bankAccountNumber: '',
salaryAmount: '',
status: 'active',
});
// Redirect the user to the employee listing page
router.push('/list-employees');
window.alert('Employee added successfully');
};
// Begin form styles
const formContainerStyle = {
maxWidth: '400px',
margin: '0 auto',
padding: '20px',
border: '1px solid #ccc',
borderRadius: '5px',
};
const inputStyle = {
display: 'block',
margin: '10px 0',
padding: '5px',
width: '100%',
};
const buttonStyle = {
backgroundColor: '#007bff',
color: 'white',
border: 'none',
padding: '10px 20px',
margin: '20px 0',
cursor: 'pointer',
};
// End form styles
//Form fields
return (
<div style={formContainerStyle}>
<form onSubmit={handleSubmit}>
<label>
First Name:
<input
type="text"
name="firstName"
value={formData.firstName}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Last Name:
<input
type="text"
name="lastName"
value={formData.lastName}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Full Address:
<input
type="text"
name="address"
value={formData.address}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Email:
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Phone Number:
<input
type="text"
name="phoneNumber"
value={formData.phoneNumber}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Country Code:
<select
name="countryCode"
value={formData.countryCode}
onChange={handleChange}
style={inputStyle}
>
<option value="">Select Country Code</option>
{countryCodes.map((country, index) => (
<option key={index} value={country.code}>
{country.name}
</option>
))}
</select>
</label>
<label>
Company:
<input
type="text"
name="company"
value={formData.company}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Role:
<input
type="text"
name="role"
value={formData.role}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Employment Date:
<input
type="date"
name="employmentDate"
value={formData.employmentDate}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Identification Card Type:
<select
name="idCardType"
value={formData.idCardType}
onChange={handleChange}
style={inputStyle}
>
<option value="">Select ID Type</option>
<option value="social_security">Social Security</option>
<option value="work_permit">Work Permit</option>
<option value="international_passport">International Passport</option>
<option value="residence_permit">Residence Permit</option>
<option value="company_registered_number">company Registered Number</option>
</select>
</label>
<label>
Identification Card Number:
<input
type="text"
name="idCardNumber"
value={formData.idCardNumber}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Bank:
<select
name="bankName"
value={formData.bankName}
onChange={handleChange}
style={inputStyle}
>
<option value="">Select Bank</option>
{AvailableBanks.map((bank, index) => (
<option key={index} value={bank.code}>
{bank.name}
</option>
))}
{/* Get the list of available banks in the selected country from Rapyd */}
</select>
</label>
<label>
Bank Account Number:
<input
type="text"
name="bankAccountNumber"
value={formData.bankAccountNumber}
onChange={handleChange}
style={inputStyle}
/>
</label>
<label>
Salary Amount:
<input
type="text"
name="salaryAmount"
value={formData.salaryAmount}
onChange={handleChange}
style={inputStyle}
/>
</label>
<button type="submit" style={buttonStyle}>Submit</button>
</form>
</div>
);
};
export default EmployeeSalaryForm;
Finally, in the components folder, copy and paste the following code snippet into the EmployeeStatusToggle.js component, which manages the enable and disable feature of the application:
import React, { useState } from 'react';
const EmployeeStatusToggle = ({ initialStatus, onToggle }) => {
const [status, setStatus] = useState(initialStatus);
const toggleStyle = {
display: 'inline-block',
position: 'relative',
width: '40px',
height: '20px',
cursor: 'pointer',
backgroundColor: 'lightgrey',
borderRadius: '10px',
};
const switchStyle = {
position: 'absolute',
width: '20px',
height: '20px',
borderRadius: '50%',
transition: 'background-color 0.3s ease',
};
const activeSwitchStyle = {
...switchStyle,
left: '0',
backgroundColor: '#2ecc71', // Green for active status
};
const inactiveSwitchStyle = {
...switchStyle,
left: '20px',
backgroundColor: '#e74c3c', // Red for inactive status
};
const handleToggle = () => {
const newStatus = status === 'active' ? 'inactive' : 'active';
setStatus(newStatus);
onToggle(newStatus);
};
return (
<label style={toggleStyle} className="employee-status-toggle">
<span
style={status === 'active' ? activeSwitchStyle : inactiveSwitchStyle}
onClick={handleToggle}
></span>
</label>
);
};
export default EmployeeStatusToggle;
Code the Pages
With the components coded, you can proceed to code the created pages. Each page will import its respective components accordingly and display them on the browser. Also, note that the name of each page is the web route to access the page on the browser.
Start by copying and pasting the following code into the file add-employee.js to import EmployeeForm.js and Header.js:
import React from 'react';
import '../app/globals.css'
import Header from '../components/Header';
import EmployeeForm from '../components/EmployeeForm';
const AddEmployee = () => {
const containerStyle = {
textAlign: 'center'
};
return (
<div style={containerStyle}>
<Header />
<h2>Add Employee</h2>
<EmployeeForm />
</div>
);
};
export default AddEmployee;
Copy and paste the following code snippet into the list-employees.js file to import the Header.js and EmployeeStatusToggle.js components and a table element that displays all the created employees:
import React, { useEffect, useState } from 'react';
import Header from '../components/Header';
import EmployeeStatusToggle from '../components/EmployeeStatusToggle';
const ListEmployees = () => {
const [employeeData, setEmployeeData] = useState([]);
useEffect(() => {
// Retrieve employee data from localStorage on component mount
const storedEmployeeData = JSON.parse(localStorage.getItem('employeeData')) || [];
setEmployeeData(storedEmployeeData);
}, []);
const handleStatusToggle = (index, newStatus) => {
// Update the status of the employee at the specified index
const updatedEmployeeData = [...employeeData];
updatedEmployeeData[index].status = newStatus;
setEmployeeData(updatedEmployeeData);
// Update the data in localStorage
localStorage.setItem('employeeData', JSON.stringify(updatedEmployeeData));
};
const containerStyle = {
textAlign: 'center'
};
const tableStyle = {
borderCollapse: 'collapse',
width: '100%',
marginTop: '20px',
};
const thStyle = {
backgroundColor: '#cacaca',
color: '#171717',
padding: '10px',
textAlign: 'center',
};
const tdStyle = {
border: '1px solid #ccc',
padding: '10px',
textAlign: 'left',
};
return (
<div style={containerStyle}>
<Header />
<h2>List of Employees</h2>
<table style={tableStyle}>
<thead>
<tr>
<th style={thStyle}>Name</th>
<th style={thStyle}>Email</th>
<th style={thStyle}>Phone</th>
<th style={thStyle}>Country Code</th>
<th style={thStyle}>Company</th>
<th style={thStyle}>Role</th>
<th style={thStyle}>Employment Date</th>
<th style={thStyle}>ID Card Type</th>
<th style={thStyle}>ID Card Number</th>
<th style={thStyle}>Bank Name</th>
<th style={thStyle}>Bank Account Number</th>
<th style={thStyle}>Salary Amount</th>
<th style={thStyle}>Status</th>
</tr>
</thead>
<tbody>
{employeeData.map((employee, index) => (
<tr key={index}>
<td style={tdStyle}>{employee.firstName} {employee.lastName}</td>
<td style={tdStyle}>{employee.email}</td>
<td style={tdStyle}>{employee.phoneNumber}</td>
<td style={tdStyle}>{employee.countryCode}</td>
<td style={tdStyle}>{employee.company}</td>
<td style={tdStyle}>{employee.role}</td>
<td style={tdStyle}>{employee.employmentDate}</td>
<td style={tdStyle}>{employee.idCardType}</td>
<td style={tdStyle}>{employee.idCardNumber}</td>
<td style={tdStyle}>{employee.bankName}</td>
<td style={tdStyle}>{employee.bankAccountNumber}</td>
<td style={tdStyle}>{employee.salaryAmount}</td>
<td style={tdStyle}>
<EmployeeStatusToggle
initialStatus={employee.status}
onToggle={(newStatus) => handleStatusToggle(index, newStatus)}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ListEmployees;
After adding the code snippet, go to src/app/globals.css
and src/app/page.module.css
. Replace all the CSS styles that came with the Next.js installation with the following CSS:
body{
margin: 0px;
}
You also need to replace the default src/app/page.js
code with the following snippet, which creates the landing page of the application that routes the users to the "Add Employee" and "List Employees" pages:
import React from 'react';
import Header from '../components/Header';
const Home = () => {
const containerStyle = {
textAlign: 'center'
};
const actionUrlsStyle = {
listStyle: 'none',
padding: 0,
display: 'flex',
justifyContent: 'center',
};
const actionUrlItemStyle = {
margin: '0 10px',
};
return (
<div style={containerStyle}>
<Header />
<h2>Welcome</h2>
<h3> Use the navigation below to add or view employees</h3>
<ul style={actionUrlsStyle} className="action-urls">
<li style={actionUrlItemStyle} className="action-url-item">
<a href={`/add-employee`}>Add Employee</a>
</li>
<li style={actionUrlItemStyle} className="action-url-item">
<a href={`/list-employees`}>List Employees</a>
</li>
</ul>
</div>
);
};
export default Home;
Before moving on to the next step, you also need to create a helper file that will contain the list of currency codes and their corresponding country code. This helper file will help you automatically determine the payout currency based on the selected beneficiary country.
Change the directory to /src
and create a new directory there called utilities
using the following command:
mkdir utilities
Then, create a new file called currencyCodes.js in the /utilities
directory using the following command:
touch currencyCodes.js
Copy the code snippet below into the newly created currencyCodes.js:
const CurrencyCodes = [
{ code: 'USD', country_code: 'US' },
{ code: 'GBP', country_code: 'GB' },
{ code: 'PHP', country_code: 'PH' },
{ code: 'CAD', country_code: 'CA' },
{ code: 'AUD', country_code: 'AU' },
{ code: 'EUR', country_code: 'DE' },
{ code: 'JPY', country_code: 'JP' },
{ code: 'BRL', country_code: 'BR' }
];
export { CurrencyCodes };
Retrieve Your Rapyd API Keys
At this point, you are done creating and styling the pages for the application. The final steps involve integrating the Rapyd Disburse API into the app. Before working with the API, you'll need your API keys. To locate them, log in to the Rapyd dashboard with your login details.
Once you're logged in, click the Developers link on the sidebar, and you should be routed to the Rapyd Credential Details page. You will see your access and secret key. Copy both keys and save them in a safe place; you will need them in the later part of this tutorial.
Integrate the Rapyd Disburse API (Set Up Base Functions)
The next step is to create another helper file for the functions that set up the Rapyd signature and header data. To do that, change the directory to the /utilities
and create a file called rapyd.js using the following command:
cd /path/to/app/src/utilities
touch rapyd.js
Copy and paste the code snippet below into the newly created rapyd.js file, replacing {{YOUR_SECRET_KEY}}
and {{YOUR_ACCESS_KEY}}
with your Rapyd secret key and access key to generate the required header, salt, and signature (see the comments within the code for more information about what's happening):
import https from 'https';
import crypto from 'crypto';
const secretKey = "{{YOUR_SECRET_KEY}}";
const accessKey = "{{YOUR_ACCESS_KEY}}";
const log = false;
async function makeRequest(method, urlPath, body = null) {
try {
const httpMethod = method; // get|put|post|delete - must be lowercase.
const httpBaseURL = "sandboxapi.rapyd.net";
const httpURLPath = urlPath; // Portion after the base URL.
const salt = generateRandomString(8); // Randomly generated for each request.
const idempotency = new Date().getTime().toString();
const timestamp = Math.round(new Date().getTime() / 1000); // Current Unix time (seconds).
const signature = sign(httpMethod, httpURLPath, salt, timestamp, body);
const options = {
hostname: httpBaseURL,
port: 443,
path: httpURLPath,
method: httpMethod,
headers: {
'Content-Type': 'application/json',
access_key: accessKey,
salt: salt,
timestamp: timestamp,
signature: signature,
idempotency: idempotency,
},
};
return await httpRequest(options, body, log);
} catch (error) {
console.error("Error generating request options", error);
throw error;
}
}
function sign(method, urlPath, salt, timestamp, body) {
try {
let bodyString = "";
if (body) {
bodyString = JSON.stringify(body); // Stringified JSON without whitespace.
bodyString = bodyString == "{}" ? "" : bodyString;
}
let toSign =
method.toLowerCase() +
urlPath +
salt +
timestamp +
accessKey +
secretKey +
bodyString;
log && console.log(`toSign: ${toSign}`);
let hash = crypto.createHmac('sha256', secretKey);
hash.update(toSign);
const signature = Buffer.from(hash.digest("hex")).toString("base64");
log && console.log(`signature: ${signature}`);
return signature;
} catch (error) {
console.error("Error generating signature");
throw error;
}
}
function generateRandomString(size) {
try {
return crypto.randomBytes(size).toString('hex');
} catch (error) {
console.error("Error generating salt");
throw error;
}
}
async function httpRequest(options, body) {
return new Promise((resolve, reject) => {
try {
let bodyString = "";
if (body) {
bodyString = JSON.stringify(body);
bodyString = bodyString == "{}" ? "" : bodyString;
}
log && console.log(`httpRequest options: ${JSON.stringify(options)}`);
const req = https.request(options, (res) => {
let response = {
statusCode: res.statusCode,
headers: res.headers,
body: '',
};
res.on('data', (data) => {
response.body += data;
});
res.on('end', () => {
response.body = response.body ? JSON.parse(response.body) : {};
log && console.log(`httpRequest response: ${JSON.stringify(response)}`);
if (response.statusCode !== 200) {
return reject(response);
}
return resolve(response);
});
});
req.on('error', (error) => {
return reject(error);
});
req.write(bodyString);
req.end();
} catch (err) {
return reject(err);
}
});
}
export { makeRequest };
This snippet also contains the generic HTTP functions that help consume all API endpoints. All you have to do to consume any API endpoint is pass the API endpoint URL, method, and payload into the function.
It's always best practice to add your {{YOUR_SECRET_KEY}}
and {{YOUR_ACCESS_KEY}}
via environment variables, especially when integrating into real-world applications.
Integrate the Payout Method Types API
Before you can send out the payment, you need to check that the employee's bank is supported by Rapyd. You can do this by confirming which payout methods are available in their country when adding employees.
To do that, you first need to create the /api
route by changing the directory and creating the /api
directory using the following command:
cd /path/to/app/src/pages
mkdir api
Create a new file called get-banks.js using the touch get-banks.js command. Then, copy and paste the code snippet below into this file to allow it to consume the Payout Method Types API:
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
import {CurrencyCodes} from '../../utilities/currencyCodes.js' // Import currency and country codes
export default async function handler(req, res) { //Function to handle Next.js request
const country = req.body.country;
var currency
// Get the selected country currency code
CurrencyCodes.forEach(curcode => {
if (curcode.country_code == country) {
currency = curcode.code;
}
});
// Set the Rapyd API URL
const url = '/v1/payouts/supported_types?category=bank&beneficiary_country='+country+'&payout_currency='+currency;
//Make the API request to Rapyd
const response = await makeRequest('get', url, {});
// Return the request response
res.status(200).json({ data: response })
}
Generate, Fund, and Retrieve Rapyd E-wallet ID
The Rapyd e-wallet can be created via your Rapyd Client Portal or API. In this tutorial, you'll use the API method to create the e-wallet.
Change the directory to /api
using the following command:
cd /path/to/app/src/page/api
Use the touch create-wallet.js
command to create a new file called create-wallet.js. Then, copy and paste the following code snippet into the file so it can consume the Create Wallet API (make sure you fill in all the relevant details in the placeholders):
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
export default async function handler(req, res) { //Function to handle Next.js request
const body = {
first_name: '{{Your First Name}}', //Your First Name
last_name: '{{Your Last Name}}', //Your Last Name
ewallet_reference_id: '{{Your Preferred Ref ID}}', //Your Preferred Ref ID
metadata: {
merchant_defined: true
},
type: 'company',
contact: {
phone_number: '{{Your Phone Number}}', //Your Phone Number
email: '{{Your Email}}', //Your Email
first_name: '{{Your First Name}}', //Your First Name
last_name: '{{Your Last Name}}', // Your Last Name
mothers_name: '{{Your Mothers Name}}', // Your Mother's Name
contact_type: 'business',
address: { //Your Address
name: '{{Name}}',
line_1: '{{Line 1}}',
line_2: '',
line_3: '',
city: '{{City}}',
state: '{{Sate}}',
country: '{{Country}}',
zip: '{{Zip}}',
phone_number: '{{Phone}}',
metadata: { number: 2 },
canton: '',
district: ''
},
identification_type: '{{Your Identification Type}}', //Your Identification Type e.g "PA",
identification_number: '{{Your Identification Number}}', //Your Identification Number e.g "1234567890"
date_of_birth: '{{Your Date of Birth}}', //Your Date of Birth
country: '{{Your Country}}', // Your Country
metadata: {
merchant_defined: true
},
business_details: {
entity_type: 'association',
name: "{{Your Company Name}}", //Your Company Name
registration_number: "{{Your company registration number}}", //Your company registration number
industry_category: 'company',
industry_sub_category: "{{Your business category}}", //Your business category
address: { //Your Address
name: '{{Name}}',
line_1: '{{Line 1}}',
line_2: '',
line_3: '',
city: '{{City}}',
state: '{{Sate}}',
country: '{{Country}}',
zip: '{{Zip}}',
phone_number: '{{Phone}}',
metadata: {
merchant_defined: true
}
}
}
}
};
// Set the Rapyd API URL
const url = '/v1/user';
//Make the API request to Rapyd
const response = await makeRequest('POST', url, body);
// Return the request response
res.status(200).json({ data: response })
}
Using touch fund-wallet.js
, create a new file called fund-wallet.js. Copy and paste in the following code to fund the wallet you just created with a fixed amount, where {{YOUR WALLET ID}}
is the ID of the wallet you just created:
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
export default async function handler(req, res) { //Function to handle Next.js request
const data = {
"amount": 500000, //fixed amount of 500,000
"currency": "USD",
"ewallet": "{{YOUR WALLET ID}}",
"metadata": {
"merchant_defined": true
}
}
const url = '/v1/account/deposit'
const response = await makeRequest('POST', url, data);
// Return the request response
res.status(200).json({ data: response })
}
Change the directory to /components
using the following command:
cd /path/to/app/src/components
Create a new file called WalletDetails.js using the touch WalletDetails.js
command. Then, copy and paste in the following code snippet, which shows the e-wallet ID if one exists or presents a call to action to create an e-wallet when clicked if one doesn't:
import React, { useState, useEffect } from 'react';
const style = {
cursor: 'pointer',
color: '#2525aa',
}
const WalletDetails = () => {
const [WalletID, setWalletID] = useState("");
useEffect(() => {
const WalletIDLSDAta = localStorage.getItem('WalletID') || "";
setWalletID(WalletIDLSDAta);
}, []);
// Function to generate wallet ID
const generateWalletId = async () => {
try {
fetch('/api/create-wallet', {
method: 'GET',
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
var resData = resJson.data.body.data.id
setWalletID(resData);
localStorage.setItem('WalletID', resData);
});
} catch (error) {
console.error("Error in API route");
}
};
// Fund wallet with a fixed amount
const fundWallet = async () => {
try {
fetch('/api/fund-wallet', {
method: 'GET',
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
alert("Fixed amount added successfully")
});
} catch (error) {
console.error("Error in API route");
}
};
return (
<div>
{WalletID == "" ?
<small onClick={generateWalletId} style={style}>Generate Wallet ID</small> :
<div>
<small>{WalletID}</small>
<br></br>
<small onClick={fundWallet} style={style}>Fund Wallet</small>
</div>
}
</div>
);
};
export default WalletDetails;
Integrate the Rapyd Payouts APIs (Create Payout, Confirm Payout, and Complete Payout)
You'll now add the necessary code to consume the Rapyd Payouts APIs and ensure a successful payout. The Create Payout endpoint initiates a payout request. If the payout request is between multiple currencies, it will require that you confirm the foreign exchange by consuming the Confirm Payout endpoint. Once it has been done and the payout has a status of "Created", you can proceed to consume the Complete Payout endpoint to finalize the payout.
Start by changing the directory to /api
directory using the following command:
cd /path/to/app/src/page/api
Afterward, use touch request-payout.js
to create a new file called request-payout.js. Then, copy and paste the following snippet into the file so it can consume the Create Payout API:
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
import {CurrencyCodes} from '../../utilities/currencyCodes.js' // Import currency and country codes
export default async function handler(req, res) { //Function to handle Next.js request
var payout_currency
// Get the selected country currency code
CurrencyCodes.forEach(curcode => {
if (curcode.country_code == req.body.country) {
payout_currency = curcode.code;
}
});
const body = {
"beneficiary": {
"payment_type": "regular",
"address": "1 Main Street", //beneficiary Address
"city": "Anytown", //beneficiary City
"country": req.body.country, //beneficiary Country Code
"first_name": req.body.first_name,
"last_name": req.body.last_name,
"state": "NY", //beneficiary State
"postcode": "10101", //beneficiary Postcode
"aba": "573675777", //for US bank account
"iban": "DE75512108001245126199", //Customer Iban
"account_number": req.body.account_number,
"identification_type": req.body.identification_type,
"identification_value": req.body.identification_value,
"phone_number": req.body.phone_number,
},
"beneficiary_country": req.body.country,
"beneficiary_entity_type": "individual",
"description": "Salary payout - wallet to bank account", //Your Secription
"payout_method_type": req.body.bank,
"ewallet": "ewallet_8ce9f7a26b296c98ed2ee32028bead0a", //Your Wallet ID
"metadata": {
"merchant_defined": true
},
"payout_amount": req.body.amount,
"payout_currency": payout_currency,
"confirm_automatically": "true",
"sender": {
"first_name": "John", //Your first name
"last_name": "Doe", // Yout last name
"identification_type": "work_permit", //Your ID Type
"identification_value": "asdasd123123", //Your ID number
"phone_number": "19019019011", //Your phone number
"occupation": "professional", //Your occupation
"source_of_income": "business",
"date_of_birth": "11/12/1913", //Your DOB
"address": "1 Main Street", //Your Address
"postcode": "12345", //Your Postcode
"country": "US",
"city": "Anytown",//Your City
"state": "NY",//Your State
"purpose_code": "investment_income",
"beneficiary_relationship": "employee"
},
"sender_country": "US", //Your Country
"sender_currency": "USD", //Your Currency
"sender_entity_type": "individual",
"statement_descriptor": "Salary payout" //Unstructured remittance information
}
// Set the Rapyd API URL
const url = '/v1/payouts';
//Make the API request to Rapyd
const response = await makeRequest('POST', url, body);
// Return the request response
res.status(200).json({ data: response })
}
Next, create a new file called confirm-payout.js using the touch confirm-payout.js
command. Copy and paste the code snippet below to the file to allow it to consume the Confirm Payout API:
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
export default async function handler(req, res) { //Function to handle Next.js request
// Set the Rapyd API URL
const payoutID = req.body.payoutID;
const url = '/v1/payouts/confirm/'+payoutID;
//Make the API request to Rapyd
const response = await makeRequest('POST', url, {});
// Return the request response
res.status(200).json({ data: response })
}
Lastly, create a new file called complete-payout.js using the touch complete-payout.js
command. Copy and paste the code snippet below into the file to allow it to consume the Complete Payout API:
import { makeRequest } from '../../utilities/rapyd.js'; // Import Rapyd base functions
export default async function handler(req, res) { //Function to handle Next.js request
// Set the Rapyd API URL
const payoutID = req.body.payoutID;
const amount = req.body.amount
const url = '/v1/payouts/complete/'+payoutID+'/'+amount;
//Make the API request to Rapyd
const response = await makeRequest('POST', url, {});
// Return the request response
res.status(200).json({ data: response })
}
You could alternatively use the Complete Payout webhook to complete the payment, but for the purpose of simplicity, this tutorial uses only the API.
Go back to /pages/list-employees.js
and import code snippet below the EmployeeStatusToggle
import :
import WalletDetails from '../components/WalletDetails';
import PayOut from '../components/PayOut';
Add <WalletDetails />
to the line after the <h2>
tag. Additionally, add a new table head and body for the PayOut
action using the code snippet below:
<th style={thStyle}>Action</th>
<td style={tdStyle}>
<PayOut
employee={employee}
/>
</td>
You then need to create the PayOut
component. Change to the component directory cd /path/to/app/src/components
and use touch PayOut.js
to create a new file. Copy and paste the code snippet below into the file to enable the functions that trigger the payout API files you coded in the previous paragraph:
import React, { useState } from 'react';
const PayOut = ({ employee }) => {
const style = {
backgroundColor: '#007BFF',
color: '#FFFFFF',
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
fontSize: '16px'
}
const [payoutText, setPayoutText] = useState("Payout");
// Function to confirm FX and complete payout
const CompletePayout = async (resData) => {
try {
fetch('/api/complete-payout', {
method: 'POST',
body: JSON.stringify({
payoutID: resData.id,
amount: resData.sender_amount,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
var resData = resJson.data.body
console.log("complete-payout", resData)
setPayoutText("Paid")
});
} catch (error) {
console.error("Error in API route");
}
};
// Function to confirm FX and complete payout
const confirmFXandCompletePayout = async (resData) => {
try {
fetch('/api/confirm-payout', {
method: 'POST',
body: JSON.stringify({
payoutID: resData.id,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
var confirmResData = resJson.data.body.data
CompletePayout(confirmResData)
console.log("confirmed-fx", confirmResData)
});
} catch (error) {
console.error("Error in API route");
}
};
const handleClick = () => {
setPayoutText("Paying...")
try {
fetch('/api/request-payout', {
method: 'POST',
body: JSON.stringify({
country: employee.countryCode,
bank: employee.bankName,
amount: employee.salaryAmount,
first_name: employee.firstName,
last_name: employee.lastName,
account_number: employee.bankAccountNumber,
identification_type: employee.idCardType,
identification_value: employee.idCardNumber,
phone_number: employee.phoneNumber,
}),
headers: {
'Content-type': 'application/json; charset=UTF-8',
}
})
.then((response) => response.json())
.then((resJson) => {
var resData = resJson.data.body.data
switch (resData.status) {
case "Confirmation":
condirmFXandCompletePayout(resData)
break;
case "Created":
CompletePayout(resData)
break;
case "Hold":
setPayoutText("Hold")
break;
case "Expired":
setPayoutText("Expired")
break;
case "Pending":
setPayoutText("Pending")
break;
case "Canceled":
setPayoutText("Canceled")
break;
case "Completed":
setPayoutText("Paid")
break;
case "Declined":
setPayoutText("Declined")
break;
case "Error":
setPayoutText("Error")
break;
default:
setPayoutText("Error")
break;
}
console.log("request-payout", resData)
});
} catch (error) {
console.error("Error in API route");
}
};
return (
<div>
<button style={style} onClick={handleClick}>{payoutText}</button>
</div>
);
};
export default PayOut;
Test the Application
Now that you've created all the necessary components and pages and integrated the Rapyd APIs, it's time to demo the application. Start by running the application with the npm run dev
command. You should see a response like the screenshot below.
You should also see the server URL. In the example, it's http://localhost:3000
. Visit the URL and you should see a "Payroll Application" page.
Click Add Employee to add a new employee.
When you are done filling out the form, click Submit and you'll see an alert indicating that an employee was added successfully.
After you acknowledge the alert, you will be redirected to the "List of Employees" page.
Go back to the "Add Employee" page and add another employee.
You can now try generating and funding a wallet. You can create as many wallets as necessary for the complexity of your application. However, for the purpose of this tutorial, you will be creating and using only one wallet.
Click Generate Wallet ID under the "List of Employees" header to create your wallet. After the wallet has been created successfully, you should see the wallet ID and a link to fund the wallet.
Fund the wallet with a fixed amount of US$500,000 by clicking Fund Wallet. You should see an alert saying "Fixed amount added successfully".
Finally, try paying the two employees by clicking Payout on the right side of the table. When the payout function is processed, the text should change to "Paying…", and it will change to "Paid" when the payout is completed. Also, note that it can show a different status, such as "Hold", "Expired", or "Declined", depending on the bank option that was selected during employee creation. See the official documentation for the list of the status and their meaning. To simulate a successful transaction, for Germany, you can select Eurozone SEPA payout, and for the Philippines, you can select Bank Transfer to ANZ Bank in the Philippines.
Conclusion
This article provided a brief introduction to Rapyd's Disburse API and a step-by-step tutorial for incorporating them into a simple payroll application using Next.js. You can view your payout history and transactions, switch between the production and sandbox environments, and more through your Client Portal. The complete code for this tutorial is also available on GitHub.
Rapyd offers a robust and reliable payment gateway solution that is fast and secure for both local and international business transactions. It supports more than nine hundred payment options and more than sixty-five currencies from different countries. Simply register on the Client Portal and follow the Get Started guide to start your integration.
Top comments (0)