feature/IO-2885-IntelliPay-App-Postback

- Finish ticket
This commit is contained in:
Dave Richer
2025-04-01 15:15:48 -04:00
parent 09c1a8ae35
commit c78b9866a3
5 changed files with 108 additions and 24 deletions

View File

@@ -18,21 +18,52 @@ const client = new SecretsManagerClient({
const gqlClient = require("../graphql-client/graphql-client").client;
/**
* Generates a properly formatted Cpteller API URL
* @param {Object} options - URL configuration options
* @param {string} options.apiType - 'webapi' or 'custapi'
* @param {string} [options.version] - API version (e.g., '26' for webapi)
* @param {Object} [options.params] - URL query parameters
* @returns {string} - The formatted Cpteller URL
*/
const getCptellerUrl = (options) => {
const { apiType = "webapi", version, params = {} } = options;
// Base URL construction
let url = `https://${domain}.cpteller.com/api/`;
// Add version if specified for webapi
if (apiType === "webapi" && version) {
url += `${version}/`;
}
// Add the API endpoint
url += `${apiType}.cfc`;
// Add query parameters if any exist
const queryParams = new URLSearchParams(params).toString();
if (queryParams) {
url += `?${queryParams}`;
}
return url;
};
/**
* @description Get shop credentials from AWS Secrets Manager
* @param bodyshop
* @returns {Promise<{error}|{merchantkey: *, apikey: *}|any>}
*/
const getShopCredentials = async (bodyshop) => {
// Development only
if (process.env.NODE_ENV === undefined) {
// In Dev/Testing we will use the environment variables
if (process.env?.NODE_ENV !== "production") {
return {
merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY
};
}
// Production code
// In Production we will use the AWS Secrets Manager
if (bodyshop?.imexshopid) {
try {
const secret = await client.send(
@@ -106,7 +137,10 @@ const lightboxCredentials = async (req, res) => {
...shopCredentials,
operatingenv: "businessattended"
}),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh
url: getCptellerUrl({
apiType: "custapi",
params: { method: `autoterminal${req.body.refresh ? "_refresh" : ""}` }
})
};
const response = await axios(options);
@@ -178,7 +212,11 @@ const paymentRefund = async (req, res) => {
paymentid: req.body.paymentid,
amount: req.body.amount
}),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
url: getCptellerUrl({
apiType: "webapi",
version: "26",
params: { method: "payment_refund" }
})
};
logger.log("intellipay-refund-options-prepared", "DEBUG", req.user?.email, null, {
@@ -259,7 +297,10 @@ const generatePaymentUrl = async (req, res) => {
invoice: req.body.invoice,
createshorturl: true
}),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`
url: getCptellerUrl({
apiType: "custapi",
params: { method: "generate_lightbox_url" }
})
};
logger.log("intellipay-generate-payment-url-options-prepared", "DEBUG", req.user?.email, null, {
@@ -344,7 +385,7 @@ const checkFee = async (req, res) => {
},
{ sort: false } // Ensure query string order is preserved
),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc`
url: getCptellerUrl({ apiType: "webapi", version: "26" })
};
logger.log("intellipay-checkfee-options-prepared", "DEBUG", req.user?.email, null, {
@@ -506,19 +547,49 @@ const postBack = async (req, res) => {
}
if (values?.invoice) {
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
// Early Bail on Merchant ID
if (!values?.merchantid) {
logger.log("intellipay-postback-no-merchantid", "ERROR", "api", null, {
message: "Merchant ID is missing",
...logResponseMeta
});
return res.status(400).send("Bad Request: Merchant ID is missing");
}
const result = await gqlClient.request(queries.GET_JOBID_BY_MERCHANTID_RONUMBER, {
merchantID: values.merchantid,
roNumber: values.invoice
});
const bodyshop = await gqlClient.request(queries.GET_BODYSHOP_BY_ID, {
id: job.jobs_by_pk.shopid
});
// Early Bail on No Jobs Found
if (!result?.jobs?.length) {
logger.log("intellipay-postback-job-not-found", "ERROR", "api", null, {
message: "Job not found",
...logResponseMeta
});
const ipMapping = bodyshop.bodyshops_by_pk.intellipay_config?.payment_map;
return res.status(400).send("Bad Request: Job not found");
}
const job = result?.jobs?.[0];
const bodyshop = job?.bodyshop;
// Early Bail on no Bodyshop Found
if (!bodyshop) {
logger.log("intellipay-postback-bodyshop-not-found", "ERROR", "api", null, {
message: "Bodyshop not found",
...logResponseMeta
});
return res.status(400).send("Bad Request: Bodyshop not found");
}
const ipMapping = bodyshop?.intellipay_config?.payment_map;
logger.log("intellipay-postback-invoice-job-fetched", "DEBUG", "api", null, {
job,
bodyshop,
...logResponseMeta
});
@@ -528,7 +599,7 @@ const postBack = async (req, res) => {
transactionid: values.authcode,
payer: "Customer",
type: ipMapping ? ipMapping[(values.cardtype || "").toLowerCase()] || values.cardtype : values.cardtype,
jobid: values.invoice,
jobid: job.id,
date: moment(Date.now())
}
});
@@ -541,9 +612,9 @@ const postBack = async (req, res) => {
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: bodyshop.bodyshops_by_pk.id,
bodyshopid: bodyshop.id,
paymentid: paymentResult.id,
jobid: values.invoice,
jobid: job.id,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
@@ -576,13 +647,10 @@ const postBack = async (req, res) => {
}
};
const postBackCallBack = async (req, res) => {};
module.exports = {
lightboxCredentials,
paymentRefund,
generatePaymentUrl,
checkFee,
postBack,
postBackCallBack
postBack
};