Build Merce's Backend Server

We will be using NodeJs as the backend server for Merce. It is an asynchronous event-driven JavaScript runtime, designed to build scalable network applications. This tutorial assumes you have NodeJs installed, if not head over to the downloads page.

ExpressJS is the most used HTTP framework for NodeJS. It is powerful and provides a set of robust features such as middleware, proxy, server-side rendering (SSR) to mention but a few.

Setting up the server

First, let us initialise the NodeJs server. Open your terminal and type the following command:

$ npm init

Fill the required information according to your specifications.

Next, let us install Express. In the same terminal, type the following:

$ npm install express helmet cors body-parser --save-dev nodemon

  1. Cross Origin Resource Sharing (CORS), allows the api’s resources to be accessed from a different domain. In this case, our front-end will be able to access the api’s resources.

  2. Body parser converts incoming body request to a javascript object.

  3. Helmet adds a layer of security to our api. It protects applications from attacks such as XSS, Content Security Policy to mention but a few. It ensures the right response HTTP headers are set.

  4. Nodemon - Automatically restart the server after every save.

After the installation is complete, open your entry file and write the following code. (Ours is index.js)

The explanation will come after the code.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const express = require("express"); const bodyParser = require('body-parser'); const cors = require("cors"); const helmet = require("helmet"); // initialise the express app const app = express(); /** * Fake database of our products. Replace with your database query. */ const products = [ { id: 1, name: "iPhone 11", price: 1500000, quantityAvailable: 6 }, { id: 2, name: "JBL speaker", price: 300000, quantityAvailable: 6 }, { id: 3, name: "New King James Bible", price: 6000000 } ]; // use helmet app.use(helmet()); // use the body parser app.use(bodyParser.json()); // next enable cors for all requests app.use(cors()); app.get('/api/product', (request, response) => { response.send(products).status(200); }) app.listen(3000, () => { console.log("Server running on port 3000"); });

Test the server

Now open another tab of your terminal and write the following command

$ curl http://localhost:3000/

You should get a response like the following:

[{"id":1,"name":"iPhone 11","price":1500000,"quantityAvailable":6},{"id":2,"name":"JBL speaker","price":300000,"quantityAvailable":6},{"id":3,"name":"New King James Bible","price":6000000}]

Congratulations! Your api is functional, we will build the front-end view in the coming sections and render the products in a nice view. Obviously in a production environment or an MVP, the route would be protected by authentication. Since our focus is integrating with Monnify, this will suffice. Next let us handle the integration.

Integrate with Monnify’s One Time Payments APIs

Monnify offers several ways to collect payments in your app and business. The include:

  1. One Time Payments - A payment can be initiated using the APIs, web and mobile (android/ios) SDKs. A dynamic account number is generated with each initialization such that a bank transfer to the account, credits your wallet with Monnify. Card payments are also supported.

  2. Customer Reserved Accounts - Generate a permanent virtual account for your dedicated customer. A bank transfer to this account credits your wallet with Monnify.

  3. Invoice Generation - Generate invoices for your customers while they pay for your goods and services.

For the sake of this tutorial, we’ll be using the One Time Payments for quick payment collection. We will integrate other methods in subsequent tutorials.

In your code editor, create a new file “MonnifyService.js”. This file will contain all integration codes with the Monnify APIs.

Remember when we signed up and got our API key and Secret key in the beginning phase of this tutorial? We need them now. Monnify’s api routes are protected by OAuth 2.0. This means you need an access token included in your Header while making a HTTP request. We will call the login endpoint to get this token and use axios to make HTTP calls subsequently.

$ npm i axios

Install dotenv module. This will allow us create environmental variables so we can add our keys. It is not advisable to include the key in the code due to security reasons.

$ npm install dotenv

Create a .env file in the Merce folder and add the keys like so

MONNIFY_API_KEY=
MONNIFY_SECRET_KEY=
MONNIFY_BASE_URL=

You might need to add some logic in your code to determine if your environment is dev, staging or production and use the appropriate base url.

.env file may contain sensitive information, such as API keys or secrets. Thus, add it to a project's .gitignore file to prevent it from being committed to version control

Replace the asterisks with the actual values from your dashboard and add the following code to the top of your entry file (ours is index.js).

1 require('dotenv').config()


