Build a Node.js Payment Gateway with Rapyd
Tags: paymentapi, node, tutorial
Published: 2023-01-17 18:14:40

*By James Olaogun*

With the increase of digital payment transactions across the globe, *payment gateways* are becoming a familiar feature of every online shop. Used by clients to accept digital payment methods, payment gateways can usually accommodate local and international credit or debit cards, eWallets, bank transfers, and even sometimes cash.
 
In this article, I’ll walk you through a step-by-step tutorial for integrating a payment gateway called [Rapyd Collect](https://www.rapyd.net/products/payments/api/) into any of your Node.js applications.
 
But before I go into the tutorial, let me tell you a bit about Rapyd Collect.
 
## What is Rapyd Collect?
 
Rapyd Collect is a payment gateway platform that enables businesses and individuals (clients) to accept payment fast, easily, securely, globally, and via multiple channels. Its security while managing sensitive information is assured with a [Level 1 service provider certification](https://docs.rapyd.net/en/information-security.html).
 
While Rapyd supports a variety of payment options, including checkout, invoicing, and subscription, the main emphasis of this tutorial will be Rapyd checkout. It’s the easiest way to accept payments via your website or mobile app, and you can incorporate it as hosted or as a toolkit.
 
The toolkit integration is embedded in your website as an iframe, and the hosted integration sends your clients to a page hosted on Rapyd servers. I'll be using the hosted technique in this tutorial.
 
## What You Need to Start
 
Before we dive into the process of building a Node.js payment gateway, there are a few requirements you must meet:
 
- Basic knowledge of Node.js
- Node installed on your local machine
- Basic knowledge of Express.js
- Basic knowledge of a database (this tutorial uses PostgreSQL, but feel free to use any database of your choice. Just make sure it’s installed on your local machine)
- Knowledge of HTML/CSS/JS
- A [Rapyd Collect Account](https://dashboard.rapyd.net/sign-up)
 
## How to Set Up Node.js
 
Having met all these requirements, let’s get started. 
 
Set up Node.js by creating a new directory called `rapyd-nodejs` in your desired folder. 
 
### 1. Initialize a New Node Application
 
Open your terminal or CLI and run the following command to create and enter the new directory:
 
```
mkdir rapyd-nodejs && cd rapyd-nodejs
```
 
Initialize a new node application from the `rapyd-nodejs` directory.
 
> Note that for the purpose of this tutorial, I’m using node version 16.13.0 and npm version 8.1.0. You can make use of [Node Version Manager (NVM)](https://github.com/nvm-sh/nvm) to manage and use this exact version of node and npm. Follow this [guide](https://www.freecodecamp.org/news/node-version-manager-nvm-install-guide) for a step-by-step tutorial on how to use NVM.
 
Run the following command to initialize a new node application:
 
```
npm init
```
 
The command will also walk you through creating a `package.json` file. It will ask you for the package name, version, description, entry point, test command, git repository, keywords, author, and license.
 
Some of these requests come with a default adoption in brackets. Click **Enter** to go with the default option; the others are blank, but you can add a value or just press **Enter** to leave it blank. You can always edit the `package.json` file.
 
### 2. Install Required Node Packages
 
The required dependencies for this tutorial include `express`, `dotenv`, `pg`, `body-parser`, and `ejs`. Run the following command from the root directory of the application to install the modules:
 
```
npm install express dotenv pg body-parser ejs
```
 
Set up the application directory structure. Create the following directories and files in the root the `rapyd-nodejs` directory:
 
- `/Model`
- `/Controller`
- `/Config`
- `/Views`
- `/Routes/index.js`
- `Public/css`
- `Public/js`
- `/Helpers`
 
Also, create `.env` and `index.js` files (the `index.js` file will be the entry point of the application; it can also be named `app.js` or `main.js`) in the root folder of the application from which the application would be served. Your application directories should now look like the following screenshot:
 
![rapyd-nodejs-directories-screenshot](https://i.imgur.com/VY8cWTO.png)
 
Add the application `PORT` and `NODE_ENV` variable to your `.env` file, as in this example:
 
```
NODE_ENV=development
PORT={{Any_port_number _of_your_choice}}
```
 
Go to `/Config` and create a `server.js` file. Add the block of code below to the `server.js` file:
 
```
const express = require('express');
const app = express();


module.exports = app;
```
 
Add the following block of code to the `index.js` file (at the root of your project):
 
```
const app = require('./Config/server');
const dotEnv = require('dotenv')
 
dotEnv.config()
 
const port = process.env.PORT || {{YOUR_PORT_NUMBER}}; // replace `{{YOUR_PORT_NUMBER}}` with the port number you used in the `.env` file.
app.listen(port, () => {console.log(`App listening on port ${port}`)})
```
 
### 3. Set Up the Development Server
 
Next, install [nodemon](https://www.npmjs.com/package/nodemon) to help monitor, reload, and serve the application when it notices any changes. Run the following command to install nodemon as a dev dependency into the application:
 
```
npm install --save-dev nodemon
```
 
To run the application, open the `package.json` file and add the code block below to the `"scripts": {}` section (that should be line number 6):
 
```
"start": "node index.js",
"dev": "nodemon index.js",
``` 
 
Open your CLI or terminal, change directory to the root folder of the application, and run `npm run dev` to start the application in dev mode. You should see something like the following screenshot:
 
![npm run dev command screenshot](https://i.imgur.com/qcZs3JW.png)
 
## How to Set Up the Database
 
Now it’s time to set up the database connection. As stated in the introduction, I’ll be making use of PostgreSQL. Follow this [guide](https://www.postgresql.org/download/) to install PostgreSQL if you plan to use it, too.
 
### 1. Create the Database Config File
 
To connect the database to the application, create a new file called `db-config.js` in the `/Config` folder. Then add the following code to the `db-config.js` file:
 
```
const dotEnv = require('dotenv');
const postgres = require('pg');
const { Pool } = postgres;
 
dotEnv.config()
 
const dbConfig = {
    user: process.env.DB_USER,
    host: process.env.DB_HOST,
    database: process.env.DB_NAME,
    password: process.env.DB_PASSWORD,
    port: process.env.DB_PORT
}
 
const pool = new Pool(dbConfig)
module.exports = pool;
```
 
### 2. Create a New Database in PostgreSQL
 
Create a new database called `rapyd_node`, set its user and password, and create a table named `checkout_details` with the following column and constraints:
 
```
CREATE TABLE checkout_details(
    id                                  SERIAL PRIMARY KEY,
    full_name                     varchar(255) NOT NULL,
    email                            varchar(255) NOT NULL,
    full_address                  TEXT NOT NULL,
    amount                         DOUBLE PRECISION NOT NULL,
    reference                      varchar(255) NOT NULL,
    product_details            varchar(225) NOT NULL,
    meta_data                    TEXT NULL,
    status		     varchar(11) NOT NULL,	
    created_at                    TIMESTAMP NOT NULL
);

```
Head back to the `.env` and add the database variable. See the following code:
 
```
DB_USER='postgres'
DB_HOST='localhost'
DB_NAME={{database_name}}
DB_PASSWORD={{db_password}}
DB_PORT={{postgres_db_port}}
```
 
Where `{{database_name}}` is `rapyd_node`, `{{db_password}}` is the password set for the database user (the default password is most often an empty string), and `{{postgres_db_port}}` is the database port (the default port number is 5432).
 
### 3. Test the Database Connection
 
To test the database connection, add the following code to the `index.js`:
 
```
const pool = require('./Config/db-config');
var sql = `SELECT * FROM checkout_details`
pool.query(sql, (error, response) => {
        if (error) return console.log(error);
        console.log(response.rows);
      }
    )
```
 
Save the file, and if you’ve deactivated the nodemon server, run `npm run dev`. It should output an empty array to the terminal.
 
## How to Develop the Application Interface
 
Congrats! By now, you’ve set up the node application and database. Next up, add the application interface.
 
### 1. Create the Interface File
 
Go to the `/Views` folder and create a new file called `checkout.ejs`. Add the following code to the file:
 
```
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Product Checkout</title>
        <link rel="stylesheet" href="https://codepen.io/gymratpacks/pen/VKzBEp#0">
        <link href='https://fonts.googleapis.com/css?family=Nunito:400,300' rel='stylesheet' type='text/css'>
        <link rel="stylesheet" href="public/css/style.css">
    </head>
    <body>
      <div class="row">
    <div class="col-md-12">
      <form id="checkoutForm">
        <h1> Product Checkout </h1>
        <h3> 
          Item: <%= product_name %> 
          <br>
          <span class="price"> Price: <%= price %> </span>
         </h3>
        
        <fieldset>
        
          <label for="name">Full Name:</label>
          <input type="text" required id="u_full_name" name="user_full_name">
        
          <label for="email">Email:</label>
          <input type="email" required id="user_email" name="user_email">
       
          <label for="name">Full Address:</label>
          <textarea id="user_full_address" required name="user_full_address"></textarea>
          
        </fieldset>
       
        <button type="submit" id="notloading">Checkout</button>
        <button type="button" style="display: none;" id="loading">Loading......</button>
        
       </form>
      </div>
      </div>
      
    </body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="public/js/ajax-script.js"></script>
</html>
```
 
Create a `/Public` folder in the root directory of the application. In the `/Public` folder, create `/css` and `/js` folders.
 
Go to `/Config/server.js` and add `app.use('/Public', express.static('public'));` above the `module.exports = app;`.
 
In the `/Public/css` folder, create a new stylesheet called `style.css`. Paste the following code into the file:
 
```
*, *:before, *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
  }
  
  body {
    font-family: 'Nunito', sans-serif;
    color: #384047;
  }
  
  form {
    max-width: 300px;
    margin: 30px auto;
    padding: 10px 20px;
    background: #f4f7f8;
    border-radius: 8px;
  }
  
  h1, h2, h3 {
    margin: 0 0 30px 0;
    text-align: center;
  }
  
  input[type="text"],
  input[type="email"],
  input[type="number"],
  textarea,
  select {
    background: rgba(255,255,255,0.1);
    border: none;
    font-size: 16px;
    height: auto;
    margin: 0;
    outline: 0;
    padding: 15px;
    width: 100%;
    background-color: #e8eeef;
    color: #8a97a0;
    box-shadow: 0 1px 0 rgba(0,0,0,0.03) inset;
    margin-bottom: 30px;
  }
  
  input[type="radio"],
  input[type="checkbox"] {
    margin: 0 4px 8px 0;
  }
  
  select {
    padding: 6px;
    height: 32px;
    border-radius: 2px;
  }
 
  .price{
    color: #4bc970;
  }
  
  button {
    padding: 19px 39px 18px 39px;
    color: #FFF;
    background-color: #4bc970;
    font-size: 18px;
    text-align: center;
    font-style: normal;
    border-radius: 5px;
    width: 100%;
    border: 1px solid #3ac162;
    border-width: 1px 1px 3px;
    box-shadow: 0 -1px 0 rgba(255,255,255,0.1) inset;
    margin-bottom: 10px;
    cursor: pointer;
  }
  
  fieldset {
    margin-bottom: 30px;
    border: none;
  }
  
  legend {
    font-size: 1.4em;
    margin-bottom: 10px;
  }
  
  label {
    display: block;
    margin-bottom: 8px;
  }
  
  label.light {
    font-weight: 300;
    display: inline;
  }
 
  
  @media screen and (min-width: 480px) {
  
    form {
      max-width: 480px;
    }
  
  }
```
 
In the `/Public/js` folder, create a new JavaScript file called `ajax-script.js`. Paste the following code into the file:
 
```
$(document).ready(function() {
 
    $("#checkoutForm").submit(function(e){
        e.preventDefault()
        $("#notloading").hide()
        $("#loading").show()
 
        $.ajax('/create-checkout', {
            type: 'POST',
            data: {
                'u_full_name': $("#u_full_name").val(),
                'user_email': $("#user_email").val(),
                'user_full_address': $("#user_full_address").val(),
            }, 
            success: function (data, status, xhr) {
                window.location.href = data.data.redirect_url;
            },
            error: function (jqXhr, textStatus, errorMessage) {
                $("#notloading").show()
                $("#loading").hide()
                console.log('Error' + errorMessage);
            }
        });
    });
})
```
 
### 2. Set Up the Route
 
First, import the route script into the `index.js` file by adding `require('./Routes/index')` to the `index.file`. 
 
Add the following code to the `/Routes/index.js` file:
 
```
const app = require('../Config/server')
const path = require('path');
const dotEnv = require('dotenv');
 
dotEnv.config()
 
app.set('views', path.join(__dirname, '../Views'))
app.set('view engine', 'ejs')
 
app.get('/', function(req, res){
    var price = 3000 / 100
    res.render('Checkout', {
        product_name: 'Gold Digger',
        price: '$'+price.toFixed(2)
    })
})
```
 
Save all files, and if you’ve deactivated the nodemon server, run `npm run dev`. Proceed to your browser and load the application via `http://127.0.0.1:{{your_port_number}}`. It should look like this:
 
![application screenshot](https://i.imgur.com/aYyyHrM.png)
 
Kudos to you if you’ve gotten to this stage! I’d recommend that you initiate git and commit your changes so far. Ensure that you git ignore the `.env` file and the `/node_modules` folder.
 
## How to Develop the Payment Module
 
This is the final and most important process of the development of the application. We’ll integrate the Rapyd Collect API into the application, enabling it to accept payment from all over the world.
 
### 1. Set Up a Rapyd Account
 
Log in to your Rapyd account.
 
There are two environments in Rapyd, namely *production* and *sandbox*. The sandbox environment is not used to process real transactions; it’s mainly for testing purposes. Go to the top right of your Rapyd dashboard to activate the sandbox environment.
 
![Rapyd dashboard](https://i.imgur.com/z0DdXEQ.png)
 
### 2. Customize the Checkout Page
 
Go to the sidebar and click **Settings > Branding**. Here, customize your hosted Rapyd checkout page to fit your requirements.
 
You can change the logo, color, and text to reflect your brand. Specify the redirect URL that the checkout page directs your user to after a successful transaction is completed. Decide which payment method to utilize.
 
> In this tutorial, we’ll only use and test the card payment method.
 
![Rapyd Checkout Settings](https://i.imgur.com/hD4VlWJ.png)
 
Please note that Rapyd will not allow you to use a fallback URL containing or similar to `localhost`. You’ll have to create a new test domain and attach the port number. In my case, I used `http://rapyd-demo.test:3153/`.
 
> Visit this [guide](https://www.basezap.com/setting-up-virtual-hosts-on-windows-linux-and-macos/) to learn how to add a new test domain to the virtual host on your local computer.
 
### 4. Grab Your API Keys
 
Go to the sidebar and click **Developers > Credentials Details** to see your access and secret key.
 
![Rapyd credential details](https://i.imgur.com/5aw3qYF.png)
 
### 5. Connect Your Node Application to the Rapyd Server
 
To connect the Rapyd server to your application, install `https` by running `npm install https` in the root directory of your application.
 
Go to the `/Model` folder and create a new file called `DBQueries.js`. Paste the code below into the file. The code block exports a class that has a generic function for the creation of new checkout records in the database.
 
```
const pool = require('../Config/db-config');
 
const  DEFAULT_PROPERTIES = {
    status: 'In Progress',
    created_at: new Date(Date.now()),
}
 
 
 
class DBQueries {
    constructor(){
 
    }
 
    static create(entry){
        const {full_name, email, full_address, amount, reference, product_details, meta_data} = entry;
 
        return new Promise((resolve, reject) => {
            pool.query(
                `INSERT INTO checkout_details(full_name, email, full_address, amount, reference, product_details, meta_data, status, created_at) 
                VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9)`, 
                [full_name, email, full_address, amount, reference, product_details, meta_data, DEFAULT_PROPERTIES.status, DEFAULT_PROPERTIES.created_at],
                (error, response) => {
 
                if (error) return reject(error);
 
                resolve(response)
            });
        })
    }
    
}
 
module.exports = DBQueries;
```
 
Go to the `/Controller` folder and create a new file called `checkoutController.js`. Paste the code block below into the file; it receives the checkout form request from the route and sends a payment request to the Rapyd API using the `makeRequest` function from `rapydUtilities.js`. It also uses the generic function from `DBQueries.js` to create a record in the database. Finally, it sends the response from Rapyd API back to the frontend interface.
 
```
const {makeRequest} = require('../Helpers/rapydUtilities')
const DBQueries = require('../Model/DBQueries')
 
class checkoutController{
    constructor(){
 
    }
 
    static createCheckout(request, response, next){
        
        const {u_full_name, user_email, user_full_address} = request.body
 
        var price = 3000 / 100
        price = price.toFixed(2)
 
            const body = {
            amount: 30.00,
            country: 'US',
            currency: 'USD',
            language: 'en',
            metadata: {
                u_full_name: u_full_name,
                user_email: user_email,
                user_full_address: user_full_address,
            },
            };
            makeRequest('POST', '/v1/checkout', body).then((data)=>{
 
                const entry = {
                    full_name: u_full_name,
                    email: user_email, 
                    full_address: user_full_address,
                    amount: price * 100, 
                    reference: data.body.data.id, 
                    product_details: 'Gold Digger',
                    meta_data: data.body.data
                };
 
                DBQueries.create(entry)
                    .then((entry) => {
 
                        return response.json({status: 200, message: 'checkout created', data: data.body.data});
 
                    }).catch((error)=>{
                        console.log('error: ',error);
                    });
            })
            .catch((error)=>{
                console.log('error: ',error);
            });
 
    }
 
} 
 
module.exports = checkoutController;
```
 
Go to the `/Helpers` folder and create a new file called `rapydUtilities.js`. Paste the following code into the file; it contains all the core functions used to integrate (make requests to) the Rapyd Collect API:
 
```
const https = require('https');
const crypto = require('crypto');
const dotEnv = require('dotenv');
dotEnv.config()
 
const secretKey = process.env.SECRETE_KEY;
const accessKey = process.env.ACCESS_KEY;
const log = false;
 
async function makeRequest(method, urlPath, body = null) {
 
    try {
        httpMethod = method;
        httpBaseURL = "sandboxapi.rapyd.net";
        httpURLPath = urlPath;
        salt = generateRandomString(8);
        idempotency = new Date().getTime().toString();
        timestamp = Math.round(new Date().getTime() / 1000);
        signature = sign(httpMethod, httpURLPath, salt, timestamp, body)
 
        const options = {
            hostname: httpBaseURL,
            port: 443,
            path: httpURLPath,
            method: httpMethod,
            headers: {
                'Content-Type': 'application/json',
                salt: salt,
                timestamp: timestamp,
                signature: signature,
                access_key: accessKey,
                idempotency: idempotency
            }
        }
 
        return await httpRequest(options, body, log);
    }
    catch (error) {
        console.error("Error generating request options");
        throw error;
    }
}
 
function sign(method, urlPath, salt, timestamp, body) {
 
    try {
        let bodyString = "";
        if (body) {
            bodyString = JSON.stringify(body);
            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);
        }
    })
 
}
 
exports.makeRequest = makeRequest;
```
 
After adding all these files, import the Body parser and the checkoutController to the route file (`/Routes/index.js`) and also configure express to use `body-parser`.
 
```
const bodyParser = require('body-parser');
const checkoutController = require('../Controller/checkoutController')
 
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
```
 
Create a new post route to accept the form request. The post route will send the form data to the checkoutController.
 
```
app.post('/create-checkout', (req, res, next) => {checkoutController.createCheckout(req, res, next)})
```
 
Finally, go back to your `.env` file and add your Rapyd secret and access key, like so:
 
```
SECRETE_KEY={{YOUR_SECRET_KEY}}
ACCESS_KEY={{YOUR_ACCESS_KEY}}
```
 
Save all the files and run the `npm run dev` to start the nodemon server.
 
## How to Demonstrate the Application
 
Congratulations for getting to this stage! Now go to your browser and load the application via `http://127.0.0.1:{{YOUR_PORT_NUMBER}}`.
 
Fill out the checkout form.
 
![Application checkout form](https://i.imgur.com/GPFSEMk.png)
 
Click **Checkout**. It should send the request to Rapyd and redirect you to the payment page.
 
![Rapyd hosted checkout](https://i.imgur.com/LcqNLVU.png)
 
Use the following test card info to make a payment:
 
- Card number: 4111 1111 1111 1111 (Mastercard)
- Expiration date: Any date in the future.
- CVV: Any three-digit number. (e.g. 123) 
- Cardholder name: Any name
 
Click **Place your Order**. You should see a success page like this:
 
![Rapyd hosted checkout success page](https://i.imgur.com/LTMVApf.png)
 
Click **Finish**. You should be redirected to the `{{redirection_url}}` you added when you were customizing the checkout page.
 
To see the payment you just made and other previous payments, navigate back to your Raypd Client Portal. Go to the sidebar and click **Collect > Payments**, as shown in the screenshot below.
 
> Remember to toggle to the sandbox environment.
 
![Rapyd payment history](https://i.imgur.com/3ireyfQ.png)
 
To get back the details about the accepted payment, you have to set up a webhook that will receive all the notifications about payment status from Rapyd and update your record accordingly. Learn more about webhook and payment status in the [official documentation](https://docs.rapyd.net/en/payment-succeeded-webhook.html).
 
## Get Support
 
This article provided a quick overview of Rapyd and its capabilities, as well as a step-by-step tutorial for implementing the Rapyd Collect API checkout page in a Node.js application. You can see payment history, edit the checkout page, and switch between production and sandbox environments. You can access the [complete code](https://github.com/Rapyd-Samples/nodejs-payment-gateway-integration-with-rapyd) for this tutorial on GitHub. 
 
Rapyd provides a fast, secure, and reliable payment gateway solution that can power both local and international business transactions. It supports more than 900 payment methods, and more than 65 currencies in hundreds of countries. If you’re looking to start your integration, you can get started by signing up the [Client Portal](https://dashboard.rapyd.net/sign-up), and follow the [get started guide](https://docs.rapyd.net/en/get-started.html).