Implementing BudPay Webhooks with Node.js and Express: A Practical Example
Tags: webdev, budpay, node, javascript
Published: 2023-06-22 22:21:14

## Introduction
With the ability to deliver automated notifications and trigger events when certain actions take place, webhooks have grown to be an indispensable tool for real-time communication between web applications. 

A webhook functionality provided by popular payment gateway [BudPay](https://budpay.com/) enables businesses to immediately get notified about payment events like successful or unsuccessful transactions. You may automate processes, update order statuses, send email notifications, and take numerous other actions depending on payment events by integrating BudPay webhooks into your application.

This article will explain how to use Node.js and Express to integrate [BudPay webhooks](https://www.apis.budpay.ng/webhook), with a practical example to make the process easier to grasp.

## Prerequisite
Before proceeding with the integration, please take note of the following prerequisites:

- Familiarity with Node.js and npm (Node Package Manager). You can find the Node.js installation guide [here](https://nodejs.dev/en/learn/how-to-install-nodejs/), and the npm installation guide [here](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)

- Understanding of webhooks and their usage

- Knowledge of JavaScript and Express framework

Next, install the necessary dependencies:

- [Express](https://expressjs.com/en/starter/installing.html): A web application framework for Node.js that will handle the webhook endpoint. 
- [dotenv](https://www.freecodecamp.org/news/how-to-use-node-environment-variables-with-a-dotenv-file-for-node-js-and-npm/): A module that allows loading environment variables from a `.env` file. 
- [Nodemailer](https://www.freecodecamp.org/news/use-nodemailer-to-send-emails-from-your-node-js-server/): A library used for sending emails from your Node.js server.
- [Mongoose](https://www.freecodecamp.org/news/mongodb-mongoose-node-tutorial/) An Object Data Modeling (ODM): library for MongoDB and Node.js, which will be used to store data in the database.

To install the dependencies, run the following command:

```
$ npm install express dotenv nodemailer mongoose
```
The installation process will download and set up the required packages for your project.

## Getting Started
Create a new directory for your project and initialize it as a Node.js project by running the following command in your terminal:

```
$ mkdir budpay-webhooks
$ cd budpay-webhooks
$ npm init -y
```

## Setup Modules and PORT
Once the dependencies are installed, create an `index.js` file in the project directory and open it in your preferred text editor. We will start by requiring the essential modules and configuring Express:

```
const express = require('express');
require('dotenv').config();

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});
```
In the above code snippet, we import the Express module and load environment variables using `dotenv`. 
Express is configured to handle JSON request bodies using the `express.json()` middleware. 
Additionally, we specify the port number that the server will use, either from an environment variable or a default value of 3000.

## Construct BudPay Notification Webhook Endpoint
Next, let's construct an endpoint to handle webhook notifications from BudPay. Add the following code to the `index.js` file:

```
app.post('/webhook', (req, res) => {
  const data  = req.body;

  if (event.eventType === 'refund') {
    handlePaymentRefund(data);
  } else if (event.eventType === 'dispute') {
    handlePaymentDispute(data);
  } else if (event.eventType === 'transaction') {
    handlePaymentTransaction(data);
  } else if (event.eventType === 'transaction.recurrent') {
    handlePaymentTransactionRecurrent(data);
  } else {
    handleOtherEvents(event, data);
  }

  res.status(200).send(`Webhook received successfully. http://localhost:3000/webhooks`);
});
```
In this code snippet, we define a `POST` route `/webhook` that will receive incoming webhook notifications from BudPay. By destructuring the `event` and `data` properties from the request body, we can access the `event` type (e.g., `refund` or `dispute`) and any additional information associated with the `event`.

Within the route handler, you can implement custom logic based on the received `event` and `data`. For example, you might choose to update the order `status` in your database, send a confirmation email to the customer, or trigger other relevant actions.

To handle the payment refund `event`, update the code as follows:

```
async function handlePaymentRefund(data) {
  const { orderId, amount, customerEmail } = data;

  try {
    await Order.findOneAndUpdate({ orderId }, { status: 'refund' });
    sendPaymentRefundEmail(customerEmail, orderId, amount);
  } catch (error) {
    console.error('Failed to handle payment refund:', error);
  }
  console.log(`Payment refund for order ${orderId}. Amount: ${amount}. Customer email: ${customerEmail}`);
}
```
In the `handlePaymentRefund` function, you can update the order `status` in the database using Mongoose. Here, we assume you have defined a Mongoose model named `Order` and use the `findOneAndUpdate` method to update the order `status` based on the `orderId` received from the webhook.

Additionally, you can send a refund email to the customer using Nodemailer. Define the following function:

```
function sendPaymentRefundEmail(email, orderId, amount) {
  const mailOptions = {
    from: 'your-email@example.com',
    to: email,
    subject: 'Payment Refund',
    text: `Your payment of ${amount} has been refunded for order ${orderId}.`,
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('Failed to send refund email:', error);
    } else {
      console.log('Refund email sent:', info.response);
    }
  });
}
```
In this function, you can customize the email content and configuration according to your needs. 
The `sendMail` method is used to send the email using the configured transporter.

**To configure Nodemailer, you can use the following code:**

```
const transporter = nodemailer.createTransport({
  host: "smtp.gmail.com",
      port: 465,
      secure: true,
  auth: {
    user: 'your-email@gmail.com',
    pass: 'your-password',
  },
});
```
In the code above we make use of Gmail for example, you can use any email service provider of your choice. 
Now, replace the `'your-email@gmail.com'` with your Gmail address and `'your-password'` with your Gmail password or an [App Password](https://devanswe.rs/create-application-specific-password-gmail/) if you have two-factor authentication enabled.

**Note:** It's important to secure your credentials and avoid hard-coding sensitive information directly in your code. Consider using environment variables or a configuration file to store and retrieve the email credentials securely.

## Handling Payment Dispute Events
To handle the payment dispute `event`, update the code as follows:

```
async function handlePaymentDispute(data) {
  const { orderId, amount, customerEmail } = data;

  try {
    await Order.findOneAndUpdate({ orderId }, { status: 'dispute' });
    sendPaymentDisputeNotification(customerEmail, orderId, amount);
  } catch (error) {
    console.error('Failed to handle payment dispute:', error);
  }

  console.log(`Payment dispute for order ${orderId}. Amount: ${amount}. Customer email: ${customerEmail}`);
}

function sendPaymentDisputeNotification(email, orderId, amount) {
  const mailOptions = {
    from: 'your-email@example.com',
    to: 'support@example.com',
    subject: 'Payment Dispute Notification',
    text: `Payment of ${amount} for order ${orderId} is on dispute for customer ${email}.`,
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('Failed to send payment dispute notification:', error);
    } else {
      console.log('Payment dispute notification sent:', info.response);
    }
  });
}
```
The `handlePaymentDispute` function updates the order `status` in the database to `'dispute'` using Mongoose. It then sends a payment dispute notification to the support team using Nodemailer.

## Handling Payment Transaction Events
Similarly, to handle the payment transaction `event`, update the code as follows:

```
async function handlePaymentTransaction(data) {
  const { orderId, amount, customerEmail } = data;

  try {
    await Order.findOneAndUpdate({ orderId }, { status: 'transaction' });
    sendPaymentTransactionNotification(customerEmail, orderId, amount);
  } catch (error) {
    console.error('Failed to handle payment transaction:', error);
  }

  console.log(`Payment transaction for order ${orderId}. Amount: ${amount}. Customer email: ${customerEmail}`);
}

function sendPaymentTransactionNotification(email, orderId, amount) {
  const mailOptions = {
    from: 'your-email@example.com',
    to: 'support@example.com',
    subject: 'Payment Transaction Notification',
    text: `Payment of ${amount} for order ${orderId} has been successful for customer ${email}.`,
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('Failed to send payment transaction notification:', error);
    } else {
      console.log('Payment transaction notification sent:', info.response);
    }
  });
}
```
The `handlePaymentTransaction` function updates the order `status` in the database to `'transaction'` using Mongoose. It then sends a payment transaction notification to the support team using Nodemailer.

## Handling Transaction Recurrent Events
For handling the transaction recurrent `event`, update the code as follows:

```
async function handlePaymentTransactionRecurrent(data) {
  const { orderId, amount, customerEmail } = data;

  try {
    await Order.findOneAndUpdate({ orderId }, { status: 'transaction-recurrent' });
    sendPTransactionRecurrentNotification(customerEmail, orderId, amount);
  } catch (error) {
    console.error('Handle Transaction Recurrent:', error);
  }

  console.log(`Transaction Recurrent for order ${orderId}. Amount: ${amount}. Customer email: ${customerEmail}`);
}

