Merged in feature/IO-3056-Enhanced-Lightbox-Logging (pull request #2014)
Feature/IO-3056 Enhanced Lightbox Logging Approved-by: Patrick Fic
This commit is contained in:
@@ -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 { 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 axios from "axios";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -23,7 +23,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
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"))
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,7 +46,6 @@ const CardPaymentModalComponent = ({
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [paymentLink, setPaymentLink] = useState();
|
const [paymentLink, setPaymentLink] = useState();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
|
||||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -48,24 +54,33 @@ const CardPaymentModalComponent = ({
|
|||||||
skip: !context?.jobid
|
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 = () => {
|
const SetIntellipayCallbackFunctions = () => {
|
||||||
console.log("*** Set IntelliPay callback functions.");
|
console.log("*** Set IntelliPay callback functions.");
|
||||||
|
|
||||||
window.intellipay.runOnClose(() => {
|
window.intellipay.runOnClose(() => {
|
||||||
//window.intellipay.initialize();
|
//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.
|
//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.
|
//Add a slight delay to allow the refetch to properly get the data.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
|
if (actions?.refetch) actions.refetch();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}, 750);
|
}, 750);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.intellipay.runOnNonApproval(async function (response) {
|
window.intellipay.runOnNonApproval(async (response) => {
|
||||||
// Mutate unsuccessful payment
|
// Mutate unsuccessful payment
|
||||||
|
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
@@ -98,16 +113,19 @@ const CardPaymentModalComponent = ({
|
|||||||
//Validate
|
//Validate
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
await form.validateFields();
|
||||||
} catch (error) {
|
} catch {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iPayData = collectIPayFields();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
refresh: !!window.intellipay,
|
refresh: !!window.intellipay,
|
||||||
paymentSplitMeta: form.getFieldsValue()
|
paymentSplitMeta: form.getFieldsValue(),
|
||||||
|
iPayData: iPayData
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
@@ -116,8 +134,8 @@ const CardPaymentModalComponent = ({
|
|||||||
SetIntellipayCallbackFunctions();
|
SetIntellipayCallbackFunctions();
|
||||||
window.intellipay.autoOpen();
|
window.intellipay.autoOpen();
|
||||||
} else {
|
} else {
|
||||||
var rg = document.createRange();
|
const rg = document.createRange();
|
||||||
let node = rg.createContextualFragment(response.data);
|
const node = rg.createContextualFragment(response.data);
|
||||||
document.documentElement.appendChild(node);
|
document.documentElement.appendChild(node);
|
||||||
SetIntellipayCallbackFunctions();
|
SetIntellipayCallbackFunctions();
|
||||||
window.intellipay.isAutoOpen = true;
|
window.intellipay.isAutoOpen = true;
|
||||||
@@ -137,25 +155,27 @@ const CardPaymentModalComponent = ({
|
|||||||
//Validate
|
//Validate
|
||||||
try {
|
try {
|
||||||
await form.validateFields();
|
await form.validateFields();
|
||||||
} catch (error) {
|
} catch {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const iPayData = collectIPayFields();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
amount: payments?.reduce((acc, val) => {
|
amount: payments.reduce((acc, val) => acc + (val?.amount || 0), 0),
|
||||||
return acc + (val?.amount || 0);
|
account: payments && data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
||||||
}, 0),
|
|
||||||
account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
|
||||||
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
||||||
paymentSplitMeta: form.getFieldsValue()
|
paymentSplitMeta: form.getFieldsValue(),
|
||||||
|
iPayData: iPayData
|
||||||
});
|
});
|
||||||
if (response.data) {
|
|
||||||
setPaymentLink(response.data?.shorUrl);
|
if (response?.data?.shorUrl) {
|
||||||
navigator.clipboard.writeText(response.data?.shorUrl);
|
setPaymentLink(response.data.shorUrl);
|
||||||
|
await navigator.clipboard.writeText(response.data.shorUrl);
|
||||||
message.success(t("general.actions.copied"));
|
message.success(t("general.actions.copied"));
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -179,67 +199,44 @@ const CardPaymentModalComponent = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.List name={["payments"]}>
|
<Form.List name={["payments"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove }) => (
|
||||||
return (
|
<div>
|
||||||
<div>
|
{fields.map((field, index) => (
|
||||||
{fields.map((field, index) => (
|
<Form.Item key={field.key}>
|
||||||
<Form.Item key={field.key}>
|
<Row gutter={[16, 16]}>
|
||||||
<Row gutter={[16, 16]}>
|
<Col span={16}>
|
||||||
<Col span={16}>
|
<Form.Item
|
||||||
<Form.Item
|
key={`${index}jobid`}
|
||||||
key={`${index}jobid`}
|
label={t("jobs.fields.ro_number")}
|
||||||
label={t("jobs.fields.ro_number")}
|
name={[field.name, "jobid"]}
|
||||||
name={[field.name, "jobid"]}
|
rules={[{ required: true }]}
|
||||||
rules={[
|
>
|
||||||
{
|
<JobSearchSelectComponent notExported={false} clm_no />
|
||||||
required: true
|
</Form.Item>
|
||||||
//message: t("general.validation.required"),
|
</Col>
|
||||||
}
|
<Col span={6}>
|
||||||
]}
|
<Form.Item
|
||||||
>
|
key={`${index}amount`}
|
||||||
<JobSearchSelectComponent notExported={false} clm_no />
|
label={t("payments.fields.amount")}
|
||||||
</Form.Item>
|
name={[field.name, "amount"]}
|
||||||
</Col>
|
rules={[{ required: true }]}
|
||||||
<Col span={6}>
|
>
|
||||||
<Form.Item
|
<CurrencyFormItemComponent />
|
||||||
key={`${index}amount`}
|
</Form.Item>
|
||||||
label={t("payments.fields.amount")}
|
</Col>
|
||||||
name={[field.name, "amount"]}
|
<Col span={2}>
|
||||||
rules={[
|
<DeleteFilled style={{ margin: "1rem" }} onClick={() => remove(field.name)} />
|
||||||
{
|
</Col>
|
||||||
required: true
|
</Row>
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<CurrencyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col span={2}>
|
|
||||||
<DeleteFilled
|
|
||||||
style={{ margin: "1rem" }}
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Form.Item>
|
|
||||||
))}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => {
|
|
||||||
add();
|
|
||||||
}}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{t("general.actions.add")}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
))}
|
||||||
);
|
<Form.Item>
|
||||||
}}
|
<Button type="dashed" onClick={() => add()} style={{ width: "100%" }}>
|
||||||
|
{t("general.actions.add")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Form.List>
|
</Form.List>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -283,9 +280,7 @@ const CardPaymentModalComponent = ({
|
|||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0);
|
||||||
return acc + (val?.amount || 0);
|
|
||||||
}, 0);
|
|
||||||
return (
|
return (
|
||||||
<Space style={{ float: "right" }}>
|
<Space style={{ float: "right" }}>
|
||||||
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
@@ -6,7 +5,6 @@ const qs = require("query-string");
|
|||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const InstanceManager = require("../utils/instanceMgr").default;
|
|
||||||
const { sendTaskEmail } = require("../email/sendemail");
|
const { sendTaskEmail } = require("../email/sendemail");
|
||||||
const generateEmailTemplate = require("../email/generateTemplate");
|
const generateEmailTemplate = require("../email/generateTemplate");
|
||||||
const { getEndpoints } = require("../email/tasksEmails");
|
const { getEndpoints } = require("../email/tasksEmails");
|
||||||
@@ -53,14 +51,19 @@ const getShopCredentials = async (bodyshop) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.lightbox_credentials = async (req, res) => {
|
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);
|
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
||||||
|
|
||||||
if (shopCredentials.error) {
|
if (shopCredentials.error) {
|
||||||
|
logger.log("intellipay-credentials-error", "ERROR", req.user?.email, null, { message: shopCredentials.error });
|
||||||
res.json(shopCredentials);
|
res.json(shopCredentials);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -74,26 +77,39 @@ exports.lightbox_credentials = async (req, res) => {
|
|||||||
|
|
||||||
const response = await axios(options);
|
const response = await axios(options);
|
||||||
|
|
||||||
|
logger.log("intellipay-lightbox-success", "DEBUG", req.user?.email, null, {
|
||||||
|
responseData: response.data,
|
||||||
|
requestOptions: options
|
||||||
|
});
|
||||||
|
|
||||||
res.send(response.data);
|
res.send(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.log(error);
|
logger.log("intellipay-lightbox-error", "ERROR", req.user?.email, null, { message: error.message });
|
||||||
logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
|
res.json({ message: error.message });
|
||||||
error: JSON.stringify(error)
|
|
||||||
});
|
|
||||||
res.json({ error });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.payment_refund = async (req, res) => {
|
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);
|
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 {
|
try {
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||||
|
|
||||||
data: qs.stringify({
|
data: qs.stringify({
|
||||||
method: "payment_refund",
|
method: "payment_refund",
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
@@ -103,132 +119,216 @@ exports.payment_refund = async (req, res) => {
|
|||||||
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
|
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);
|
const response = await axios(options);
|
||||||
|
|
||||||
|
logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, {
|
||||||
|
responseData: response.data
|
||||||
|
});
|
||||||
|
|
||||||
res.send(response.data);
|
res.send(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.log(error);
|
|
||||||
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
|
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) => {
|
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);
|
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 {
|
try {
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||||
//TODO: Move these to environment variables/database.
|
|
||||||
data: qs.stringify({
|
data: qs.stringify({
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
//...req.body,
|
|
||||||
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
|
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
|
||||||
account: req.body.account,
|
account: req.body.account,
|
||||||
comment: req.body.comment,
|
comment: req.body.comment,
|
||||||
invoice: req.body.invoice,
|
invoice: req.body.invoice,
|
||||||
createshorturl: true
|
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`
|
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);
|
const response = await axios(options);
|
||||||
|
|
||||||
|
logger.log("intellipay-generate-payment-url-success", "DEBUG", req.user?.email, null, {
|
||||||
|
responseData: response.data
|
||||||
|
});
|
||||||
|
|
||||||
res.send(response.data);
|
res.send(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.log(error);
|
logger.log("intellipay-generate-payment-url-error", "ERROR", req.user?.email, null, {
|
||||||
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
|
error: error.message,
|
||||||
error: JSON.stringify(error)
|
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
|
//Reference: https://intellipay.com/dist/webapi26.html#operation/fee
|
||||||
exports.checkfee = async (req, res) => {
|
exports.checkfee = async (req, res) => {
|
||||||
// Requires amount, bodyshop.imexshopid, and state? to get data.
|
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, {
|
||||||
logger.log("intellipay-fee-check", "DEBUG", req.user?.email, null, 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) {
|
if (!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."
|
||||||
|
});
|
||||||
res.json({ fee: 0 });
|
res.json({ fee: 0 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shopCredentials = await getShopCredentials(req.body.bodyshop);
|
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 {
|
try {
|
||||||
const options = {
|
const options = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "content-type": "application/x-www-form-urlencoded" },
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||||
//TODO: Move these to environment variables/database.
|
|
||||||
data: qs.stringify(
|
data: qs.stringify(
|
||||||
{
|
{
|
||||||
method: "fee",
|
method: "fee",
|
||||||
...shopCredentials,
|
...shopCredentials,
|
||||||
amount: req.body.amount,
|
amount: req.body.amount,
|
||||||
paymenttype: `CC`,
|
paymenttype: `CC`,
|
||||||
cardnum: "4111111111111111", //Not needed per documentation, but incorrect values come back without it.
|
cardnum: "4111111111111111", // Required for compatibility with API
|
||||||
state:
|
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()
|
? 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`
|
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);
|
const response = await axios(options);
|
||||||
|
|
||||||
if (response.data?.error) {
|
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 });
|
res.status(400).json({ error: response.data.error });
|
||||||
} else if (response.data < 0) {
|
} 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." });
|
res.json({ error: "Fee amount negative. Check API credentials & account configuration." });
|
||||||
} else {
|
} else {
|
||||||
|
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, {
|
||||||
|
fee: response.data
|
||||||
|
});
|
||||||
res.json({ fee: response.data });
|
res.json({ fee: response.data });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.log(error);
|
logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, {
|
||||||
logger.log("intellipay-fee-check-error", "ERROR", req.user?.email, null, {
|
error: error.message,
|
||||||
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) => {
|
exports.postback = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
|
|
||||||
const { body: values } = req;
|
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.
|
//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);
|
res.sendStatus(200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comment) {
|
if (decodedComment) {
|
||||||
//Shifted the order to have this first to retain backwards compatibility for the old style of short link.
|
//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.
|
//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.
|
//Adding in the user email to the short pay email.
|
||||||
//Need to check this to ensure backwards compatibility for clients that don't update.
|
//Need to check this to ensure backwards compatibility for clients that don't update.
|
||||||
|
|
||||||
const partialPayments = Array.isArray(parsedComment) ? parsedComment : parsedComment.payments;
|
const partialPayments = Array.isArray(parsedComment) ? parsedComment : parsedComment.payments;
|
||||||
|
|
||||||
|
// Fetch jobs by job IDs
|
||||||
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
|
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
|
||||||
ids: partialPayments.map((p) => p.jobid)
|
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, {
|
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
|
||||||
paymentInput: partialPayments.map((p) => ({
|
paymentInput: partialPayments.map((p) => ({
|
||||||
amount: p.amount,
|
amount: p.amount,
|
||||||
@@ -250,13 +350,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
|
paymentResult
|
||||||
});
|
});
|
||||||
|
|
||||||
if (values.origin === "OneLink" && parsedComment.userEmail) {
|
if (values.origin === "OneLink" && parsedComment.userEmail) {
|
||||||
//Send an email, it was a text to pay link.
|
|
||||||
try {
|
try {
|
||||||
const endPoints = getEndpoints();
|
const endPoints = getEndpoints();
|
||||||
sendTaskEmail({
|
sendTaskEmail({
|
||||||
@@ -275,20 +374,23 @@ exports.postback = async (req, res) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("intellipay-postback-app-email-error", "DEBUG", req.user?.email, JSON.stringify(jobs), {
|
logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, {
|
||||||
iprequest: values,
|
error: error.message,
|
||||||
paymentResult,
|
jobs,
|
||||||
error: error.message
|
paymentResult
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} else if (values.invoice) {
|
} else if (values.invoice) {
|
||||||
//This is a link email that's been sent out.
|
|
||||||
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
|
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
|
||||||
id: values.invoice
|
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, {
|
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
|
||||||
paymentInput: {
|
paymentInput: {
|
||||||
amount: values.total,
|
amount: values.total,
|
||||||
@@ -300,6 +402,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, {
|
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
|
||||||
paymentResponse: {
|
paymentResponse: {
|
||||||
amount: values.total,
|
amount: values.total,
|
||||||
@@ -313,18 +419,17 @@ exports.postback = async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, values.invoice, {
|
logger.log("intellipay-postback-invoice-response-success", "DEBUG", req.user?.email, null, {
|
||||||
iprequest: values,
|
responseResults
|
||||||
responseResults,
|
|
||||||
paymentResult
|
|
||||||
});
|
});
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("intellipay-postback-total-error", "ERROR", req.user?.email, null, {
|
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
|
||||||
error: JSON.stringify(error),
|
error: error.message,
|
||||||
body: req.body
|
stack: error.stack,
|
||||||
|
iprequest: req.body
|
||||||
});
|
});
|
||||||
res.status(400).json({ succesful: false, error: error.message });
|
res.status(400).json({ successful: false, error: error.message });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user