diff --git a/client/src/components/card-payment-modal/card-payment-modal.component..jsx b/client/src/components/card-payment-modal/card-payment-modal.component..jsx
index fcdbd417c..67fa15e4d 100644
--- a/client/src/components/card-payment-modal/card-payment-modal.component..jsx
+++ b/client/src/components/card-payment-modal/card-payment-modal.component..jsx
@@ -1,6 +1,6 @@
-import { DeleteFilled, CopyFilled } from "@ant-design/icons";
+import { CopyFilled, DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
-import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
+import { Button, Card, Col, Form, Input, message, notification, Row, Space, Spin, Statistic } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -23,7 +23,14 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
- insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
+ insertAuditTrail: ({ jobid, operation, type }) =>
+ dispatch(
+ insertAuditTrail({
+ jobid,
+ operation,
+ type
+ })
+ ),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
});
@@ -39,7 +46,6 @@ const CardPaymentModalComponent = ({
const [form] = Form.useForm();
const [paymentLink, setPaymentLink] = useState();
const [loading, setLoading] = useState(false);
- // const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
@@ -48,24 +54,33 @@ const CardPaymentModalComponent = ({
skip: !context?.jobid
});
- //Initialize the intellipay window.
+ const collectIPayFields = () => {
+ const iPayFields = document.querySelectorAll(".ipayfield");
+ const iPayData = {};
+ iPayFields.forEach((field) => {
+ iPayData[field.dataset.ipayname] = field.value;
+ });
+ return iPayData;
+ };
+
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
+
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
});
- window.intellipay.runOnApproval(async function (response) {
+ window.intellipay.runOnApproval(() => {
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
//Add a slight delay to allow the refetch to properly get the data.
setTimeout(() => {
- if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
+ if (actions?.refetch) actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
});
- window.intellipay.runOnNonApproval(async function (response) {
+ window.intellipay.runOnNonApproval(async (response) => {
// Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
@@ -98,16 +113,19 @@ const CardPaymentModalComponent = ({
//Validate
try {
await form.validateFields();
- } catch (error) {
+ } catch {
setLoading(false);
return;
}
+ const iPayData = collectIPayFields();
+
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay,
- paymentSplitMeta: form.getFieldsValue()
+ paymentSplitMeta: form.getFieldsValue(),
+ iPayData: iPayData
});
if (window.intellipay) {
@@ -116,8 +134,8 @@ const CardPaymentModalComponent = ({
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
} else {
- var rg = document.createRange();
- let node = rg.createContextualFragment(response.data);
+ const rg = document.createRange();
+ const node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
@@ -137,25 +155,27 @@ const CardPaymentModalComponent = ({
//Validate
try {
await form.validateFields();
- } catch (error) {
+ } catch {
setLoading(false);
return;
}
+ const iPayData = collectIPayFields();
+
try {
const { payments } = form.getFieldsValue();
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
- amount: payments?.reduce((acc, val) => {
- return acc + (val?.amount || 0);
- }, 0),
- account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
+ amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
+ account: payments && data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
- paymentSplitMeta: form.getFieldsValue()
+ paymentSplitMeta: form.getFieldsValue(),
+ iPayData: iPayData
});
- if (response.data) {
- setPaymentLink(response.data?.shorUrl);
- navigator.clipboard.writeText(response.data?.shorUrl);
+
+ if (response?.data?.shorUrl) {
+ setPaymentLink(response.data.shorUrl);
+ await navigator.clipboard.writeText(response.data.shorUrl);
message.success(t("general.actions.copied"));
}
setLoading(false);
@@ -179,67 +199,44 @@ const CardPaymentModalComponent = ({
}}
>
- {(fields, { add, remove, move }) => {
- return (
-
- {fields.map((field, index) => (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- remove(field.name);
- }}
- />
-
-
-
- ))}
-
-
+ {(fields, { add, remove }) => (
+
+ {fields.map((field, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ remove(field.name)} />
+
+
-
- );
- }}
+ ))}
+
+
+
+
+ )}
{() => {
const { payments } = form.getFieldsValue();
- const totalAmountToCharge = payments?.reduce((acc, val) => {
- return acc + (val?.amount || 0);
- }, 0);
+ const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0);
return (
diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js
index 4b2f89dcd..c839cb29d 100644
--- a/server/intellipay/intellipay.js
+++ b/server/intellipay/intellipay.js
@@ -1,4 +1,3 @@
-const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
@@ -6,7 +5,6 @@ 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");
@@ -53,14 +51,19 @@ const getShopCredentials = async (bodyshop) => {
};
exports.lightbox_credentials = async (req, res) => {
- logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
+ logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, {
+ iPayData: req.body.iPayData,
+ bodyshop: req.body.bodyshop
+ });
const shopCredentials = await getShopCredentials(req.body.bodyshop);
if (shopCredentials.error) {
+ logger.log("intellipay-credentials-error", "ERROR", req.user?.email, null, { message: shopCredentials.error });
res.json(shopCredentials);
return;
}
+
try {
const options = {
method: "POST",
@@ -74,26 +77,39 @@ exports.lightbox_credentials = async (req, res) => {
const response = await axios(options);
+ logger.log("intellipay-lightbox-success", "DEBUG", req.user?.email, null, {
+ responseData: response.data,
+ requestOptions: 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)
- });
+ logger.log("intellipay-lightbox-error", "ERROR", req.user?.email, null, { message: error.message });
res.json({ error });
}
};
exports.payment_refund = async (req, res) => {
- logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null);
+ logger.log("intellipay-refund-request-received", "DEBUG", req.user?.email, null, {
+ bodyshop: req.body.bodyshop,
+ paymentid: req.body.paymentid,
+ amount: req.body.amount
+ });
const shopCredentials = await getShopCredentials(req.body.bodyshop);
+ if (shopCredentials.error) {
+ logger.log("intellipay-refund-credentials-error", "ERROR", req.user?.email, null, {
+ credentialsError: shopCredentials.error
+ });
+ res.status(400).json({ error: shopCredentials.error });
+ return;
+ }
+
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
-
data: qs.stringify({
method: "payment_refund",
...shopCredentials,
@@ -103,132 +119,219 @@ exports.payment_refund = async (req, res) => {
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
};
+ logger.log("intellipay-refund-options-prepared", "DEBUG", req.user?.email, null, {
+ options
+ });
+
const response = await axios(options);
+ logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, {
+ responseData: response.data
+ });
+
res.send(response.data);
} catch (error) {
- //console.log(error);
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
- error: JSON.stringify(error)
+ error: error.message,
+ stack: error.stack,
+ requestOptions: {
+ paymentid: req.body.paymentid,
+ amount: req.body.amount
+ }
});
- res.json({ error });
+ res.status(500).json({ error: error.message });
}
};
exports.generate_payment_url = async (req, res) => {
- logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null);
+ logger.log("intellipay-generate-payment-url-received", "DEBUG", req.user?.email, null, {
+ bodyshop: req.body.bodyshop,
+ amount: req.body.amount,
+ account: req.body.account,
+ comment: req.body.comment,
+ invoice: req.body.invoice
+ });
+
const shopCredentials = await getShopCredentials(req.body.bodyshop);
+ if (shopCredentials.error) {
+ logger.log("intellipay-generate-payment-url-credentials-error", "ERROR", req.user?.email, null, {
+ credentialsError: shopCredentials.error
+ });
+ res.status(400).json({ error: shopCredentials.error });
+ return;
+ }
+
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`
};
+ logger.log("intellipay-generate-payment-url-options-prepared", "DEBUG", req.user?.email, null, {
+ options
+ });
+
const response = await axios(options);
+ logger.log("intellipay-generate-payment-url-success", "DEBUG", req.user?.email, null, {
+ responseData: response.data
+ });
+
res.send(response.data);
} catch (error) {
- //console.log(error);
- logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
- error: JSON.stringify(error)
+ logger.log("intellipay-generate-payment-url-error", "ERROR", req.user?.email, null, {
+ error: error.message,
+ stack: error.stack,
+ requestOptions: {
+ amount: req.body.amount,
+ account: req.body.account,
+ comment: req.body.comment,
+ invoice: req.body.invoice
+ }
});
- res.json({ error });
+ res.status(500).json({ error: error.message });
}
};
//Reference: https://intellipay.com/dist/webapi26.html#operation/fee
exports.checkfee = async (req, res) => {
- // Requires amount, bodyshop.imexshopid, and state? to get data.
- logger.log("intellipay-fee-check", "DEBUG", req.user?.email, null, null);
+ logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, {
+ bodyshop: req.body.bodyshop,
+ amount: req.body.amount
+ });
- //If there's no amount, there can't be a fee. Skip the call.
if (!req.body.amount || req.body.amount <= 0) {
+ logger.log(
+ "intellipay-checkfee-skip",
+ "DEBUG",
+ req.user?.email,
+ "Amount is zero or undefined, skipping fee check."
+ );
res.json({ fee: 0 });
return;
}
const shopCredentials = await getShopCredentials(req.body.bodyshop);
+ if (shopCredentials.error) {
+ logger.log("intellipay-checkfee-credentials-error", "ERROR", req.user?.email, null, {
+ credentialsError: shopCredentials.error
+ });
+ res.status(400).json({ error: shopCredentials.error });
+ return;
+ }
+
try {
const options = {
method: "POST",
headers: { "content-type": "application/x-www-form-urlencoded" },
- //TODO: Move these to environment variables/database.
data: qs.stringify(
{
method: "fee",
...shopCredentials,
amount: req.body.amount,
paymenttype: `CC`,
- cardnum: "4111111111111111", //Not needed per documentation, but incorrect values come back without it.
+ cardnum: "4111111111111111", // Required for compatibility with API
state:
- req.body.bodyshop?.state && req.body.bodyshop.state?.length === 2
+ req.body.bodyshop?.state && req.body.bodyshop.state.length === 2
? req.body.bodyshop.state.toUpperCase()
- : "ZZ" //Same as above
+ : "ZZ"
},
- { sort: false } //ColdFusion Query Strings depend on order. This preserves it.
+ { sort: false } // Ensure query string order is preserved
),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc`
};
+ logger.log("intellipay-checkfee-options-prepared", "DEBUG", req.user?.email, null, {
+ options
+ });
+
const response = await axios(options);
+
if (response.data?.error) {
+ logger.log("intellipay-checkfee-api-error", "ERROR", req.user?.email, null, {
+ apiError: response.data.error
+ });
res.status(400).json({ error: response.data.error });
} else if (response.data < 0) {
+ logger.log("intellipay-checkfee-negative-fee", "ERROR", req.user?.email, "Fee amount returned is negative.");
res.json({ error: "Fee amount negative. Check API credentials & account configuration." });
} else {
+ logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, {
+ fee: response.data
+ });
res.json({ fee: response.data });
}
} catch (error) {
- //console.log(error);
- logger.log("intellipay-fee-check-error", "ERROR", req.user?.email, null, {
- error: error.message
+ logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, {
+ error: error.message,
+ stack: error.stack,
+ amount: req.body.amount
});
- res.status(400).json({ error });
+ res.status(500).json({ error: error.message });
}
};
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();
+ logger.log("intellipay-postback-received", "DEBUG", req.user?.email, null, {
+ iprequest: values,
+ base64Comment: values?.comment || null
+ });
- if ((!values.invoice || values.invoice === "") && !comment) {
+ // Decode the base64 comment, if it exists
+ const decodedComment = values?.comment ? Buffer.from(values.comment, "base64").toString() : null;
+
+ logger.log("intellipay-postback-decoded-comment", "DEBUG", req.user?.email, null, {
+ decodedComment
+ });
+
+ if ((!values.invoice || values.invoice === "") && !decodedComment) {
//invoice is specified through the pay link. Comment by IO.
- logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
+ logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, {
+ reason: "No invoice or comment provided",
+ iprequest: values
+ });
res.sendStatus(200);
return;
}
- if (comment) {
+ if (decodedComment) {
//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);
+ const parsedComment = JSON.parse(decodedComment);
+
+ logger.log("intellipay-postback-parsed-comment", "DEBUG", req.user?.email, null, {
+ parsedComment
+ });
//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;
+ // Fetch jobs by job IDs
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
+ logger.log("intellipay-postback-jobs-fetched", "DEBUG", req.user?.email, null, {
+ jobs
+ });
+
+ // Insert new payments
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
@@ -250,13 +353,12 @@ exports.postback = async (req, res) => {
}
}))
});
- logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, JSON.stringify(jobs), {
- iprequest: values,
+
+ logger.log("intellipay-postback-payment-success", "DEBUG", req.user?.email, null, {
paymentResult
});
if (values.origin === "OneLink" && parsedComment.userEmail) {
- //Send an email, it was a text to pay link.
try {
const endPoints = getEndpoints();
sendTaskEmail({
@@ -275,20 +377,23 @@ exports.postback = async (req, res) => {
})
});
} catch (error) {
- logger.log("intellipay-postback-app-email-error", "DEBUG", req.user?.email, JSON.stringify(jobs), {
- iprequest: values,
- paymentResult,
- error: error.message
+ logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, {
+ error: error.message,
+ jobs,
+ paymentResult
});
}
}
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
});
+ logger.log("intellipay-postback-invoice-job-fetched", "DEBUG", req.user?.email, null, {
+ job
+ });
+
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
@@ -300,6 +405,10 @@ exports.postback = async (req, res) => {
}
});
+ logger.log("intellipay-postback-invoice-payment-success", "DEBUG", req.user?.email, null, {
+ paymentResult
+ });
+
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
@@ -313,18 +422,17 @@ exports.postback = async (req, res) => {
}
});
- logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, values.invoice, {
- iprequest: values,
- responseResults,
- paymentResult
+ logger.log("intellipay-postback-invoice-response-success", "DEBUG", req.user?.email, null, {
+ responseResults
});
res.sendStatus(200);
}
} catch (error) {
- logger.log("intellipay-postback-total-error", "ERROR", req.user?.email, null, {
- error: JSON.stringify(error),
- body: req.body
+ logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
+ error: error.message,
+ stack: error.stack,
+ iprequest: req.body
});
- res.status(400).json({ succesful: false, error: error.message });
+ res.status(400).json({ successful: false, error: error.message });
}
};