DEV Community

Cover image for Payroll Payment Processing with Rapyd Disburse
Drew Harris for Rapyd

Posted on • Edited on • Originally published at community.rapyd.net

Payroll Payment Processing with Rapyd Disburse

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.

Application architecture

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
Enter fullscreen mode Exit fullscreen mode

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.).

New Next.js application

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
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then, create a new file called currencyCodes.js in the /utilities directory using the following command:

touch currencyCodes.js
Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

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.

Rapyd API keys

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
Enter fullscreen mode Exit fullscreen mode

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 };


Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

Change the directory to /components using the following command:

cd /path/to/app/src/components
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

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 })

}
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

Running the application

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.

Application page

Click Add Employee to add a new employee.

Add Employee page

When you are done filling out the form, click Submit and you'll see an alert indicating that an employee was added successfully.

Employee added alert

After you acknowledge the alert, you will be redirected to the "List of Employees" page.

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.

Wallet ID

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".

Fixed amount added successfully alert

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)