DEV Community

Cover image for Optimizing Code Organization for Payment Gateway Integration on AWS Lambda
Rishad Omar
Rishad Omar

Posted on

Optimizing Code Organization for Payment Gateway Integration on AWS Lambda

Introduction

In a recent project, I integrated a payment gateway API into an application hosted on AWS Lambda cloud functions, powered by Node.js. This experience led me to develop a streamlined and organized code structure, and I'm excited to share my insights with you. While the examples I'll provide in this article won't cover every API implementation, they'll certainly serve as a solid foundation to illustrate the concept.

Implementation

Three files: (which you can organize to suit your structure)

payment.js (AWS Lambda interface)

'use strict';

const {
    getPlans,
    createCustomer,
} = require('./paystackApi');

const { handleSuccess, handleError } = require('./responseHandlers');

module.exports.paymentPlans = (event, context, callback) => {
    getPlans()
        .then((data) => {
            handleSuccess(data, callback);
        })
        .catch((error) => {
            handleError(error, callback);
        });
};

module.exports.createPaymentCustomer = (event, context, callback) => {
    const body = JSON.parse(event.body);
    const email = body.email;
    const first_name = body.first_name;
    const last_name = body.last_name;
    const phone = body.phone;

    if (email === undefined || first_name === undefined || last_name === undefined || phone === undefined) {
        handleError('Invalid request. Missing email or first_name or last_name or phone', callback);
        return;
    }

    createCustomer({ email: email, first_name: first_name, last_name: last_name, phone: phone })
        .then((data) => {
            handleSuccess(data, callback);
        })
        .catch((error) => {
            handleError(error, callback);
        });
};
Enter fullscreen mode Exit fullscreen mode

paystackApi.js (API requests and any logic to do with your API)

const fetch = require('node-fetch');
const process = require('process');

const PAYSTACK_BASE_URL = 'https://api.paystack.co';
const NUMBER_OF_TRIAL_DAYS = 7;

const getSecretKey = () => {
    const secretKey = process.env.PAYSTACK_SECRET_KEY;
    if (!secretKey) {
        throw new Error('PAYSTACK_SECRET_KEY environment variable is not set');
    }
    return secretKey;
};

/**
 * Get available configured plans
 */
const getPlans = () => {
    return new Promise(async (resolve, reject) => {
        const secretKey = getSecretKey();

        try {
            const response = await fetch(`${PAYSTACK_BASE_URL}/plan`, {
                method: 'GET',
                headers: {
                    Authorization: `Bearer ${secretKey}`,
                },
            });

            if (!response.ok) {
                const errorData = await response.json();
                reject(errorData.message);
                return;
            }

            const responseData = await response.json();
            const plans = [];
            responseData.data.forEach((plan) => {
                plans.push({
                    id: plan.id,
                    plan_code: plan.plan_code,
                    domain: plan.domain,
                    name: plan.name,
                    description: plan.description,
                    currency: plan.currency,
                    amount: plan.amount,
                    interval: plan.interval,
                    is_deleted: plan.is_deleted,
                    is_archived: plan.is_archived,
                });
            });
            resolve(plans);
        } catch (error) {
            console.log('Error in getting plans: ', error);
            reject(error);
        }
    });
};

/**
 * Register a new customer
 * CustomerDetails is on object with the following expected fields:
 *  email
 *  first_name
 *  last_name
 *  phone
 */
const createCustomer = (customerDetails) => {
    return new Promise(async (resolve, reject) => {
        const secretKey = getSecretKey();

        if (
            !customerDetails.email ||
            !customerDetails.first_name ||
            !customerDetails.last_name ||
            !customerDetails.phone
        ) {
            reject('Invalid customer details');
        }

        try {
            const response = await fetch(`${PAYSTACK_BASE_URL}/customer`, {
                method: 'POST',
                headers: {
                    Authorization: `Bearer ${secretKey}`,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(customerDetails),
            });

            if (!response.ok) {
                const errorData = await response.json();
                reject(errorData.message);
                return;
            }

            const responseData = await response.json();
            resolve(responseData.data);
        } catch (error) {
            reject(error);
        }
    });
};
Enter fullscreen mode Exit fullscreen mode

responseHandlers.js (useful functions to be used to respond to a Lambda request)

module.exports.handleError = (error, callback) => {
    let errorMessage = 'An error occurred while processing the request.';
    if (error instanceof Error) {
        errorMessage = error.message;
    } else if (typeof error === 'string') {
        errorMessage = error;
    }
    console.error(error);
    callback(null, {
        statusCode: 500,
        headers: {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Credentials': true,
        },
        body: JSON.stringify({ message: errorMessage }),
    });
};

module.exports.handleSuccess = (data, callback) => {
    const response = {
        statusCode: 200,
        headers: {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Credentials': true,
        },
        body: JSON.stringify(data, null, 4),
    };
    callback(null, response);
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

Consider taking your code organization to the next level by breaking down these files into smaller, more modular units. This approach offers several notable advantages:

  1. Enhanced Testability: By breaking down the code, individual components, like "paystackApi.js," become more testable and can be used independently, even outside of the Lambda functionality.

  2. Improved Maintainability: A clean separation of code into smaller units makes it easier to work with, code, and debug, which ultimately simplifies the maintenance process.

Taking these steps toward code organization can lead to a more robust and efficient development process.

Thanks

Image by lucabravo on Freepik

Github copilot for helping code the responses

ChatGPT for cleanin up my intro and conclusion

Top comments (0)