DEV Community

xunylpay
xunylpay

Posted on • Updated on

Integrating PayMongo API in Next.js - Part 1

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.

Demo

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

PayMongo Payment Workflows

Before diving into the code, I would like to discuss PayMongo's two Payment Workflows. These workflows differ in implementation and payment method. I have summarized the steps below:

1.Payment Intent Payment Method Workflow

This is what PayMongo uses to process credit card and PayMaya payments. It involves creating a payment intent, creating a payment method, and then attaching the intent and method together.

Payment Intent Workflow

      - Creating a PaymentIntent from the server-side
      - Collect Card Information from the client-side
      - Send Payment Information to PayMongo
      - Monitoring PaymentIntents through webhooks

2.Source and Payment Workflow

This is what PayMongo uses to process GCash and GrabPay payments. This involves creating a source, listening to customer authorization, and creating a payment for chargeable source.

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

Let's Build

Setting Up

As mentioned in the title, we'll be using Next.js for building this.

For Next.js to work, we need to have Node.js and npm installed.

So, first, install Node.js if you haven't yet.

Let's also download yarn

npm install --global yarn
Enter fullscreen mode Exit fullscreen mode

Additionally, you also need a PayMongo Account. You can sign up here and get your test API keys at the developers tab.

API Keys Dashboard

Lastly, to follow along this tutorial, we will be starting with the front end already built. Feel free to download or clone the front-end-done branch in the repository.

I built this simple front-end to simulate a simple payment process. The site uses localstorage to simulate database push and pull requests. We will be primarily concerned with the src/components/payments folder and src/pages/api/createPaymentIntent.js file.

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

Next, 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

Implementing Card Payments

As mentioned previously, the payment intent payment method workflow is used when implementing card payments. Let's do the steps provided in the PayMongo Accepting Cards Documentation

Creating a PaymentIntent from the server-side

In the src/pages/api/createPaymentIntent.js file, let us create an endpoint that would allow us to create a PaymentIntent given a POST request. The req.body should contain the necessary information as required by the Create A PaymentIntent API call.

As per the API reference, we need to authenticate our API requests. PayMongo uses HTTP Basic Auth and your API key as the basic auth username, encoded in Base64. This may seem complicated but it is really easy to implement in JavaScript as shown below.

src/pages/api/createPaymentIntent.js

// This function is called to create a Payment intent
// Step 1 of https://developers.paymongo.com/docs/accepting-cards

export default async function handler(req, res) {
  if (req.method === "POST") {

    // Creating our options for the Create a Payment Intent Call
    const optionsIntent = {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Basic ${Buffer.from(
          process.env.PAYMONGO_SECRET
        ).toString("base64")}`, // HTTP Basic Auth and Encoding
      },
      body: JSON.stringify(req.body),
      // The req.body should follow this specific format
      //   {
      //     "data": {
      //          "attributes": {
      //               "amount": 10000 (int32) note that 10000 = PHP 100.00,
      //               "payment_method_allowed": [
      //                    "card",
      //                    "paymaya"
      //               ](string array),
      //               "payment_method_options": {
      //                    "card": {
      //                         "request_three_d_secure": "any"
      //                    }
      //               },
      //               "currency": "PHP" (string),
      //               "description": "description" (string),
      //               "statement_descriptor": "descriptor business name" (string)
      //          }
      //     }
      //  }
    };

    // Calling the Create a Payment Intent API
    await fetch("https://api.paymongo.com/v1/payment_intents", optionsIntent)
      .then((response) => response.json())
      .then(async (response) => {
        if (response.errors) {
          console.log(JSON.stringify(response.errors));
        } else {
          res.status(200).json({ body: response });
        }
      });
  } else {
  }
}
Enter fullscreen mode Exit fullscreen mode

In the src/components/payments/CreditCard.js, we fill up the createPaymentIntent so that the function calls the src/pages/api/createPaymentIntent.js we just made. Keep in mind that we are using data from component props, but you can handle it in any way that you like.

src/components/payments/CreditCard.js - createPaymentIntent

// Function to Create a Payment Intent by calling the site's api
  const createPaymentIntent = async () => {
    setPaymentStatus("Creating Payment Intent");
    const paymentIntent = await fetch("/api/createPaymentIntent", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        data: {
          attributes: {
            amount: amount * 100,
            payment_method_allowed: ["card"],
            payment_method_options: {
              card: { request_three_d_secure: "any" },
            },
            currency: "PHP",
            description: description,
            statement_descriptor: "descriptor business name",
          },
        },
      }),
    })
      .then((response) => {
        return response.json();
      })
      .then((response) => {
        return response.body.data;
      });

    return paymentIntent;
  };
Enter fullscreen mode Exit fullscreen mode

Collect Card Information from the client-side

The starter template already included this. I handled this by simply creating a useState for all the fields and setting the value on change. PayMongo does not recommend to send this data on the server or store it anywhere. We will be using this card information on the next step.

Send Card Information to PayMongo

To send card information to PayMongo securely we will be creating a paymentMethod and attaching it to the paymentIntent we created in the first step.

In calling the creating a payment method, we use the card details that the client provided. We also use our public key encoded in the base64 when calling the API Call.

src/components/payments/CreditCard.js - createPaymentMethod

// Function to Create a Payment Method by calling the PayMongo API
  const createPaymentMethod = async () => {
    setPaymentStatus("Creating Payment Method");
    const paymentMethod = fetch("https://api.paymongo.com/v1/payment_methods", {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Basic ${Buffer.from(process.env.NEXT_PUBLIC_PAYMONGO_PUBLIC).toString("base64")}`,
      },
      body: JSON.stringify({
        data: {
          attributes: {
            details: {
              card_number: `${number}`, //"4343434343434345",
              exp_month: parseInt(`${month}`), //2
              exp_year: parseInt(`${year}`), //22
              cvc: `${code}`, //"123",
            },
            billing: {
              name: `${name}`,
              email: `${email}`,
              phone: `${phone}`,
            },
            type: "card",
          },
        },
      }),
    })
      .then((response) => {
        return response.json();
      })
      .then((response) => {
        return response.data;
      })
      .catch((err) => {
        console.log(err);
        setPaymentStatus(err);
        return err;
      });

    return paymentMethod;
  };
