Improvements to intellipay script.

This commit is contained in:
Patrick Fic
2023-08-11 09:48:39 -07:00
parent cbe0c78553
commit c3fe763261
8 changed files with 3277 additions and 3115 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -1474,6 +1474,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>admin_jobuninvoice</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>admin_jobunvoid</name> <name>admin_jobunvoid</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3571,6 +3592,27 @@
<folder_node> <folder_node>
<name>validation</name> <name>validation</name>
<children> <children>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>inventoryquantity</name> <name>inventoryquantity</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4205,6 +4247,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>country</name> <name>country</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19213,6 +19276,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>openingip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>title</name> <name>title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -23689,6 +23773,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>date_void</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>ded_amt</name> <name>ded_amt</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -28469,6 +28574,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>contracts</name> <name>contracts</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -40685,6 +40811,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>parts_return_slip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>sublet_order</name> <name>sublet_order</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -44536,6 +44683,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>work_in_progress_jobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>work_in_progress_labour</name> <name>work_in_progress_labour</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,7 +1,17 @@
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect, useState } from "react";
import axios from "axios"; import axios from "axios";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, Card, Form, Input, InputNumber, Row, Select } from "antd"; import {
Button,
Card,
Form,
Input,
InputNumber,
Row,
Select,
Spin,
notification,
} from "antd";
import moment from "moment"; import moment from "moment";
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -16,6 +26,14 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { createStructuredSelector } from "reselect";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation }) =>
@@ -25,24 +43,39 @@ const mapDispatchToProps = (dispatch) => ({
const CardPaymentModalComponent = ({ const CardPaymentModalComponent = ({
bodyshop, bodyshop,
context, cardPaymentModal,
toggleModalVisible, toggleModalVisible,
insertAuditTrail, insertAuditTrail,
}) => { }) => {
const { context } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
const amount = Form.useWatch("amount", form); const amount = Form.useWatch("amount", form);
const payer = Form.useWatch("payer", form);
const jobid = Form.useWatch("jobid", form); const jobid = Form.useWatch("jobid", form);
const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation(); const { t } = useTranslation();
const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, { const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, {
variables: { jobid: context?.jobid ?? "" }, variables: { jobid: context?.jobid ?? "" },
skip: !context?.jobid,
}); });
const nonApproval = useCallback( //Initialize the intellipay window.
async (response) => { const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***");
form.setFieldValue("paymentResponse", response);
form.submit();
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
await insertPaymentResponse({ await insertPaymentResponse({
variables: { variables: {
@@ -57,221 +90,182 @@ const CardPaymentModalComponent = ({
}, },
}, },
}); });
// Insert failed payment to audit trail
insertAuditTrail({ insertAuditTrail({
jobid: jobid || context?.jobid, jobid: jobid || context?.jobid,
operation: AuditTrailMapping.failedpayment(), operation: AuditTrailMapping.failedpayment(),
}); });
}, });
[bodyshop, context, insertAuditTrail, insertPaymentResponse, jobid] };
);
const initIntellipayFunctions = useCallback(() => {
if (window.intellipay !== undefined && typeof jobid !== "undefined") {
console.log("intellipay init functions");
window.intellipay.runOnClose(() => {
window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
form.setFieldValue("paymentResponse", response);
form.submit();
toggleModalVisible();
});
window.intellipay.runOnNonApproval(nonApproval);
}
}, [form, jobid, nonApproval, toggleModalVisible]);
const initJobId = useCallback(() => {
if (context?.jobid) {
form.setFieldValue("jobid", context.jobid);
}
form.setFieldValue("payer", t("payments.labels.customer"));
}, [context?.jobid, form, t]);
useEffect(() => {
initJobId();
axios
.post("/intellipay/lightbox_credentials", { bodyshop })
.then((response) => {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
window.intellipay.initialize();
initIntellipayFunctions();
});
function handleEvents(...props) {
const operation = props[0].data.operation;
if (operation === "updateform") {
props[0].stopImmediatePropagation();
}
}
window.addEventListener("message", handleEvents, false);
return () => window.removeEventListener("message", handleEvents, false);
}, [bodyshop, initJobId, initIntellipayFunctions]);
const handleFinish = async (values) => { const handleFinish = async (values) => {
const paymentResult = await insertPayment({ try {
variables: { const paymentResult = await insertPayment({
paymentInput: { variables: {
amount: values.amount, paymentInput: {
transactionid: values.paymentResponse.receiptelements.transid, amount: values.amount,
payer: values.payer, transactionid: values.paymentResponse.receiptelements.transid,
type: values.paymentResponse.cardType, payer: t("payments.labels.customer"),
jobid: values.jobid, type: values.paymentResponse.cardType,
date: moment(Date.now()), jobid: values.jobid,
}, date: moment(Date.now()),
},
update(cache, { data }) {
cache.modify({
id: cache.identify({ id: values.jobid, __typename: "jobs" }),
fields: {
payments(cachedPayments) {
return [...data.insert_payments.returning, ...cachedPayments];
},
}, },
});
},
});
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: values.amount,
bodyshopid: bodyshop.id,
paymentid: paymentResult.data.insert_payments.returning[0].id,
jobid: values.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
}, },
}, refetchQueries: ["GET_JOB_BY_PK"],
}); update(cache, { data }) {
cache.modify({
id: cache.identify({ id: values.jobid, __typename: "jobs" }),
fields: {
payments(cachedPayments) {
return [...data.insert_payments.returning, ...cachedPayments];
},
},
});
},
});
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: values.amount,
bodyshopid: bodyshop.id,
paymentid: paymentResult.data.insert_payments.returning[0].id,
jobid: values.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
},
});
toggleModalVisible();
} catch (error) {
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => {
setLoading(true);
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
});
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
} catch (error) {
notification.open({
type: "error",
message: t("job_payments.notifications.error.openingip"),
});
setLoading(false);
}
}; };
return ( return (
<Card title="Card Payment"> <Card title="Card Payment">
<Form onFinish={handleFinish} form={form}> <Spin spinning={loading}>
<LayoutFormRow grow> <Form
<Form.Item onFinish={handleFinish}
name="jobid" form={form}
label={t("bills.fields.ro_number")} initialValues={{ jobid: context?.jobid }}
rules={[ >
{ <LayoutFormRow grow>
required: true, <Form.Item
// message: t("general.validation.required"), name="jobid"
}, label={t("bills.fields.ro_number")}
]} rules={[
> {
<JobSearchSelectComponent required: true,
disabled={context?.jobid} // message: t("general.validation.required"),
notExported={false} },
clm_no ]}
onChange={(e) => {
refetch({ jobid: e });
}}
/>
</Form.Item>
</LayoutFormRow>
{/* Lighbox Input amount needs to be hidden */}
<Input
className="ipayfield"
data-ipayname="amount"
type="hidden"
value={amount}
hidden
/>
<Input
className="ipayfield"
data-ipayname="account"
type="hidden"
value={data?.jobs_by_pk.ro_number}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
type="hidden"
value={data?.jobs_by_pk.owner.ownr_ea}
hidden
/>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" value={amount} />
</Form.Item>
<LayoutFormRow grow>
<Form.Item
label={t("payments.fields.payer")}
name="payer"
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<Select>
<Select.Option value={t("payments.labels.customer")}>
{t("payments.labels.customer")}
</Select.Option>
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label="Amount"
name="amount"
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Row justify="space-around">
<Button
type="primary"
data-ipayname="submit"
className="ipayfield"
disabled={!amount || !payer || !jobid}
> >
{t("job_payments.buttons.proceedtopayment")} <JobSearchSelectComponent
</Button> disabled={context?.jobid}
{context?.balance && ( notExported={false}
<DataLabel clm_no
valueStyle={{ onChange={(e) => {
color: context?.balance.getAmount() !== 0 ? "red" : "green", refetch({ jobid: e });
}} }}
label={t("payments.labels.balance")} />
</Form.Item>
</LayoutFormRow>
{/* Lighbox Input amount needs to be hidden */}
<Input
className="ipayfield"
data-ipayname="amount"
type="hidden"
value={amount}
hidden
/>
<Input
className="ipayfield"
data-ipayname="account"
type="hidden"
value={data?.jobs_by_pk.ro_number}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
type="hidden"
value={data?.jobs_by_pk.owner.ownr_ea}
hidden
/>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" value={amount} />
</Form.Item>
<LayoutFormRow grow>
<Form.Item
label="Amount"
name="amount"
rules={[
{
required: true,
// message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Row justify="space-around">
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
disabled={!amount || !jobid}
onClick={handleIntelliPayCharge}
> >
{context?.balance.toFormat()} {t("job_payments.buttons.proceedtopayment")}
</DataLabel> </Button>
)} {context?.balance && (
</Row> <DataLabel
</LayoutFormRow> valueStyle={{
</Form> color: context?.balance.getAmount() !== 0 ? "red" : "green",
}}
label={t("payments.labels.balance")}
>
{context?.balance.toFormat()}
</DataLabel>
)}
</Row>
</LayoutFormRow>
</Form>
</Spin>
</Card> </Card>
); );
}; };
export default connect(null, mapDispatchToProps)(CardPaymentModalComponent); export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalComponent);

