const Dinero = require("dinero.js"); const qs = require("query-string"); const axios = require("axios"); const logger = require("../utils/logger"); const { isEmpty, isNumber } = require("lodash"); const handleCommentBasedPayment = require("./lib/handleCommentBasedPayment"); const handleInvoiceBasedPayment = require("./lib/handleInvoiceBasedPayment"); const logValidationError = require("./lib/handlePaymentValidationError"); const getCptellerUrl = require("./lib/getCptellerUrl"); const getShopCredentials = require("./lib/getShopCredentials"); const decodeComment = require("./lib/decodeComment"); /** * @description Get lightbox credentials for the shop * @param req * @param res * @returns {Promise} */ const lightboxCredentials = async (req, res) => { const decodedComment = decodeComment(req.body?.comment); const logMeta = { iPayData: req.body?.iPayData, decodedComment, bodyshop: { id: req.body?.bodyshop?.id, imexshopid: req.body?.bodyshop?.imexshopid, name: req.body?.bodyshop?.shopname } }; logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, logMeta); const shopCredentials = await getShopCredentials(req.body.bodyshop); if (shopCredentials?.error) { logger.log("intellipay-credentials-error", "ERROR", req.user?.email, null, { message: shopCredentials.error?.message, ...logMeta }); return res.json({ message: shopCredentials.error?.message, type: "intellipay-credentials-error", ...logMeta }); } try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, data: qs.stringify({ ...shopCredentials, operatingenv: "businessattended" }), url: getCptellerUrl({ apiType: "custapi", params: { method: `autoterminal${req.body.refresh ? "_refresh" : ""}` } }) }; const response = await axios(options); logger.log("intellipay-lightbox-success", "DEBUG", req.user?.email, null, { requestOptions: options, ...logMeta }); return res.send(response.data); } catch (error) { logger.log("intellipay-lightbox-error", "ERROR", req.user?.email, null, { message: error?.message, ...logMeta }); return res.json({ message: error?.message, type: "intellipay-lightbox-error", ...logMeta }); } }; /** * @description Process payment refund * @param req * @param res * @returns {Promise} */ const paymentRefund = async (req, res) => { const decodedComment = decodeComment(req.body.iPayData?.comment); const logResponseMeta = { iPayData: req.body?.iPayData, bodyshop: { id: req.body.bodyshop?.id, imexshopid: req.body.bodyshop?.imexshopid, name: req.body.bodyshop?.shopname }, paymentid: req.body?.paymentid, amount: req.body?.amount, decodedComment }; logger.log("intellipay-refund-request-received", "DEBUG", req.user?.email, null, logResponseMeta); const shopCredentials = await getShopCredentials(req.body.bodyshop); if (shopCredentials?.error) { logger.log("intellipay-refund-credentials-error", "ERROR", req.user?.email, null, { credentialsError: shopCredentials.error, ...logResponseMeta }); return res.status(400).json({ credentialsError: shopCredentials.error, type: "intellipay-refund-credentials-error", ...logResponseMeta }); } try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, data: qs.stringify({ method: "payment_refund", ...shopCredentials, paymentid: req.body.paymentid, amount: req.body.amount }), url: getCptellerUrl({ apiType: "webapi", version: "26", params: { method: "payment_refund" } }) }; logger.log("intellipay-refund-options-prepared", "DEBUG", req.user?.email, null, { requestOptions: options, ...logResponseMeta }); const response = await axios(options); logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, { requestOptions: options, response: response?.data, ...logResponseMeta }); return res.send(response.data); } catch (error) { logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { message: error?.message, ...logResponseMeta }); return res.status(500).json({ message: error?.message, type: "intellipay-refund-error", ...logResponseMeta }); } }; /** * @description Generate payment URL for the shop * @param req * @param res * @returns {Promise} */ const generatePaymentUrl = async (req, res) => { const decodedComment = decodeComment(req.body.comment); const logResponseMeta = { iPayData: req.body?.iPayData, bodyshop: { id: req.body.bodyshop?.id, imexshopid: req.body.bodyshop?.imexshopid, name: req.body.bodyshop?.shopname }, amount: req.body?.amount, account: req.body?.account, comment: req.body?.comment, invoice: req.body?.invoice, decodedComment }; logger.log("intellipay-generate-payment-url-received", "DEBUG", req.user?.email, null, logResponseMeta); const shopCredentials = await getShopCredentials(req.body.bodyshop); if (shopCredentials?.error) { logger.log("intellipay-generate-payment-url-credentials-error", "ERROR", req.user?.email, null, { message: shopCredentials.error?.message, ...logResponseMeta }); return res.status(400).json({ message: shopCredentials.error?.message, type: "intellipay-generate-payment-url-credentials-error", ...logResponseMeta }); } try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, data: qs.stringify({ ...shopCredentials, amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"), account: req.body.account, comment: req.body.comment, invoice: req.body.invoice, createshorturl: true }), url: getCptellerUrl({ apiType: "custapi", params: { method: "generate_lightbox_url" } }) }; logger.log("intellipay-generate-payment-url-options-prepared", "DEBUG", req.user?.email, null, { requestOptions: options, ...logResponseMeta }); const response = await axios(options); logger.log("intellipay-generate-payment-url-success", "DEBUG", req.user?.email, null, { requestOptions: options, shortUrl: response.data?.shorturl, ...logResponseMeta }); return res.send(response.data); } catch (error) { logger.log("intellipay-generate-payment-url-error", "ERROR", req.user?.email, null, { message: error?.message, ...logResponseMeta }); return res.status(500).json({ message: error?.message, ...logResponseMeta }); } }; /** * @description Check the fee for a given amount * Reference: https://intellipay.com/dist/webapi26.html#operation/fee * @param req * @param res * @returns {Promise} */ const checkFee = async (req, res) => { const { bodyshop = {}, amount } = req.body || {}; const { id, imexshopid, shopname, state } = bodyshop; const logResponseMeta = { bodyshop: { id, imexshopid, name: shopname, state }, amount }; logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta); if (!isNumber(amount) || amount <= 0) { logger.log("intellipay-checkfee-skip", "DEBUG", req.user?.email, null, { message: "Amount is zero or undefined, skipping fee check.", ...logResponseMeta }); return res.json({ fee: 0 }); } const shopCredentials = await getShopCredentials(bodyshop); if (shopCredentials?.error) { logger.log("intellipay-checkfee-credentials-error", "ERROR", req.user?.email, null, { message: shopCredentials.error?.message, ...logResponseMeta }); return res.status(400).json({ error: shopCredentials.error?.message, ...logResponseMeta }); } try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, data: qs.stringify( { method: "fee", ...shopCredentials, amount: String(amount), // Type cast to string as required by API paymenttype: "CC", cardnum: "4111111111111111", // Required for compatibility with API state: state?.toUpperCase() || "ZZ" }, { sort: false } // Ensure query string order is preserved ), url: getCptellerUrl({ apiType: "webapi", version: "26" }) }; logger.log("intellipay-checkfee-options-prepared", "DEBUG", req.user?.email, null, { requestOptions: options, ...logResponseMeta }); const { data } = await axios(options); if (data?.error || data < 0) { const errorType = data?.error ? "intellipay-checkfee-api-error" : "intellipay-checkfee-negative-fee"; const errorMessage = data?.error ? data?.error : "Fee amount negative. Check API credentials & account configuration."; logger.log(errorType, "ERROR", req.user?.email, null, { message: errorMessage, data, ...logResponseMeta }); return res.status(400).json({ error: errorMessage, type: errorType, data, ...logResponseMeta }); } logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, { fee: data, ...logResponseMeta }); return res.json({ fee: data, ...logResponseMeta }); } catch (error) { logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, { message: error?.message, ...logResponseMeta }); return res.status(500).json({ error: error?.message, logResponseMeta }); } }; /** * @description Handle the postback from Intellipay * @param req * @param res * @returns {Promise} */ /** * Handle the postback from Intellipay payment system */ const postBack = async (req, res) => { const { body: values } = req; const decodedComment = decodeComment(values?.comment); const logMeta = { iprequest: values, decodedComment }; logger.log("intellipay-postback-received", "DEBUG", "api", null, logMeta); try { // Handle empty/invalid requests if (isEmpty(values?.invoice) && !decodedComment) { logger.log("intellipay-postback-ignored", "DEBUG", "api", null, { message: "No invoice or comment provided", ...logMeta }); return res.sendStatus(200); } // Process payment based on data type if (decodedComment) { return await handleCommentBasedPayment(values, decodedComment, logger, logMeta, res); } else if (values?.invoice) { return await handleInvoiceBasedPayment(values, logger, logMeta, res); } else { // This should be caught by first validation, but as a safeguard logValidationError("intellipay-postback-invalid", "No valid invoice or comment provided", logMeta); return res.status(400).send("Bad Request: No valid invoice or comment provided"); } } catch (error) { logger.log("intellipay-postback-error", "ERROR", "api", null, { message: error?.message, ...logMeta }); return res.status(400).json({ successful: false, error: error.message, ...logMeta }); } }; module.exports = { lightboxCredentials, paymentRefund, generatePaymentUrl, checkFee, postBack };