Setup
In our Next JS app that lets issuers surface pending tasks in the UI, and approve or reject them, we’ll use:- A singleton in-memory DB
- ShadCN UI components
- Lucide icons
Scaffold your project
In-memory DB means data resets on restart. For production, swap in a real database.
Start the app
Folder structure
Next, you’re going to want to create a folder struture similar to this layout.folder structure
api/webhook
folder. We’re going to opt for creating a separate route for each webhook event to keep a clear separation of concerns and since we’re using Next JS’s app based routing system, that means we need to create the following files
Webhook POST handler
Signature Verification
Finally, we’ll add a helper function to validate the webhook signature from incoming events sent by Paylias. Open up thelib/signature.ts
file and add in the following code.
signature.ts
Data Layer
Next up, we’ll define our basic data models to handle our internal representation of an incoming Payment Request, create a simple in-memory database and a repository layer that will let us perform our CRUD operations. Create a file undertypes
called task.ts
if you haven’t already done so and paste this inside
task.ts
class
that manages a Map
. Open up the database/memory-db.ts
file (or create one if you haven’t) and add the following code inside
memory-db.ts
taskStore
variable is not defined. Because we have chosen to use an in-memory database, in Next, (whether in dev with HMR or in production on Vercel’s serverless functions), the data in this module can be torn down and re-evaluated at any time, so tasks gets reset. In order to avoid this and truly get one bucket of memory to live across all imports (& HMR reloads) in a single Node process, you need to hang it on the Node “global”. So create a new file inside the database
folder called global-db.ts
and add this code
global-db.ts
memory-db.ts
file to fix the compile errors.
memory-db.ts
memory-db
. So go ahead and open up the file repository/taskRepository.ts
and add the following code inside.
taskRepository.ts
Webhook implementation
Now that we’ve set up the data layer, we’ll look into implementing the relevant webhooks required to build out our issuer app! If you haven’t already, we recommend going through the Webhook guides to get an understanding of what webhooks are and how they work. Once you’ve completed the guides, you can start implementing the webhooks for your issuer app. Since we’re opting to create an individual endpoint for each combination ofrecord_type
and event_type
, the first step we have to complete is to create webhook subscriptions on Paylias for each endpoint. You can follow the Webhook Implementation guide to create the endpoint subscriptions on Paylias.
Since Paylias cannot deliver webhooks to localhost, we’re going to first have to expose our app through a tunnel. We recommend using ngrok. You may need to create a free account and install it on your system before proceeding further.
Side step
You can run this command in another terminal window to expose your app through ngrok to the internet.Make sure you subscribe to the right combination of
record_type
and event_type
for each endpoint on your appImplementation
Now, we’ll implement only the four Paylias webhooks an Issuer needs1
`payment-admission:created`
When Paylias notifies us that a new payment admission has been created, we will
- Parse the payload to extract the relevant information
- Validate the signature in the header
- Create a new Task record in your database with the amount and currency in the payment and status
PENDING
- Add a placeholder to trigger any risk-check logic
- Send a response back to Paylias to acknowledge receipt of the webhook
2
`payment-admission-task:created
When Paylias notifies us that a new payment admission task has been created, we will
- Parse the payload to extract the relevant information
- Validate the signature in the header
- Look up the original task
- Update the task to either
PENDING
orREJECTED
- Send a response back to Paylias to acknowledge receipt of the webhook
3
payment-exception:created
If Paylias sends an event notifying us that there was an exception during the payment processing flow, we’ll update the task to
REJECTED
and save the error message.4
payment-transaction:created
The final step in the payment journey is marking the payment complete. When you receive this event, the payment is considered as final and any deductions in the user’s balance and ledger updates should be made on your platform (preferably asynchronously so you can respond back to Paylias quickly). The Transaction Resource guide explains this in more details.
Front-End UI
Now that we’ve set up majority of the application at the backend, its time to move on to the frontend so that our users can see and respond to their payment requests. The general functionality we’re going to develop on the frontend will include- Displaying a list of all
Tasks
in a column view - Polling for all tasks in the background and updating the list
- Taking action on
PENDING
tasks to allow our user to either accept or reject them.
This is a fairly simple UI/UX implementation meant for demostration purposes only. In your production application you would most likely use push notifications as new tasks come in to your system and have a separate notification page or tray to display any pending tasks to your user.
Install shad-cn components
First off, we’ll install the relevant Shad Cn components needed to build our UI. Open up a terminal window andcd
into the root directory of your app. Once there copy-paste this command
Create components
Once these have been installed, add the following components under thecomponents
directory.
Fetch All Tasks
Now that we’ve created the basic components needed to display a Task, we’re going to create an endpoint that will return us a list of tasks for us to render in a list. Open upapp/api/tasks/route.ts
and add in the following code
route.ts
TasksList
component to poll for all tasks every 5 seconds. Lets create the TasksList
component now. Create a new file under components
called TasksLists.tsx
and add in the following code
TasksList.tsx
app/page.tsx
and replace the existing code with the following
page.tsx
PENDING
task.
Updating a task
Updating the task is a two step process. When our user clicks on a task they want to action on, we’ll open up a dialog that will first fetch the task along with its details from our database. Then, depending on whether they want to approve or reject the payment request, we’ll update the task accordingly and respond to Paylias with our decision. To make this work, lets first start with implementing the two endpoints we’re going to need. Open up the/api/tasks/[task-id]route.ts
file and add in the code below to implement both the GET
and PUT
endpoints.
PUT
endpoint we’re also calling the Update Admission Task endpoint. You’ll notice that we’re not updating the task in our database yet. If we reject the task, Paylias will trigger an Exception event which our /api/webhook/payment-exception/created
endpoint will catch. This is where our the task will be updated in our system. Similarly, if we approve the task, Paylias will trigger a Transaction event which our /api/webhook/payment-transaction/created
endpoint will catch. This is where we will mark the task as COMPLETED
.
The final step in this guide is to implement the UI for updating a task. We’ll accomplish this using the <Dialog>
component from Shad Cn along with implementing parallel and intercepting routes from Next JS.
Create a new file under the components
directory called UpdateTask.tsx
and add in the following code
UpdateTask.tsx
app
directory.