View File

@@ -22,7 +22,7 @@ function CardPaymentModalContainer({
toggleModalVisible, toggleModalVisible,
bodyshop, bodyshop,
}) { }) {
const { context, visible } = cardPaymentModal; const { visible } = cardPaymentModal;
const { t } = useTranslation(); const { t } = useTranslation();
const handleCancel = () => { const handleCancel = () => {
@@ -35,7 +35,7 @@ function CardPaymentModalContainer({
return ( return (
<Modal <Modal
visible={visible} open={visible}
onOk={handleOK} onOk={handleOK}
onCancel={handleCancel} onCancel={handleCancel}
footer={[ footer={[
@@ -46,7 +46,7 @@ function CardPaymentModalContainer({
width="60%" width="60%"
destroyOnClose destroyOnClose
> >
<CardPaymentModalComponent bodyshop={bodyshop} context={context} /> <CardPaymentModalComponent />
</Modal> </Modal>
); );
} }

View File

@@ -18,7 +18,7 @@ export default function DataLabel({
<div {...props} style={{ display: "flex" }}> <div {...props} style={{ display: "flex" }}>
<div <div
style={{ style={{
flex: 2, // flex: 2,
marginRight: ".2rem", marginRight: ".2rem",
}} }}
> >

File diff suppressed because it is too large Load Diff

View File

@@ -1198,6 +1198,7 @@
"notifications": { "notifications": {
"error": { "error": {
"description": "", "description": "",
"openingip": "",
"title": "" "title": ""
} }
}, },

View File

@@ -1198,6 +1198,7 @@
"notifications": { "notifications": {
"error": { "error": {
"description": "", "description": "",
"openingip": "",
"title": "" "title": ""
} }
}, },

View File

@@ -59,11 +59,7 @@ exports.lightbox_credentials = async (req, res) => {
//TODO: Move these to environment variables/database. //TODO: Move these to environment variables/database.
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
operatingenv: operatingenv: "businessattended",
// process.env.NODE_ENV === undefined
// ? process.env.NODE_ENV
// :
"businessattended",
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal`, url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal`,
}; };
@@ -114,6 +110,7 @@ exports.generate_payment_url = async (req, res) => {
...shopCredentials, ...shopCredentials,
...req.body, ...req.body,
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`,
}; };