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

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "intellipay_merchant_id" text
-- null unique;

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "intellipay_merchant_id" text
null unique;

View File

@@ -2832,3 +2832,15 @@ exports.GET_DOCUMENTS_BY_IDS = `
takenat takenat
} }
}`; }`;
exports.GET_JOBID_BY_MERCHANTID_RONUMBER = `
query GET_JOBID_BY_MERCHANTID_RONUMBER($merchantID: String!, $roNumber: String!) {
jobs(where: {ro_number: {_eq: $roNumber}, bodyshop: {intellipay_merchant_id: {_eq: $merchantID}}}) {
id
shopid
bodyshop {
id
intellipay_config
}
}
}`;

View File

@@ -18,21 +18,52 @@ const client = new SecretsManagerClient({
const gqlClient = require("../graphql-client/graphql-client").client; 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 * @description Get shop credentials from AWS Secrets Manager
* @param bodyshop * @param bodyshop
* @returns {Promise<{error}|{merchantkey: *, apikey: *}|any>} * @returns {Promise<{error}|{merchantkey: *, apikey: *}|any>}
*/ */
const getShopCredentials = async (bodyshop) => { const getShopCredentials = async (bodyshop) => {
// Development only // In Dev/Testing we will use the environment variables
if (process.env.NODE_ENV === undefined) { if (process.env?.NODE_ENV !== "production") {
return { return {
merchantkey: process.env.INTELLIPAY_MERCHANTKEY, merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY apikey: process.env.INTELLIPAY_APIKEY
}; };
} }
// Production code // In Production we will use the AWS Secrets Manager
if (bodyshop?.imexshopid) { if (bodyshop?.imexshopid) {
try { try {
const secret = await client.send( const secret = await client.send(
@@ -106,7 +137,10 @@ const lightboxCredentials = async (req, res) => {
...shopCredentials, ...shopCredentials,
operatingenv: "businessattended" 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); const response = await axios(options);
@@ -178,7 +212,11 @@ const paymentRefund = async (req, res) => {
paymentid: req.body.paymentid, paymentid: req.body.paymentid,
amount: req.body.amount 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, { logger.log("intellipay-refund-options-prepared", "DEBUG", req.user?.email, null, {
@@ -259,7 +297,10 @@ const generatePaymentUrl = async (req, res) => {
invoice: req.body.invoice, invoice: req.body.invoice,
createshorturl: true 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, { 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 { 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, { logger.log("intellipay-checkfee-options-prepared", "DEBUG", req.user?.email, null, {
@@ -506,19 +547,49 @@ const postBack = async (req, res) => {
} }
if (values?.invoice) { if (values?.invoice) {
const job = await gqlClient.request(queries.GET_JOB_BY_PK, { // Early Bail on Merchant ID
id: values.invoice 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, { // Early Bail on No Jobs Found
id: job.jobs_by_pk.shopid 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, { logger.log("intellipay-postback-invoice-job-fetched", "DEBUG", "api", null, {
job, job,
bodyshop,
...logResponseMeta ...logResponseMeta
}); });
@@ -528,7 +599,7 @@ const postBack = async (req, res) => {
transactionid: values.authcode, transactionid: values.authcode,
payer: "Customer", payer: "Customer",
type: ipMapping ? ipMapping[(values.cardtype || "").toLowerCase()] || values.cardtype : values.cardtype, type: ipMapping ? ipMapping[(values.cardtype || "").toLowerCase()] || values.cardtype : values.cardtype,
jobid: values.invoice, jobid: job.id,
date: moment(Date.now()) date: moment(Date.now())
} }
}); });
@@ -541,9 +612,9 @@ const postBack = async (req, res) => {
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: { paymentResponse: {
amount: values.total, amount: values.total,
bodyshopid: bodyshop.bodyshops_by_pk.id, bodyshopid: bodyshop.id,
paymentid: paymentResult.id, paymentid: paymentResult.id,
jobid: values.invoice, jobid: job.id,
declinereason: "Approved", declinereason: "Approved",
ext_paymentid: values.paymentid, ext_paymentid: values.paymentid,
successful: true, successful: true,
@@ -576,13 +647,10 @@ const postBack = async (req, res) => {
} }
}; };
const postBackCallBack = async (req, res) => {};
module.exports = { module.exports = {
lightboxCredentials, lightboxCredentials,
paymentRefund, paymentRefund,
generatePaymentUrl, generatePaymentUrl,
checkFee, checkFee,
postBack, postBack
postBackCallBack
}; };

View File

@@ -6,8 +6,7 @@ const {
paymentRefund, paymentRefund,
generatePaymentUrl, generatePaymentUrl,
postBack, postBack,
checkFee, checkFee
postBackCallBack
} = require("../intellipay/intellipay"); } = require("../intellipay/intellipay");
router.post("/lightbox_credentials", validateFirebaseIdTokenMiddleware, lightboxCredentials); router.post("/lightbox_credentials", validateFirebaseIdTokenMiddleware, lightboxCredentials);
@@ -15,6 +14,5 @@ router.post("/payment_refund", validateFirebaseIdTokenMiddleware, paymentRefund)
router.post("/generate_payment_url", validateFirebaseIdTokenMiddleware, generatePaymentUrl); router.post("/generate_payment_url", validateFirebaseIdTokenMiddleware, generatePaymentUrl);
router.post("/checkfee", validateFirebaseIdTokenMiddleware, checkFee); router.post("/checkfee", validateFirebaseIdTokenMiddleware, checkFee);
router.post("/postback", postBack); router.post("/postback", postBack);
router.post("/postback-callback", postBackCallBack);
module.exports = router; module.exports = router;