Open the “MonnifyService.js” file and add the following code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const axios = require("axios"); const apiKey = process.env.MONNIFY_API_KEY; const apiSecret = process.env.MONNIFY_SECRET_KEY; const baseUrl = process.env.MONNIFY_BASE_URL; async function authenticate() { const clientIDSecretInBase64 = Buffer.from(apiKey + ':' + apiSecret).toString('base64'); const headers = { Authorization: 'Basic ' + clientIDSecretInBase64 } const response = await axios.post(baseUrl + '/api/v1/auth/login', null, { headers }); const { responseBody } = response.data; const { accessToken } = responseBody; } async function initialiseTransaction() { } module.exports = { authenticate: authenticate }

We’ve been able to authenticate against the Monnify authentication server and retrieve our accessToken. Remember as stated earlier, this token will be used as to access other Monnify’s API resources.

The token as seen below is valid for only approximately 60 minutes (1 hour)

Fig 2: Monnify authentication response

The payment flow will be as follows:

  1. The customer adds products to the cart

  2. The customer clicks the pay button

  3. The system sends the cart total amount to the payment API endpoint (we will implement this soon)

  4. The API calls Monnify’s initialise transaction API endpoint with the access token retrieved from the authentication call along side relevant details (we will mock some)

  5. A response containing a checkout url is received from it.

  6. We return the response to our front-end view to load to allow our customer pay for the products.

Do you notice a slight hiccup in the flow number 4? If the token is expired, we have to re-authenticate against Monnify’s auth endpoint, get a new token and call the initialise transaction endpoint.

This might lead to a bad User Experience (UX) because latency has been introduced. We wouldn’t want to keep our customers waiting for so long to allow them pay for their products.

What can we do? We CACHE and make our app faster and more responsive. With caching introduced, our new flow becomes:

  1. The customer adds products to the cart

  2. The customer clicks the pay button

  3. The system sends the cart total amount to the payment API endpoint (we will implement this soon)

  4. The API calls Monnify’s initialise transaction API endpoint with the access token retrieved from the authentication call along side relevant details (we will mock some)

  5. The token is cached for the TTL number returned in the response

  6. A response containing a checkout url is received from it.

  7. We return the response to our front-end view to load to allow our customer pay for the products.

  8. On the next request, pull token from cache and if not expired, use in request else get new token

We’ll make use of this cache module. Run the following command in your terminal:

$ npm i node-cache

Update your .env file to reflect the following:

MONNIFY_API_KEY=**********
MONNIFY_SECRET_KEY=************
MONNIFY_BASE_URL=https://api.monnify.com
MONNIFY_CONTRACT_CODE=**********
MONNIFY_CARD_PAYMENT_METHOD=CARD
MONNIFY_ACCOUNT_TRANSFER_PAYMENT_METHOD=ACCOUNT_TRANSFER
REDIRECT_URL=*********** (replace with the front-end url - coming soon)

Create a new file “CacheService.js”. This will be a wrapper around the cache module.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const NodeCache = require( "node-cache" ); const cache = new NodeCache(); async function set(key, value, ttl = null) { if (ttl) { return cache.set(key, value, ttl); } return cache.set(key, value); } async function get(key) { return cache.get(key); } async function del(key) { return cache.del(key); } module.exports = { set: set, get: get, del: del }

