DEV Community

FOLASAYO SAMUEL OLAYEMI
FOLASAYO SAMUEL OLAYEMI

Posted on

Implementing BudPay Webhooks with Node.js and Express: A Practical Example

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 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, 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, and the npm installation guide here

  • Understanding of webhooks and their usage

  • Knowledge of JavaScript and Express framework

Next, install the necessary dependencies:

  • Express: A web application framework for Node.js that will handle the webhook endpoint.
  • dotenv: A module that allows loading environment variables from a .env file.
  • Nodemailer: A library used for sending emails from your Node.js server.
  • Mongoose 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
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

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

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

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

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

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

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

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

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

  • And the webhook

Webhook Paid response

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 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!

Top comments (0)