Compare commits

..

1 Commits

Author SHA1 Message Date
Allan Carr
24a92e69f2 IO-3059 Kaizen Datapump Additional Shop
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-12-13 11:00:04 -08:00
4 changed files with 148 additions and 419 deletions

79
.gitattributes vendored
View File

@@ -1,80 +1 @@
# Ensure all text files use LF for line endings
* text eol=lf * text eol=lf
# Binary files should not be modified by Git
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.svg binary
# Fonts
*.woff binary
*.woff2 binary
*.ttf binary
*.otf binary
*.eot binary
# Videos
*.mp4 binary
*.mov binary
*.avi binary
*.mkv binary
*.webm binary
# Audio
*.mp3 binary
*.wav binary
*.ogg binary
*.flac binary
# Archives and compressed files
*.zip binary
*.gz binary
*.tar binary
*.7z binary
*.rar binary
# PDF and documents
*.pdf binary
*.doc binary
*.docx binary
*.xls binary
*.xlsx binary
*.ppt binary
*.pptx binary
# Exclude JSON and other data files from text processing, if necessary
*.json text
*.xml text
*.csv text
# Scripts and code files should maintain LF endings
*.js text eol=lf
*.jsx text eol=lf
*.ts text eol=lf
*.tsx text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.py text eol=lf
*.rb text eol=lf
*.java text eol=lf
*.php text eol=lf
# Git configuration files
.gitattributes text eol=lf
.gitignore text eol=lf
*.gitattributes text eol=lf
# Exclude some other potential binary files
*.db binary
*.sqlite binary
*.exe binary
*.dll binary

View File