Next, let’s create an endpoint to accept cart details and process payment. The “MonnifyService.js” should be updated to look like this

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 const axios = require("axios"); const cacheService = require('./CacheService'); const apiKey = process.env.MONNIFY_API_KEY; const apiSecret = process.env.MONNIFY_SECRET_KEY; const baseUrl = process.env.MONNIFY_BASE_URL; const monnifyCardPaymentMethod = process.env.MONNIFY_CARD_PAYMENT_METHOD; const monnifyAccountTransferPaymentMethod = process.env.MONNIFY_ACCOUNT_TRANSFER_PAYMENT_METHOD; const monnifyContractCode = process.env.MONNIFY_CONTRACT_CODE; const redirectUrl = process.env.REDIRECT_URL; async function authenticate() { try { const cachedAccessToken = await cacheService.get('monnifyAccessToken'); if (cachedAccessToken) { return cachedAccessToken; } const clientIDSecretInBase64 = Buffer.from(apiKey + ':' + apiSecret).toString('base64'); const headers = { Authorization: 'Basic ' + clientIDSecretInBase64 } const response = await axios.post(baseUrl + '/api/v1/auth/login', null, { headers }); const { responseBody } = response.data; const { accessToken, expiresIn } = responseBody; await cacheService.set('monnifyAccessToken', accessToken, expiresIn); return accessToken; } catch (error) { console.error('Error authenticating on Monnify. Monnify error: ', error.response.data.responseMessage); console.error('Error authenticating on Monnify. Server error: ', error.message); } } async function initialiseTransaction(totalAmount, customerName, customerEmail, paymentDescription) { try { const dataToSend = { "amount": totalAmount, "customerName": customerName, "customerEmail": customerEmail, "paymentReference": Date.now(), "paymentDescription": paymentDescription, "currencyCode": "NGN", "contractCode": monnifyContractCode, "redirectUrl": redirectUrl, "paymentMethods":[monnifyCardPaymentMethod, monnifyAccountTransferPaymentMethod] } const accessToken = await authenticate(); const headers = { Authorization: 'Bearer ' + accessToken } const response = await axios.post(baseUrl + '/api/v1/merchant/transactions/init-transaction', dataToSend, { headers }); const { responseBody } = response.data; return responseBody; } catch (error) { console.error('Error initialising Monnify transaction. Monnify error: ', error.response.data.responseMessage); console.error('Error initialising Monnify transaction. Server error: ', error.message); } } async function handleWebhook(webhookData) { console.log(webhookData); } module.exports = { authenticate: authenticate, initialiseTransaction: initialiseTransaction, handleWebhook: handleWebhook }

