DEV Community

xunylpay
xunylpay

Posted on

Integrating PayMongo API in Next.js - Part 2

Hi there,

In this two part article, I'll be guiding you on how to integrate the PayMongo API in NextJS

In Part 1, we have a brief discussion on PayMongo payment workflows and how to setup credit card payments.

In Part 2, we tackle using webhooks to monitor payments and we move forward to processing GrabPay and GCash Payments.

Table of Contents

Introduction

This is a simple guide for developers to integrate the PayMongo API in NextJS. Here, we'll start with a simple checkout and payment template project, and then move forward by filling up the API calls necessary to process payments.

PayMongo provides businesses an easy, user-friendly way to accept payments from their customers. It is a payment gateway that process Visa/Mastercard, GCash, and GrabPay payments.

PayMongo API is for those who want to directly integrate their site or app with PayMongo. Using the API allows you to take full control of the user's experience and integrate the payments directly with your systems and databases.

ngrok is a free service that helps you share a site or server running on your local machine

Demo

Live Preview: https://nextjs-paymongo-api.vercel.app/
GitHub: https://github.com/xunylpay/nextjs-paymongo-api

Webhooks

PayMongo provides webhooks that notify you of events happening during the payment process. Webhooks are a sure fire way to track payments especially if you want to store payment statuses in the database. Listening to events in the front end may result to consistency/reliability issues in your system (eg. client connection is cut, client closes the window after payment).

Let's Code

Setting Up

In setting up, we assume that you have already finished doing the first part of this tutorial. Feel free to download or clone the part-1-done branch in the repository.

If you have not done so, create a .env file on the root folder. Insert your public and secret keys in this format:

NEXT_PUBLIC_PAYMONGO_PUBLIC=pk_test_xxxxxxxxxxxxxxxxxxxxxxxx
PAYMONGO_SECRET=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx
Enter fullscreen mode Exit fullscreen mode
  • The NEXT_PUBLIC_ is important when exposing the environment variable in the front end

We also need ngrok to test our webhook endpoint locally.

yarn add ngrok --dev
Enter fullscreen mode Exit fullscreen mode

Let's also set up our package.json script so we can start our ngrok tunnel.
package.json

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "ngrok": "ngrok http 3000"
 },
Enter fullscreen mode Exit fullscreen mode

Running the project

Now open the project in your favorite text editor or IDE (Integrated Development Environment). Open a terminal then run the following commands to start up the development server.

yarn
yarn dev
Enter fullscreen mode Exit fullscreen mode

On localhost:3000 you should be seeing this:

localhost:3000 home

On a different terminal, let's start our ngrok tunnel.

yarn ngrok

or

ngrok http 3000
Enter fullscreen mode Exit fullscreen mode

On your terminal, you should be seeing something like this:
Ngrok Example

Take note of your forwarding address. In the case above, it is https://0f99-136-158-3-235.ngrok.io

Creating an endpoint for the webhook

We need to create an endpoint in our site wherein PayMongo will make a post request to. In Next.JS, it's as easy as creating a file under the pages/api folder. For now, let's fill up the pages/api/paymongo_webhook.js with an endpoint that prints out the request PayMongo sends us for testing.

pages/api/paymongo_webhook.js

// Webhook for paymongo payments

const handler = async (req, res) => {
  if (req.method === "POST") {
    console.log("===Webhook triggered===")
    const data = req.body.data
    console.log(data)
    console.log("===webhook end===")
    res.status(200).send("Webhook Received")
  }
  else {
    res.setHeader("Allow", "POST");
    res.status(405).send("Method Not Allowed");
  }
};

export default handler;
Enter fullscreen mode Exit fullscreen mode

Let's test this out by creating a webhook and listening to a credit card payment.

Creating the webhook

We can easily create a webhook with PayMongo's Create a webhook API Reference. Enter your secret key in the username, enter your ngrok url + /api/paymongo_webhook in the url field (eg. https://4566-49-145-8-183.ngrok.io/api/paymongo_webhook), enter source.chargeable, payment.paid and payment.failed in the events field and click on "Try It". You can also use curl in doing this but doing it on the site is, personally, more hassle free.

