const GraphQLClient = require("graphql-request").GraphQLClient; const path = require("path"); const queries = require("../graphql-client/queries"); const Dinero = require("dinero.js"); const qs = require("query-string"); const axios = require("axios"); const moment = require("moment"); const logger = require("../utils/logger"); const InstanceManager = require("../utils/instanceMgr").default; const { sendTaskEmail } = require("../email/sendemail"); const generateEmailTemplate = require("../email/generateTemplate"); const { getEndpoints } = require("../email/tasksEmails"); require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); const domain = process.env.NODE_ENV ? "secure" : "test"; const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const { InstanceRegion } = require("../utils/instanceMgr"); const client = new SecretsManagerClient({ region: InstanceRegion() }); const gqlClient = require("../graphql-client/graphql-client").client; const getShopCredentials = async (bodyshop) => { // Development only if (process.env.NODE_ENV === undefined) { return { merchantkey: process.env.INTELLIPAY_MERCHANTKEY, apikey: process.env.INTELLIPAY_APIKEY }; } // Production code if (bodyshop?.imexshopid) { try { const secret = await client.send( new GetSecretValueCommand({ SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified }) ); return JSON.parse(secret.SecretString); } catch (error) { return { error: error.message }; } } }; exports.lightbox_credentials = async (req, res) => { logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null); const shopCredentials = await getShopCredentials(req.body.bodyshop); if (shopCredentials.error) { res.json(shopCredentials); return; } try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, data: qs.stringify({ ...shopCredentials, operatingenv: "businessattended" }), url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh }; const response = await axios(options); res.send(response.data); } catch (error) { //console.log(error); logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, { error: JSON.stringify(error) }); res.json({ error }); } }; exports.payment_refund = async (req, res) => { logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null); const shopCredentials = await getShopCredentials(req.body.bodyshop); 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: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund` }; const response = await axios(options); res.send(response.data); } catch (error) { //console.log(error); logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { error: JSON.stringify(error) }); res.json({ error }); } }; exports.generate_payment_url = async (req, res) => { logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null); const shopCredentials = await getShopCredentials(req.body.bodyshop); try { const options = { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, //TODO: Move these to environment variables/database. data: qs.stringify({ ...shopCredentials, //...req.body, 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 //The postback URL is set at the CP teller global terminal settings page. }), url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url` }; const response = await axios(options); res.send(response.data); } catch (error) { //console.log(error); logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { error: JSON.stringify(error) }); res.json({ error }); } }; exports.postback = async (req, res) => { try { logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body); const { body: values } = req; const comment = Buffer.from(values?.comment, "base64").toString(); if ((!values.invoice || values.invoice === "") && !comment) { //invoice is specified through the pay link. Comment by IO. logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body); res.sendStatus(200); return; } if (comment) { //Shifted the order to have this first to retain backwards compatibility for the old style of short link. //This has been triggered by IO and may have multiple jobs. const parsedComment = JSON.parse(comment); //Adding in the user email to the short pay email. //Need to check this to ensure backwards compatibility for clients that don't update. const partialPayments = Array.isArray(parsedComment) ? parsedComment : parsedComment.payments; const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, { ids: partialPayments.map((p) => p.jobid) }); const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { paymentInput: partialPayments.map((p) => ({ amount: p.amount, transactionid: values.authcode, payer: "Customer", type: values.cardtype, jobid: p.jobid, date: moment(Date.now()), payment_responses: { data: { amount: values.total, bodyshopid: jobs.jobs[0].shopid, jobid: p.jobid, declinereason: "Approved", ext_paymentid: values.paymentid, successful: true, response: values } } })) }); logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, JSON.stringify(jobs), { iprequest: values, paymentResult }); if (values.origin === "OneLink" && parsedComment.userEmail) { //Send an email, it was a text to pay link. try { const endPoints = getEndpoints(); sendTaskEmail({ to: parsedComment.userEmail, subject: `New Payment(s) Received - RO ${jobs.jobs.map((j) => j.ro_number).join(", ")}`, type: "html", html: generateEmailTemplate({ header: "New Payment(s) Received", subHeader: "", body: jobs.jobs .map( (job) => `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()} | $${partialPayments.find((p) => p.jobid === job.id).amount}` ) .join("
") }) }); } catch (error) { logger.log("intellipay-postback-app-email-error", "DEBUG", req.user?.email, JSON.stringify(jobs), { iprequest: values, paymentResult, error: error.message }); } } res.sendStatus(200); } else if (values.invoice) { //This is a link email that's been sent out. const job = await gqlClient.request(queries.GET_JOB_BY_PK, { id: values.invoice }); const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { paymentInput: { amount: values.total, transactionid: values.authcode, payer: "Customer", type: values.cardtype, jobid: values.invoice, date: moment(Date.now()) } }); const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { paymentResponse: { amount: values.total, bodyshopid: job.jobs_by_pk.shopid, paymentid: paymentResult.id, jobid: values.invoice, declinereason: "Approved", ext_paymentid: values.paymentid, successful: true, response: values } }); logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, values.invoice, { iprequest: values, responseResults, paymentResult }); res.sendStatus(200); } } catch (error) { logger.log("intellipay-postback-total-error", "ERROR", req.user?.email, null, { error: JSON.stringify(error), body: req.body }); res.status(400).json({ succesful: false, error: error.message }); } };