Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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).

Code Block
languagejs
require('dotenv').config()


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

Code Block
language
js
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
}

...

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 customer customers waiting for so long to allow them pay for their products.

...

  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

...

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

Code Block
languagejs
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
}

...

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:

Code Block
languagejs
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:

Code Block
languagejs
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");
});

...

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 thjen then carry on to dispatching the order to the customer. How do we get this knowledge?

...

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

...

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

Code Block
languagejs
app.post('/api/monnify/webhook', async (request, response) => {
    response.status(200);

    monnifService.handleWebhook(request.body);
})

...

More specifically, this is the data returned

Code Block
languagejs
{

...


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.

...