@@ -1,6 +1,6 @@
import { CopyFilled, DeleteFilled } from "@ant-design/icons"; import { DeleteFilled, CopyFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Col, Form, Input, message, notification, Row, Space, Spin, Statistic } from "antd"; import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } 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,14 +23,7 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")) toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
}); });
@@ -46,6 +39,7 @@ 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();
@@ -54,33 +48,24 @@ const CardPaymentModalComponent = ({
skip: !context?.jobid skip: !context?.jobid
}); });
const collectIPayFields = () => { //Initialize the intellipay window.
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(() => { window.intellipay.runOnApproval(async function (response) {
//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?.refetch) actions.refetch(); if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
setLoading(false); setLoading(false);
toggleModalVisible(); toggleModalVisible();
}, 750); }, 750);
}); });
window.intellipay.runOnNonApproval(async (response) => { window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
@@ -113,21 +98,16 @@ const CardPaymentModalComponent = ({
//Validate //Validate
try { try {
await form.validateFields(); await form.validateFields();
} catch { } catch (error) {
setLoading(false); setLoading(false);
return; return;
} }
const iPayData = collectIPayFields();
const { payments } = form.getFieldsValue();
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,
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email }))
}); });
if (window.intellipay) { if (window.intellipay) {
@@ -136,8 +116,8 @@ const CardPaymentModalComponent = ({
SetIntellipayCallbackFunctions(); SetIntellipayCallbackFunctions();
window.intellipay.autoOpen(); window.intellipay.autoOpen();
} else { } else {
const rg = document.createRange(); var rg = document.createRange();
const node = rg.createContextualFragment(response.data); let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node); document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions(); SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true; window.intellipay.isAutoOpen = true;
@@ -157,27 +137,25 @@ const CardPaymentModalComponent = ({
//Validate //Validate
try { try {
await form.validateFields(); await form.validateFields();
} catch { } catch (error) {
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) => acc + (val?.amount || 0), 0), amount: payments?.reduce((acc, val) => {
account: payments && data?.jobs?.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null, return acc + (val?.amount || 0);
}, 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) {
if (response?.data?.shorUrl) { setPaymentLink(response.data?.shorUrl);
setPaymentLink(response.data.shorUrl); navigator.clipboard.writeText(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);
@@ -201,44 +179,67 @@ const CardPaymentModalComponent = ({
}} }}
> >
<Form.List name={["payments"]}> <Form.List name={["payments"]}>
{(fields, { add, remove }) => ( {(fields, { add, remove, move }) => {
<div> return (
{fields.map((field, index) => ( <div>
<Form.Item key={field.key}> {fields.map((field, index) => (
<Row gutter={[16, 16]}> <Form.Item key={field.key}>
<Col span={16}> <Row gutter={[16, 16]}>
<Form.Item <Col span={16}>
key={`${index}jobid`} <Form.Item
label={t("jobs.fields.ro_number")} key={`${index}jobid`}
name={[field.name, "jobid"]} label={t("jobs.fields.ro_number")}
rules={[{ required: true }]} name={[field.name, "jobid"]}
> rules={[
<JobSearchSelectComponent notExported={false} clm_no /> {
</Form.Item> required: true
</Col> //message: t("general.validation.required"),
<Col span={6}> }
<Form.Item ]}
key={`${index}amount`} >
label={t("payments.fields.amount")} <JobSearchSelectComponent notExported={false} clm_no />
name={[field.name, "amount"]} </Form.Item>
rules={[{ required: true }]} </Col>
> <Col span={6}>
<CurrencyFormItemComponent /> <Form.Item
</Form.Item> key={`${index}amount`}
</Col> label={t("payments.fields.amount")}
<Col span={2}> name={[field.name, "amount"]}
<DeleteFilled style={{ margin: "1rem" }} onClick={() => remove(field.name)} /> rules={[
</Col> {
</Row> required: true
//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
@@ -282,7 +283,9 @@ const CardPaymentModalComponent = ({
> >
{() => { {() => {
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0); const totalAmountToCharge = payments?.reduce((acc, val) => {
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} />

View File

@@ -16,7 +16,7 @@ const { sendServerEmail } = require("../email/sendemail");
const DineroFormat = "0,0.00"; const DineroFormat = "0,0.00";
const DateFormat = "MM/DD/YYYY"; const DateFormat = "MM/DD/YYYY";
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"]; const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW", "DEERFOOT"];
const ftpSetup = { const ftpSetup = {
host: process.env.KAIZEN_HOST, host: process.env.KAIZEN_HOST,

View File

@@ -1,16 +1,18 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path"); const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const qs = require("query-string"); 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");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const domain = process.env.NODE_ENV ? "secure" : "test"; const domain = process.env.NODE_ENV ? "secure" : "test";
@@ -50,43 +52,15 @@ const getShopCredentials = async (bodyshop) => {
} }
}; };
const decodeComment = (comment) => {
try {
return comment ? JSON.parse(Buffer.from(comment, "base64").toString()) : null;
} catch (error) {
return null; // Handle malformed base64 string gracefully
}
};
exports.lightbox_credentials = async (req, res) => { exports.lightbox_credentials = async (req, res) => {
const decodedComment = decodeComment(req.body?.comment); logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
const logMeta = {
iPayData: req.body?.iPayData,
decodedComment,
bodyshop: {
id: req.body?.bodyshop?.id,
imexshopid: req.body?.bodyshop?.imexshopid,
name: req.body?.bodyshop?.shopname
}
};
logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, logMeta);
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, { res.json(shopCredentials);
message: shopCredentials.error?.message,
...logMeta
});
res.json({
message: shopCredentials.error?.message,
type: "intellipay-credentials-error",
...logMeta
});
return; return;
} }
try { try {
const options = { const options = {
method: "POST", method: "POST",
@@ -100,61 +74,26 @@ 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, {
requestOptions: options,
...logMeta
});
res.send(response.data); res.send(response.data);
} catch (error) { } catch (error) {
logger.log("intellipay-lightbox-error", "ERROR", req.user?.email, null, { //console.log(error);
message: error?.message, logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
...logMeta error: JSON.stringify(error)
});
res.json({
message: error?.message,
type: "intellipay-lightbox-error",
...logMeta
}); });
res.json({ error });
} }
}; };
exports.payment_refund = async (req, res) => { exports.payment_refund = async (req, res) => {
const decodedComment = decodeComment(req.body.iPayData?.comment); logger.log("intellipay-refund", "DEBUG", req.user?.email, null, null);
const logResponseMeta = {
iPayData: req.body?.iPayData,
bodyshop: {
id: req.body.bodyshop?.id,
imexshopid: req.body.bodyshop?.imexshopid,
name: req.body.bodyshop?.shopname
},
paymentid: req.body?.paymentid,
amount: req.body?.amount,
decodedComment
};
logger.log("intellipay-refund-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
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,
...logResponseMeta
});
res.status(400).json({
credentialsError: shopCredentials.error,
type: "intellipay-refund-credentials-error",
...logResponseMeta
});
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,
@@ -164,255 +103,132 @@ 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, {
requestOptions: options,
...logResponseMeta
});
const response = await axios(options); const response = await axios(options);
logger.log("intellipay-refund-success", "DEBUG", req.user?.email, null, {
requestOptions: options,
...logResponseMeta
});
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, {
message: error?.message, error: JSON.stringify(error)
...logResponseMeta
});
res.status(500).json({
message: error?.message,
type: "intellipay-refund-error",
...logResponseMeta
}); });
res.json({ error });
} }
}; };
exports.generate_payment_url = async (req, res) => { exports.generate_payment_url = async (req, res) => {
const decodedComment = decodeComment(req.body.comment); logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null);
const logResponseMeta = {
iPayData: req.body?.iPayData,
bodyshop: {
id: req.body.bodyshop?.id,
imexshopid: req.body.bodyshop?.imexshopid,
name: req.body.bodyshop?.shopname
},
amount: req.body?.amount,
account: req.body?.account,
comment: req.body?.comment,
invoice: req.body?.invoice,
decodedComment
};
logger.log("intellipay-generate-payment-url-received", "DEBUG", req.user?.email, null, logResponseMeta);
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, {
message: shopCredentials.error?.message,
...logResponseMeta
});
res.status(400).json({
message: shopCredentials.error?.message,
type: "intellipay-generate-payment-url-credentials-error",
...logResponseMeta
});
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, {
requestOptions: options,
...logResponseMeta
});
const response = await axios(options); const response = await axios(options);
logger.log("intellipay-generate-payment-url-success", "DEBUG", req.user?.email, null, {
requestOptions: options,
shortUrl: response.data?.shorturl,
...logResponseMeta
});
res.send(response.data); res.send(response.data);
} catch (error) { } catch (error) {
logger.log("intellipay-generate-payment-url-error", "ERROR", req.user?.email, null, { //console.log(error);
message: error?.message, logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
...logResponseMeta error: JSON.stringify(error)
}); });
res.status(500).json({ message: error?.message, ...logResponseMeta }); res.json({ error });
} }
}; };
//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) => {
const logResponseMeta = { // Requires amount, bodyshop.imexshopid, and state? to get data.
bodyshop: { logger.log("intellipay-fee-check", "DEBUG", req.user?.email, null, null);
id: req.body?.bodyshop?.id,
imexshopid: req.body?.bodyshop?.imexshopid,
name: req.body?.bodyshop?.shopname,
state: req.body?.bodyshop?.state
},
amount: req.body?.amount
};
logger.log("intellipay-checkfee-request-received", "DEBUG", req.user?.email, null, logResponseMeta);
//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.",
...logResponseMeta
});
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, {
message: shopCredentials.error?.message,
...logResponseMeta
});
res.status(400).json({ error: shopCredentials.error?.message, ...logResponseMeta });
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", // Required for compatibility with API cardnum: "4111111111111111", //Not needed per documentation, but incorrect values come back without it.
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" : "ZZ" //Same as above
}, },
{ sort: false } // Ensure query string order is preserved { sort: false } //ColdFusion Query Strings depend on order. This preserves it.
), ),
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, {
requestOptions: options,
...logResponseMeta
});
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, { res.status(400).json({ error: response.data.error });
message: response.data?.error,
...logResponseMeta
});
res.status(400).json({
error: response.data?.error,
type: "intellipay-checkfee-api-error",
...logResponseMeta
});
} else if (response.data < 0) { } else if (response.data < 0) {
logger.log("intellipay-checkfee-negative-fee", "ERROR", req.user?.email, null, { res.json({ error: "Fee amount negative. Check API credentials & account configuration." });
message: "Fee amount returned is negative.",
...logResponseMeta
});
res.json({
error: "Fee amount negative. Check API credentials & account configuration.",
...logResponseMeta,
type: "intellipay-checkfee-negative-fee"
});
} else { } else {
logger.log("intellipay-checkfee-success", "DEBUG", req.user?.email, null, { res.json({ fee: response.data });
fee: response.data,
...logResponseMeta
});
res.json({ fee: response.data, ...logResponseMeta });
} }
} catch (error) { } catch (error) {
logger.log("intellipay-checkfee-error", "ERROR", req.user?.email, null, { //console.log(error);
message: error?.message, logger.log("intellipay-fee-check-error", "ERROR", req.user?.email, null, {
...logResponseMeta error: error.message
}); });
res.status(500).json({ error: error?.message, logResponseMeta }); res.status(400).json({ error });
} }
}; };
exports.postback = async (req, res) => { exports.postback = async (req, res) => {
const { body: values } = req;
const decodedComment = decodeComment(values?.comment);
const logResponseMeta = {
bodyshop: {
id: req.body?.bodyshop?.id,
imexshopid: req.body?.bodyshop?.imexshopid,
name: req.body?.bodyshop?.shopname,
state: req.body?.bodyshop?.state
},
iprequest: values,
decodedComment
};
logger.log("intellipay-postback-received", "DEBUG", req.user?.email, null, logResponseMeta);
try { try {
if ((!values.invoice || values.invoice === "") && !decodedComment) { 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. //invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, { logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
message: "No invoice or comment provided",
...logResponseMeta
});
res.sendStatus(200); res.sendStatus(200);
return; return;
} }
if (decodedComment) { if (comment) {
//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(decodedComment); const parsedComment = JSON.parse(comment);
logger.log("intellipay-postback-parsed-comment", "DEBUG", req.user?.email, null, {
parsedComment,
...logResponseMeta
});
//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,
parsedComment,
...logResponseMeta
});
// 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,
@@ -434,15 +250,13 @@ exports.postback = async (req, res) => {
} }
})) }))
}); });
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, JSON.stringify(jobs), {
logger.log("intellipay-postback-payment-success", "DEBUG", req.user?.email, null, { iprequest: values,
paymentResult, paymentResult
jobs,
parsedComment,
...logResponseMeta
}); });
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({
@@ -461,25 +275,20 @@ exports.postback = async (req, res) => {
}) })
}); });
} catch (error) { } catch (error) {
logger.log("intellipay-postback-email-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-app-email-error", "DEBUG", req.user?.email, JSON.stringify(jobs), {
message: error.message, iprequest: values,
jobs,
paymentResult, paymentResult,
...logResponseMeta error: error.message
}); });
} }
} }
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,
...logResponseMeta
});
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,
@@ -491,11 +300,6 @@ exports.postback = async (req, res) => {
} }
}); });
logger.log("intellipay-postback-invoice-payment-success", "DEBUG", req.user?.email, null, {
paymentResult,
...logResponseMeta
});
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,
@@ -509,17 +313,18 @@ exports.postback = async (req, res) => {
} }
}); });
logger.log("intellipay-postback-invoice-response-success", "DEBUG", req.user?.email, null, { logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, values.invoice, {
iprequest: values,
responseResults, responseResults,
...logResponseMeta paymentResult
}); });
res.sendStatus(200); res.sendStatus(200);
} }
} catch (error) { } catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-total-error", "ERROR", req.user?.email, null, {
message: error?.message, error: JSON.stringify(error),
...logResponseMeta body: req.body
}); });
res.status(400).json({ successful: false, error: error.message, ...logResponseMeta }); res.status(400).json({ succesful: false, error: error.message });
} }
}; };