import { CopyFilled, DeleteFilled } from "@ant-design/icons"; import { useLazyQuery, useMutation } from "@apollo/client"; 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"; 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 { 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"; import { getCurrentUser } from "../../firebase/firebase.utils"; const mapStateToProps = createStructuredSelector({ cardPaymentModal: selectCardPayment, bodyshop: selectBodyshop, currentUser: getCurrentUser }); const mapDispatchToProps = (dispatch) => ({ insertAuditTrail: ({ jobid, operation, type }) => dispatch( insertAuditTrail({ jobid, operation, type }) ), toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")) }); const CardPaymentModalComponent = ({ bodyshop, currentUser, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => { const { context, actions } = cardPaymentModal; const [form] = Form.useForm(); const [paymentLink, setPaymentLink] = useState(); const [loading, setLoading] = useState(false); 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: !context?.jobid }); 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(() => { //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?.refetch) actions.refetch(); setLoading(false); toggleModalVisible(); }, 750); }); window.intellipay.runOnNonApproval(async (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 handleIntelliPayCharge = async () => { setLoading(true); //Validate try { await form.validateFields(); } catch { setLoading(false); return; } const iPayData = collectIPayFields(); const { payments } = form.getFieldsValue(); try { const response = await axios.post("/intellipay/lightbox_credentials", { bodyshop, refresh: !!window.intellipay, paymentSplitMeta: form.getFieldsValue(), iPayData: iPayData, comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })) }); if (window.intellipay) { // eslint-disable-next-line no-eval eval(response.data); pollForIntelliPay(() => { SetIntellipayCallbackFunctions(); window.intellipay.autoOpen(); }); } else { const rg = document.createRange(); const node = rg.createContextualFragment(response.data); document.documentElement.appendChild(node); pollForIntelliPay(() => { SetIntellipayCallbackFunctions(); window.intellipay.isAutoOpen = true; window.intellipay.initialize(); }); } } catch (error) { notification.open({ type: "error", message: t("job_payments.notifications.error.openingip") }); setLoading(false); } }; const handleIntelliPayChargeShortLink = async () => { setLoading(true); //Validate try { await form.validateFields(); } 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) => 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(), iPayData: iPayData }); if (response?.data?.shorUrl) { setPaymentLink(response.data.shorUrl); await navigator.clipboard.writeText(response.data.shorUrl); message.success(t("general.actions.copied")); } setLoading(false); } catch (error) { notification.open({ type: "error", message: t("job_payments.notifications.error.openingip") }); setLoading(false); } }; return (
{(fields, { add, remove }) => (
{fields.map((field, index) => ( remove(field.name)} /> ))}
)}
prevValues.payments?.map((p) => p?.jobid + p?.amount).join() !== curValues.payments?.map((p) => p?.jobid + p?.amount).join() } > {() => { //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) { refetch({ jobids: payments.map((p) => p.jobid) }); } return ( <> 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null } /> 0 ? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea : null } /> ); }} prevValues.payments?.map((p) => p?.amount).join() !== curValues.payments?.map((p) => p?.amount).join() } > {() => { const { payments } = form.getFieldsValue(); const totalAmountToCharge = payments?.reduce((acc, val) => acc + (val?.amount || 0), 0); return ( ); }}
{paymentLink && ( { navigator.clipboard.writeText(paymentLink); message.success(t("general.actions.copied")); }} >
{paymentLink}
)}
); }; export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent); //Poll for window.IntelliPay.fixAmount for 5 seconds. If it doesn't come up, just try anyways to force the possible error. function pollForIntelliPay(callbackFunction) { const timeout = 5000; const interval = 150; // Poll every 100 milliseconds const startTime = Date.now(); function checkFixAmount() { if (window.intellipay && window.intellipay.fixAmount !== undefined) { callbackFunction(); return; } if (Date.now() - startTime >= timeout) { console.log("Stopped polling IntelliPay after 10 seconds. Attemping to set functions anyways."); callbackFunction(); return; } setTimeout(checkFixAmount, interval); } checkFixAmount(); }