Create a webhook

This would create a webhook that you can use. Remember to list down your webhook ID, you can do it in a text file inside your project or add it in the .env file .

You can also do the following in the API Reference:

Do take note that every time you start your ngrok tunnel, it will give you a different url. You would have to update your webhook when the url changes.

Testing the webhook

You can now test out your webhook that we have just finished setting up. In localhost:8000, make a successful card payment. It might take a couple of seconds to reach your ngrok tunnel but it should log a post request and a console log. The console log contains what the payload looks like.

Image description

If you are not receiving any events after a couple of seconds, make sure that your webhook configurations are correct such as the url end point and the events array.

There are 3 different webhook events that we are listening to:

  1. source.chargeable - when an e-wallet payment has been authorized by the client
  2. payment.paid - when a card/PayMaya/GCash/GrabPay payment is successful
  3. payment.failed - when a card/PayMaya payment fails

Accepting E-wallet Payments (GCash and GrabPay)

Now that we finished setting up our initial webhook end point, let's start accepting GCash and GrabPay. As mentioned in the first part of the tutorial, PayMongo uses the Source and Payment workflow to process GCash and GrabPay payments. Let's follow these steps as stated in the guide:

      - Create a Source
      - Have the customer authorize the payment
      - Create a Payment using the chargeable Source

Let's edit the src/components/payments/GCash.js and src/components/payments/GrabPay.js

Create a source

In both the e-wallet component files, I've already created a function called createSource. Let's fill up both of these functions and call the Create a Source API.

// In src/components/payments/GCash.js
// Function to Create A Source
  const createSource = async () => {
    setPaymentStatus("Creating Source")
    const options = {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        Authorization: `Basic ${Buffer.from(publicKey).toString("base64")}`
      },
      body: JSON.stringify({
        data: {
          attributes: {
            amount: amount * 100,
            redirect: { success: 'http://localhost:3000/payment', failed: 'http://localhost:3000/payment' },
            billing: { name: `${name}`, phone: `${phone}`, email: `${email}` },
            type: 'gcash', //change to graby_pay in GrabPay.js
            currency: 'PHP'
          }
        }
      })
    }
    return fetch('https://api.paymongo.com/v1/sources', options)
      .then(response => response.json())
      .then(response => {
        return response
      })
      .catch(err => console.error(err));
  }

Enter fullscreen mode Exit fullscreen mode

We can also create a front-end function to listen to the status of our payment source. We can use the Retrieve a Source API call for this.
In src/components/payments/GCash.js and src/components/payments/GrabPay.js

