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.
- 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
Additionally, you also need a PayMongo Account. You can sign up here and get your test API keys at the developers tab.
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
On localhost:3000 you should be seeing this:
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
- 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 {
}
}
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;
};
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;
};
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));
});
};
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);
};
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;
}
}
}
}
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));
});
...
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)