Enter fullscreen mode Exit fullscreen mode

Now, to complete the credit card payment, we attach the payment intent and the payment method we created together.

Here, we need to keep in mind two scenarios. Basic cards and 3DS cards. 3DS cards are cards where we need to handle a page wherein the user will enter their OTP (One Time Pin). The OTP is part of the 3DS protocol implemented by banks for clients to approve their online transactions. Basic cards are rare in the Philippines, and almost all PayMongo card transactions are done with 3DS cards. We render this page by creating an iframe or opening a window as seen below.

src/components/payments/CreditCard.js - attachIntentMethod

// Function to Attach a Payment Method to the Intent by calling the PayMongo API
  const attachIntentMethod = async (intent, method) => {
    setPaymentStatus("Attaching Intent to Method");
    fetch(`https://api.paymongo.com/v1/payment_intents/${intent.id}/attach`, {
      method: "POST",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: `Basic ${Buffer.from(process.env.NEXT_PUBLIC_PAYMONGO_PUBLIC).toString("base64")}`,
      },
      body: JSON.stringify({
        data: {
          attributes: {
            payment_method: `${method.id}`,
            client_key: `${intent.attributes.client_key}`,
          },
        },
      }),
    })
      .then((response) => response.json())
      .then((response) => {
        const paymentIntent = response.data;
        console.log(paymentIntent)
        const paymentIntentStatus = paymentIntent.attributes.status;
        if (paymentIntentStatus === 'awaiting_next_action') {
          // Render your modal for 3D Secure Authentication since next_action has a value. You can access the next action via paymentIntent.attributes.next_action.
          setPaymentStatus(paymentIntentStatus);
          window.open(
            paymentIntent.attributes.next_action.redirect.url, "_blank");
        } else {
          setPaymentStatus(paymentIntentStatus);
        }
      })
      .catch((err) => {
        console.log(err);
        setPaymentStatus(JSON.stringify(err));
      });
  };
Enter fullscreen mode Exit fullscreen mode

Now, let's call up these functions in our submit function.

src/components/payments/CreditCard.js - onSubmit

const onSubmit = async (event) => {
    event.preventDefault();
    const paymentIntent = await createPaymentIntent();
    const paymentMethod = await createPaymentMethod();
    await attachIntentMethod(paymentIntent, paymentMethod);
};

Enter fullscreen mode Exit fullscreen mode

Monitoring PaymentIntents through webhooks

The last step of the Payment Intent Payment Method workflow is monitoring the payment intent through the use of webhooks. We'll discuss this in the next part of the guide. However, we can still monitor paymentIntents in the client side by calling the Retrieve a Payment Intent API. Let us create this function and call it after attaching the payment intent and method.

This is kind of a hack to set a timeout that for every 5 seconds we check the status by calling the API until payment is resolved.

src/components/payments/CreditCard.js - listenToPayment

// Function to Listen to the Payment in the Front End
  const listenToPayment = async (fullClient) => {
    const paymentIntentId = fullClient.split('_client')[0];
    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 paymentIntentData = await fetch(
          'https://api.paymongo.com/v1/payment_intents/' + paymentIntentId + '?client_key=' + fullClient,
          {
            headers: {
              // Base64 encoded public PayMongo API key.
              Authorization: `Basic ${Buffer.from(process.env.NEXT_PUBLIC_PAYMONGO_PUBLIC).toString("base64")}`
            }
          }
        ).then((response) => {
          return response.json()
        }).then((response) => {
          console.log(response.data)
          return response.data
        })

        if (paymentIntentData.attributes.last_payment_error) {
          setPaymentStatus(JSON.stringify(paymentIntentData.attributes.last_payment_error))
        }
        else if (paymentIntentData.attributes.status === "succeeded") {
          setPaymentStatus("Payment Success")
        }
        else {
          i = 5;
        }
      }
    }
  }

Enter fullscreen mode Exit fullscreen mode

In attachIntentMethod, we listen to the status of the payment intent after opening the OTP page.

src/components/payments/CreditCard.js - attachIntentMethod

...
      .then((response) => response.json())
      .then((response) => {
        const paymentIntent = response.data;
        console.log(paymentIntent)
        const paymentIntentStatus = paymentIntent.attributes.status;
        if (paymentIntentStatus === 'awaiting_next_action') {
          // Render your modal for 3D Secure Authentication since next_action has a value. You can access the next action via paymentIntent.attributes.next_action.
          setPaymentStatus(paymentIntentStatus);
          window.open(
            paymentIntent.attributes.next_action.redirect.url, "_blank");
            listenToPayment(paymentIntent.attributes.client_key);
        } else {
          setPaymentStatus(paymentIntentStatus);
        }
      })
      .catch((err) => {
        console.log(err);
        setPaymentStatus(JSON.stringify(err));
      });
...

Enter fullscreen mode Exit fullscreen mode

After doing this, the app should be able to accept Credit card payments that would reflect on your PayMongo dashboard. PayMongo conveniently provides test cards that you can use for testing here.

Conclusion

In this guide, you learned how to use PayMongo API keys, PayMongo payment workflows, and accepting credit card payments. For the next part of the guide, we'll learn more about webhooks and using the payment source and payment to process e-wallet transactions (GCash and GrabPay).

Top comments (0)