...
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 | ||
---|---|---|
| ||
require('dotenv').config() |
Open the “MonnifyService.js” file and add the following code:
Code Block | |||
---|---|---|---|
|
| ||
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.
...
The customer adds products to the cart
The customer clicks the pay button
The system sends the cart total amount to the payment API endpoint (we will implement this soon)
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)
The token is cached for the TTL number returned in the response
A response containing a checkout url is received from it.
We return the response to our front-end view to load to allow our customer pay for the products.
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
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 | ||
---|---|---|
| ||
app.post('/api/monnify/webhook', async (request, response) => { response.status(200); monnifService.handleWebhook(request.body); }) |
...
More specifically, this is the data returned
Code Block | ||
---|---|---|
| ||
{ |
...
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.
...