358 lines
16 KiB
JavaScript
358 lines
16 KiB
JavaScript
import {DeleteFilled} from "@ant-design/icons";
|
|
import {useLazyQuery, useMutation} from "@apollo/client";
|
|
import {Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic,} from "antd";
|
|
import axios from "axios";
|
|
import dayjs from "../../utils/day";
|
|
import React, {useState} from "react";
|
|
import {useTranslation} from "react-i18next";
|
|
import {connect} from "react-redux";
|
|
import {createStructuredSelector} from "reselect";
|
|
import {INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS,} from "../../graphql/payment_response.queries";
|
|
import {INSERT_NEW_PAYMENT} from "../../graphql/payments.queries";
|
|
import {insertAuditTrail} from "../../redux/application/application.actions";
|
|
import {toggleModalVisible} from "../../redux/modals/modals.actions";
|
|
import {selectCardPayment} from "../../redux/modals/modals.selectors";
|
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
cardPaymentModal: selectCardPayment,
|
|
bodyshop: selectBodyshop,
|
|
});
|
|
|
|
const mapDispatchToProps = (dispatch) => ({
|
|
insertAuditTrail: ({jobid, operation, type}) =>
|
|
dispatch(insertAuditTrail({jobid, operation, type})),
|
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
|
});
|
|
|
|
const CardPaymentModalComponent = ({
|
|
bodyshop,
|
|
cardPaymentModal,
|
|
toggleModalVisible,
|
|
insertAuditTrail,
|
|
}) => {
|
|
const {context} = cardPaymentModal;
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
|
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
|
const {t} = useTranslation();
|
|
|
|
const [, {data, refetch, queryLoading}] = useLazyQuery(
|
|
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
|
{
|
|
variables: {jobids: [context.jobid]},
|
|
skip: true,
|
|
}
|
|
);
|
|
|
|
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
|
|
//Initialize the intellipay window.
|
|
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
|
|
|
|
const {payments} = form.getFieldsValue();
|
|
|
|
await insertPaymentResponse({
|
|
variables: {
|
|
paymentResponse: payments.map((payment) => ({
|
|
amount: payment.amount,
|
|
bodyshopid: bodyshop.id,
|
|
jobid: payment.jobid,
|
|
declinereason: response.declinereason,
|
|
ext_paymentid: response.paymentid.toString(),
|
|
successful: false,
|
|
response,
|
|
})),
|
|
},
|
|
});
|
|
|
|
payments.forEach((payment) =>
|
|
insertAuditTrail({
|
|
jobid: payment.jobid,
|
|
operation: AuditTrailMapping.failedpayment(),
|
|
type: "failedpayment",})
|
|
);
|
|
});
|
|
};
|
|
|
|
const handleFinish = async (values) => {
|
|
try {
|
|
await insertPayment({
|
|
variables: {
|
|
paymentInput: values.payments.map((payment) => ({
|
|
amount: payment.amount,
|
|
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
|
payer: t("payments.labels.customer"),
|
|
type: values.paymentResponse.cardbrand,
|
|
jobid: payment.jobid,
|
|
date: dayjs(Date.now()),
|
|
payment_responses: {
|
|
data: [
|
|
{
|
|
amount: payment.amount,
|
|
bodyshopid: bodyshop.id,
|
|
|
|
jobid: payment.jobid,
|
|
declinereason: values.paymentResponse.declinereason,
|
|
ext_paymentid: values.paymentResponse.paymentid.toString(),
|
|
successful: true,
|
|
response: values.paymentResponse,
|
|
},
|
|
],
|
|
},
|
|
})),
|
|
},
|
|
refetchQueries: ["GET_JOB_BY_PK"],
|
|
});
|
|
toggleModalVisible();
|
|
} catch (error) {
|
|
console.error(error);
|
|
notification.open({
|
|
type: "error",
|
|
message: t("payments.errors.inserting", {error: error.message}),
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleIntelliPayCharge = async () => {
|
|
setLoading(true);
|
|
|
|
//Validate
|
|
try {
|
|
await form.validateFields();
|
|
} catch (error) {
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
|
bodyshop,
|
|
refresh: !!window.intellipay,
|
|
});
|
|
|
|
if (window.intellipay) {
|
|
// eslint-disable-next-line no-eval
|
|
eval(response.data);
|
|
SetIntellipayCallbackFunctions();
|
|
window.intellipay.autoOpen();
|
|
} else {
|
|
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 (
|
|
<Card title="Card Payment">
|
|
<Spin spinning={loading}>
|
|
<Form
|
|
onFinish={handleFinish}
|
|
form={form}
|
|
layout="vertical"
|
|
initialValues={{
|
|
payments: context.jobid ? [{jobid: context.jobid}] : [],
|
|
}}
|
|
>
|
|
<Form.List name={["payments"]}>
|
|
{(fields, {add, remove, move}) => {
|
|
return (
|
|
<div>
|
|
{fields.map((field, index) => (
|
|
<Form.Item key={field.key}>
|
|
<Row gutter={[16, 16]}>
|
|
<Col span={16}>
|
|
<Form.Item
|
|
key={`${index}jobid`}
|
|
label={t("jobs.fields.ro_number")}
|
|
name={[field.name, "jobid"]}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
},
|
|
]}
|
|
>
|
|
<JobSearchSelectComponent
|
|
notExported={false}
|
|
clm_no
|
|
/>
|
|
</Form.Item>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Form.Item
|
|
key={`${index}amount`}
|
|
label={t("payments.fields.amount")}
|
|
name={[field.name, "amount"]}
|
|
rules={[
|
|
{
|
|
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>
|
|
</div>
|
|
);
|
|
}}
|
|
</Form.List>
|
|
|
|
<Form.Item
|
|
shouldUpdate={(prevValues, curValues) =>
|
|
prevValues.payments?.map((p) => p?.jobid).join() !==
|
|
curValues.payments?.map((p) => p?.jobid).join()
|
|
}
|
|
>
|
|
{() => {
|
|
console.log("Updating the owner info section.");
|
|
//If all of the job ids have been fileld in, then query and update the IP field.
|
|
const {payments} = form.getFieldsValue();
|
|
if (
|
|
payments?.length > 0 &&
|
|
payments?.filter((p) => p?.jobid).length === payments?.length
|
|
) {
|
|
console.log("**Calling refetch.");
|
|
refetch({jobids: payments.map((p) => p.jobid)});
|
|
}
|
|
console.log(
|
|
"Acc info",
|
|
data,
|
|
payments && data && data.jobs.length > 0
|
|
? data.jobs.map((j) => j.ro_number).join(", ")
|
|
: null
|
|
);
|
|
return (
|
|
<>
|
|
<Input
|
|
className="ipayfield"
|
|
data-ipayname="account"
|
|
type="hidden"
|
|
value={
|
|
payments && data && data.jobs.length > 0
|
|
? data.jobs.map((j) => j.ro_number).join(", ")
|
|
: null
|
|
}
|
|
/>
|
|
<Input
|
|
className="ipayfield"
|
|
data-ipayname="email"
|
|
type="hidden"
|
|
value={
|
|
payments && data && data.jobs.length > 0
|
|
? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea
|
|
: null
|
|
}
|
|
/>
|
|
</>
|
|
);
|
|
}}
|
|
</Form.Item>
|
|
<Form.Item
|
|
shouldUpdate={(prevValues, curValues) =>
|
|
prevValues.payments?.map((p) => p?.amount).join() !==
|
|
curValues.payments?.map((p) => p?.amount).join()
|
|
}
|
|
>
|
|
{() => {
|
|
const {payments} = form.getFieldsValue();
|
|
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
|
return acc + (val?.amount || 0);
|
|
}, 0);
|
|
|
|
return (
|
|
<Space style={{float: "right"}}>
|
|
<Statistic
|
|
title="Amount To Charge"
|
|
value={totalAmountToCharge}
|
|
precision={2}
|
|
/>
|
|
<Input
|
|
className="ipayfield"
|
|
data-ipayname="amount"
|
|
type="hidden"
|
|
value={totalAmountToCharge?.toFixed(2)}
|
|
/>
|
|
<Button
|
|
type="primary"
|
|
// data-ipayname="submit"
|
|
className="ipayfield"
|
|
loading={queryLoading || loading}
|
|
disabled={!(totalAmountToCharge > 0)}
|
|
onClick={handleIntelliPayCharge}
|
|
>
|
|
{t("job_payments.buttons.proceedtopayment")}
|
|
</Button>
|
|
</Space>
|
|
);
|
|
}}
|
|
</Form.Item>
|
|
|
|
{/* Lightbox payment response when it is completed */}
|
|
<Form.Item name="paymentResponse" hidden>
|
|
<Input type="hidden"/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Spin>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default connect(
|
|
mapStateToProps,
|
|
mapDispatchToProps
|
|
)(CardPaymentModalComponent);
|