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
- 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
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"
},
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:
On a different terminal, let's start our ngrok tunnel.
yarn ngrok
or
ngrok http 3000
On your terminal, you should be seeing something like this:
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;
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.
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.
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:
- source.chargeable - when an e-wallet payment has been authorized by the client
- payment.paid - when a card/PayMaya/GCash/GrabPay payment is successful
- 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));
}
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)
}
}
}
}
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)
};
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.
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;
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));
}
...
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)
Hey there! Awesome post! How can I get PayMongo API keys?
Hi Vicente, you can get the PayMongo API keys by signing up to PayMongo and going to their developers tab.
Do you know if you can automatically skip the authorize/fail/expire payment section?
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?