408 lines
12 KiB
JavaScript
408 lines
12 KiB
JavaScript
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<void>}
|
|
*/
|
|
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<void>}
|
|
*/
|
|
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,
|
|
...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<void>}
|
|
*/
|
|
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<void>}
|
|
*/
|
|
const checkFee = async (req, res) => {
|
|
const logResponseMeta = {
|
|
bodyshop: {
|
|
id: req.body?.bodyshop?.id,
|
|
imexshopid: req.body?.bodyshop?.imexshopid,
|
|
name: req.body?.bodyshop?.shopname,
|
|
state: req.body?.bodyshop?.state
|
|
},
|
|
amount: req.body?.amount
|
|
};
|
|
|
|
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
|
|
|
|
if (!isNumber(req.body?.amount) || req.body?.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(req.body.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: req.body.amount,
|
|
paymenttype: `CC`,
|
|
cardnum: "4111111111111111", // Required for compatibility with API
|
|
state:
|
|
req.body.bodyshop?.state && req.body.bodyshop.state.length === 2
|
|
? req.body.bodyshop.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 response = await axios(options);
|
|
|
|
if (response.data?.error) {
|
|
logger.log("intellipay-checkfee-api-error", "ERROR", req.user?.email, null, {
|
|
message: response.data?.error,
|
|
...logResponseMeta
|
|
});
|
|
|
|
return res.status(400).json({
|
|
error: response.data?.error,
|
|
type: "intellipay-checkfee-api-error",
|
|
...logResponseMeta
|
|
});
|
|
}
|
|
|
|
if (response.data < 0) {
|
|
logger.log("intellipay-checkfee-negative-fee", "ERROR", req.user?.email, null, {
|
|
message: "Fee amount returned is negative.",
|
|
...logResponseMeta
|
|
});
|
|
|
|
return res.json({
|
|
error: "Fee amount negative. Check API credentials & account configuration.",
|
|
...logResponseMeta,
|
|
type: "intellipay-checkfee-negative-fee"
|
|
});
|
|
}
|
|
|
|
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, {
|
|
fee: response.data,
|
|
...logResponseMeta
|
|
});
|
|
|
|
return res.json({ fee: response.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<void>}
|
|
*/
|
|
/**
|
|
* 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
|
|
};
|