// Function to Listen to the Source in the Front End
  const listenToPayment = async (sourceId) => {
    let i = 5;
    for (let i = 5; i > 0; i--) {
      setPaymentStatus(`Listening to Payment in ${i}`)
      await new Promise(resolve => setTimeout(resolve, 1000))

      if (i == 1) {
        const sourceData = await fetch(
          'https://api.paymongo.com/v1/sources/' + sourceId,
          {
            headers: {
              // Base64 encoded public PayMongo API key.
              Authorization: `Basic ${Buffer.from(publicKey).toString("base64")}`
            }
          }
        ).then((response) => {
          return response.json()
        }).then((response) => {
          console.log(response.data)
          return response.data
        })

        if (sourceData.attributes.status === "failed") {
          setPaymentStatus("Payment Failed")
        }
        else if (sourceData.attributes.status === "paid") {
          setPaymentStatus("Payment Success")
        }
        else {
          i = 5;
          setPayProcess(sourceData.attributes.status)
        }
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Let's test this out by calling the two functions we made in our onSubmit function.

In src/components/payments/GCash.js and src/components/payments/GrabPay.js

const onSubmit = async (event) => {
     event.preventDefault();
    const source = await createSource();
    window.open(
      source.data.attributes.redirect.checkout_url, "_blank");
    listenToPayment(source.data.id)
  };
Enter fullscreen mode Exit fullscreen mode

Have the customer authorize the payment

After creating the source and opening the checkout_url, we can simulate how a customer will authorize the payment.
Authorize GCash Payment

Here we can observe that:

  • If the customer fails the payment, the source status stays at pending.
  • If the customer cancels the payment, the source status becomes cancelled
  • If the customer authorizes the payment, the source becomes chargeable.

Keep in mind that even if a source becomes chargeable, this does not mean that the payment is already successful. You still need to create a payment for the chargeable source. If you fail to do this after an hour, PayMongo would return the funds back to the customer's e-wallet and the status would become cancelled (see more). As you may have noticed, we'll also be able to see changes to our source on our webhook having the events source.chargeable and payment.failed.

Create a Payment using the chargeable Source

Theoretically, you could create a payment after listening in the front-end; however, it is not advisable. End users might close the window or lose internet connection so it is better to create the payment on webhooks.

Let's edit our src/pages/api/paymongo_webhook.js to handle this for us and call the Create a Payment API. Let us separate each event with an if-else statement.

In src/pages/api/paymongo_webhook.js

const handler = async (req, res) => {
  if (req.method === "POST") {
    console.log("===Webhook triggered===")
    const data = req.body.data
    console.log(data)
    console.log("===webhook end===")
    if (data.attributes.type === "source.chargeable") {
      // Gcash and Grab Pay
      console.log("E-wallet Payment Chargeable")

    }
    if (data.attributes.type === "payment.paid") {
      // All Payment Types
      // Add next steps for you
      console.log("Payment Paid")
    }
    if (data.attributes.type === "payment.failed") {
      // Failed Payments - Cards Paymaya
      // Add next steps for you
      console.log("Payment Failed")
    }
    res.status(200).send("Webhook Received")
  }
  else {
    res.setHeader("Allow", "POST");
    res.status(405).send("Method Not Allowed");
  }
};

export default handler;
Enter fullscreen mode Exit fullscreen mode

Here, you can also do the your next steps after listening to the event. A great example of this is updating your checkout link or your transactions table in a database.

After separating our webhook events, let's create a payment every time a source becomes chargeable.

In src/pages/api/paymongo_webhook.js

...
    if (data.attributes.type === "source.chargeable") {
      // Gcash and Grab Pay
      console.log("E-wallet Payment Chargeable")

      // Create a payment resource
      const options = {
        method: 'POST',
        headers: {
          Accept: 'application/json',
          'Content-Type': 'application/json',
          Authorization: `Basic ${Buffer.from(
            process.env.PAYMONGO_SECRET
          ).toString("base64")}`,
        },
        body: JSON.stringify({
          data: {
            attributes: {
              amount: data.attributes.data.attributes.amount,
              source: { id: `${data.attributes.data.id}`, type: `${data.attributes.data.type}` },
              description: data.attributes.data.attributes.description,
              currency: 'PHP',
              statement_descriptor: data.attributes.data.attributes.statement_descriptor
            }
          }
        })
      };

      fetch('https://api.paymongo.com/v1/payments', options)
        .then(response => response.json())
        .then(response => console.log(response))
        .catch(err => console.error(err));

    }
...
Enter fullscreen mode Exit fullscreen mode

After doing so, we should now be able to accept e-wallet payments successfully. The webhook would log a source.chargeable event and then log a payment.paid event.

Conclusion

In this guide, you learned how to use PayMongo webhooks and accepting GrabPay and GCash Payments. This concludes the two part series and I hope you like this article and feel free to give feedbacks on my writing.

Top comments (4)

Collapse
 
highcenburg profile image
Vicente G. Reyes

Hey there! Awesome post! How can I get PayMongo API keys?

Collapse
 
xunylpay profile image
xunylpay

Hi Vicente, you can get the PayMongo API keys by signing up to PayMongo and going to their developers tab.

Collapse
 
joseph_jasonbuhain_971f5 profile image
Joseph Jason Buhain

Do you know if you can automatically skip the authorize/fail/expire payment section?

Collapse
 
crisdevtech profile image
CrisDevtech

Hi thank you i get the post 200 ok status is that okay right if im going to live this i will only change the live key right?