Create a file “PaymentService.js”. We will handle payments in this file. At the moment, we are tightly coupled with Monnify but this is only a test app. Employ the strategy design pattern to choose the payment service at runtime. Add the following code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const monnifService = require('./MonnifyService'); async function processPayment(productId, totalAmount, paymentDescription, customerName, customerEmail) { // create an order in your db with product id, trans ref, payment ref and status pending const response = await monnifService.initialiseTransaction(totalAmount, paymentDescription, customerEmail, customerName); // we got a response from monnify if (Object.keys(response).length > 0) { const { checkoutUrl, transactionReference, paymentReference} = response; // update order in db with ID by changing the status to AWAITING_PAYMENT return checkoutUrl; } return null; } module.exports = { processPayment: processPayment }

Update your “index.js” (entry file) file to look like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 require('dotenv').config() const express = require("express"); const bodyParser = require('body-parser'); const cors = require("cors"); const helmet = require("helmet"); const monnifService = require('./MonnifyService'); const database = require('./database'); const paymentService = require('./PaymentService'); // initialise the express app const app = express(); // use helmet app.use(helmet()); // use the body parser app.use(bodyParser.json()); // next enable cors for all requests app.use(cors()); app.get('/api/product', async (request, response) => { const { products } = database; response.send(products).status(200); }); app.post('/api/product/process-payment', async (request, response) => { const { totalAmount, paymentDescription, customerName, customerEmail } = request.body; const checkoutUrl = await paymentService.processPayment(totalAmount, paymentDescription, customerName, customerEmail); if (checkoutUrl === null) { response.send('Error processing payment. Try again').status(400); } response.send(checkoutUrl).status(200); }) app.listen(3000, () => { console.log("Server running on port 3000"); });

Let’s test our new route “/api/product/process-payment". We will use Postman rather than making a curl request.

Your request body should look like this

Fig 3: Postman request and response

We extract the checkout url from the Monnify init-transaction api endpoint call. This checkout url will be loaded in the front-end for the customer to pay through the payment methods we specified earlier in our code (MonnifyService.js).

Congratulations, we can now process payments using Monnify.

Lastly, we need to know when a transaction is completed so that we can provide value to our customers either through an SMS or Email notification. The knowledge (transaction info) will also allow us update the order we created in the code earlier (PaymentService.js) to PAYMENT_RECEIVED and then carry on to dispatching the order to the customer. How do we get this knowledge?

Through a feature known as Webhooks . It is a way for an app/server to notify another app/server that something has happened.

Monnify in this case can notify us of a transaction that has been completed through the Webhook feature.

Supported webhook actions include:

  1. Card transaction completed or account transfer payment received - See https://teamapt.atlassian.net/wiki/spaces/MON/pages/213909300

  2. Settlement is made to your wallet or account - https://teamapt.atlassian.net/wiki/spaces/MON/pages/213909380

  3. Disbursement completed. This is in beta stage, using our new streamline event based notification system.

We are interested in the first action since our customers will pay for their products through a bank transfer or debit card transaction.

Let’s create an endpoint to receive webhook notifications from Monnify. Add the following code the “index.js” file.

1 2 3 4 5 6 app.post('/api/monnify/webhook', async (request, response) => { response.status(200); monnifService.handleWebhook(request.body); })

We respond with a status of 200 before processing the data to let Monnify know that the notification was successful. This prevents timeouts which causes Monnify to repeatedly notify us.

Your final “index.js” file should look like this

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 require('dotenv').config() const express = require("express"); const bodyParser = require('body-parser'); const cors = require("cors"); const helmet = require("helmet"); const monnifService = require('./MonnifyService'); const database = require('./database'); const paymentService = require('./PaymentService'); // initialise the express app const app = express(); // use helmet app.use(helmet()); // use the body parser app.use(bodyParser.json()); // next enable cors for all requests app.use(cors()); app.get('/api/product', async (request, response) => { const { products } = database; response.send(products).status(200); }); app.post('/api/product/process-payment', async (request, response) => { const { totalAmount, paymentDescription, customerName, customerEmail } = request.body; const checkoutUrl = await paymentService.processPayment(productId, totalAmount, paymentDescription, customerName, customerEmail); if (checkoutUrl === null) { response.send('Error processing payment. Try again').status(400); } response.send(checkoutUrl).status(200); }) app.post('/api/monnify/webhook', async (request, response) => { response.status(200); monnifService.handleWebhook(request.body); }) app.listen(3000, () => { console.log("Server running on port 3000"); });

Setup and test the web hook

Let’s set it up. Navigate to the Monnify dashboard → Settings → API Keys & Webhooks

Fig 3: Monnify Dashboard Settings - Webhook settings page

 

We might be tempted to just add a url like this “http://localhost/api/monnify/webhook” but the problem is localhost resolves to the computer on which the sever is running (our computer). How do we make Monnify notify our localhost if we haven’t deployed our app to production and gotten an internet address?

NGROK to the rescue. With this tool, we can expose our localhost over the internet. Set it up and run the following command in your terminal

$ ngrok http 3000

My server runs on port 3000, specify yours.

You should get a response like this

Fig 4: Ngrok initialise response

Copy the forwarding url (from https to .io) and paste the following in the webhook url input box on your dashboard and click the save button

https://aabe-105-112-38-79.ngrok.io/api/monnify/webhook

Monnify will be able to notify us on a successful transaction.

Let’s give it a test. Open post man and hit the “/api/product/process-payment" route.

Open the checkout link in your browser. You should see a view like this

Fig 5: Monnify checkout view

 

Use any payment method you like (assuming you are using your test credentials, your money won’t depart from you). After completing the payment, you should see a response like the following in your terminal:

Fig 6: Monnify transaction webhook notification

More specifically, this is the data returned

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 { transactionReference: 'MNFY|03|20211104113914|000379', paymentReference: '1636022352790', amountPaid: '500.00', totalPayable: '500.00', settlementAmount: '490.00', paidOn: '04/11/2021 11:39:28 AM', paymentStatus: 'PAID', paymentDescription: 'kelechi', transactionHash: 'f3013f6921e4df5215aaf7dab8cf7b2abae91dc512e4be55ef968b3555159b141a2ba9434d07be25e9091708a6fa806df77b668c2ede3d810bd65dad8434baf2', currency: 'NGN', paymentMethod: 'CARD', product: { type: 'WEB_SDK', reference: '1636022352790' }, cardDetails: {cardType: null, authorizationCode: null, last4: '1111', expMonth: '09', expYear: '22', bin: '411111', bankCode: null, bankName: null, reusable: false, countryCode: null, cardToken: null}, accountDetails: null, accountPayments: [], customer: { email: 'kelechi@gmail.com', name: 'Payment for product' }, metaData: {}}

 

The next thing we can do is use the payment reference to update the payment status of your created Order in your DB to PAYMENT_COMPLETED. Then you can follow up by emailing the customer or providing any value you deem fit.

CONGRATULATIONS. OUR BACKEND IS DONE.

LET’S GO BUILD OUR FRONT-END.