function sendTransactionRecurrentNotification(email, orderId, amount) {
  const mailOptions = {
    from: 'your-email@example.com',
    to: 'support@example.com',
    subject: 'Transaction Recurrent Notification',
    text: `Payment of ${amount} for order ${orderId} has recurred for customer ${email}.`,
  };

  transporter.sendMail(mailOptions, (error, info) => {
    if (error) {
      console.error('Transaction Recurrent to send notification:', error);
    } else {
      console.log('Transaction Recurrent notification sent:', info.response);
    }
  });
}
```
The `handleTransactionRecurrent` function updates the order `status` in the database using Mongoose. It then sends a payment transaction recurrent notification to the support team using Nodemailer.

To use Nodemailer, make sure to configure it with your email service provider's details (SMTP credentials). You can replace the placeholders in the code with the actual values.

Remember to replace `'your-email@example.com'` with the appropriate email addresses for sending emails notifications.

## Conclusion
Lastly, configure the webhook URL in BudPay. Create a `.env` file in the project directory and add the following line:

```
WEBHOOK_URL=http://localhost:3000/webhook
```
If you deploy your application to a production environment, replace the URL with the actual server URL.

To load the environment variables from the `.env` file like we did while setting up the modules, add the following code at the top of the `index.js` file:

```
require('dotenv').config();
```
This configuration allows access to the `WEBHOOK_URL` variable through `process.env.WEBHOOK_URL` within our code.

With everything set up, you can now test your implementation. Start the server by executing the following command in your terminal:

```
$ node index.js
```
Congratulations! You have successfully integrated BudPay webhook notifications into your Node.js and Express application. You are now ready to receive and handle real-time payment events, such as refund, transaction, transaction recurrent and dispute transactions, from BudPay.

**Please find below a collection of screenshots showcasing:** 

- The tested order endpoint

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2k1q5tlryqy333xw02ua.png)

- And the webhook

![Webhook Paid response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zois0m7bi5m0v50a5z5j.png)

Feel free to customize the logic inside the webhook route handler to align with your specific use case. You can update the database, send email notifications, trigger additional actions, or implement any other functionality based on the received payment events.

Remember to secure your credentials and sensitive information by using environment variables or a configuration file instead of hard-coding them directly in your code.

For a complete reference and source code, you can visit the [GitHub repository](https://github.com/Folasayo-Samuel/BudPay-Node-Express-Webhook) associated with this integration.

**Note:** Please ensure that you adhere to BudPay's terms of service and any applicable legal requirements while implementing the webhook functionality.

Thanks for reading...
**Happy Coding!**