IO-2933 Add in ability to text payments for multiple ROs.

This commit is contained in:
Patrick Fic
2024-09-16 14:33:17 -07:00
parent 6382fdf19c
commit cbc164dbeb
7 changed files with 3736 additions and 3643 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2"> <babeledit_project version="1.2" be_version="2.7.1">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -22361,6 +22361,27 @@
<folder_node> <folder_node>
<name>buttons</name> <name>buttons</name>
<children> <children>
<concept_node>
<name>create_short_link</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>goback</name> <name>goback</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,6 +1,6 @@
import { 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, Row, Space, Spin, Statistic, notification } 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";
@@ -29,7 +29,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
const { context, actions } = cardPaymentModal; const { context, actions } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [paymentLink, setPaymentLink] = useState();
const [loading, setLoading] = useState(false); 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);
@@ -51,8 +51,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
//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") if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
actions.refetch();
setLoading(false); setLoading(false);
toggleModalVisible(); toggleModalVisible();
}, 750); }, 750);
@@ -86,7 +85,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
}); });
}; };
const handleIntelliPayCharge = async () => { const handleIntelliPayCharge = async () => {
setLoading(true); setLoading(true);
//Validate //Validate
@@ -101,7 +99,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
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()
}); });
if (window.intellipay) { if (window.intellipay) {
@@ -126,6 +124,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
} }
}; };
const handleIntelliPayChargeShortLink = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
} catch (error) {
setLoading(false);
return;
}
try {
const { payments } = form.getFieldsValue();
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
amount: payments?.reduce((acc, val) => {
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)),
paymentSplitMeta: form.getFieldsValue()
});
if (response.data) {
setPaymentLink(response.data?.shorUrl);
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 ( return (
<Card title="Card Payment"> <Card title="Card Payment">
<Spin spinning={loading}> <Spin spinning={loading}>
@@ -208,10 +242,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
{() => { {() => {
//If all of the job ids have been fileld in, then query and update the IP field. //If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
if ( if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
refetch({ jobids: payments.map((p) => p.jobid) }); refetch({ jobids: payments.map((p) => p.jobid) });
} }
return ( return (
@@ -246,7 +277,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
const totalAmountToCharge = payments?.reduce((acc, val) => { const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0); return acc + (val?.amount || 0);
}, 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} />
@@ -273,11 +303,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
> >
{t("job_payments.buttons.proceedtopayment")} {t("job_payments.buttons.proceedtopayment")}
</Button> </Button>
<Space direction="vertical" align="center">
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
loading={queryLoading || loading}
disabled={!(totalAmountToCharge > 0)}
onClick={handleIntelliPayChargeShortLink}
>
{t("job_payments.buttons.create_short_link")}
</Button>
</Space>
</Space> </Space>
); );
}} }}
</Form.Item> </Form.Item>
</Form> </Form>
{paymentLink && (
<Space
style={{ cursor: "pointer", float: "right" }}
align="end"
onClick={() => {
navigator.clipboard.writeText(paymentLink);
message.success(t("general.actions.copied"));
}}
>
<div
onClick={() => {
//Copy the link.
}}
>
{paymentLink}
</div>
<CopyFilled />
</Space>
)}
</Spin> </Spin>
</Card> </Card>
); );

View File

@@ -30,8 +30,12 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
const handleFinish = async ({ amount }) => { const handleFinish = async ({ amount }) => {
setLoading(true); setLoading(true);
let p;
const p = parsePhoneNumber(job.ownr_ph1, "CA"); try {
p = parsePhoneNumber(job.ownr_ph1 || "", "CA");
} catch (error) {
console.log("Unable to part phone number");
}
setLoading(true); setLoading(true);
const response = await axios.post("/intellipay/generate_payment_url", { const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop, bodyshop,
@@ -42,17 +46,19 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
setLoading(false); setLoading(false);
setPaymentLink(response.data.shorUrl); setPaymentLink(response.data.shorUrl);
openChatByPhone({ if (p) {
phone_num: p.formatInternational(), openChatByPhone({
jobid: job.id phone_num: p.formatInternational(),
}); jobid: job.id
setMessage( });
t("payments.labels.smspaymentreminder", { setMessage(
shopname: bodyshop.shopname, t("payments.labels.smspaymentreminder", {
amount: amount, shopname: bodyshop.shopname,
payment_link: response.data.shorUrl amount: amount,
}) payment_link: response.data.shorUrl
); })
);
}
//Add in confirmation & errors. //Add in confirmation & errors.
if (callback) callback(); if (callback) callback();

View File

@@ -1373,6 +1373,7 @@
}, },
"job_payments": { "job_payments": {
"buttons": { "buttons": {
"create_short_link": "Generate Short Link",
"goback": "Go Back", "goback": "Go Back",
"proceedtopayment": "Proceed to Payment", "proceedtopayment": "Proceed to Payment",
"refundpayment": "Refund Payment" "refundpayment": "Refund Payment"

File diff suppressed because it is too large Load Diff

View File

@@ -1373,6 +1373,7 @@
}, },
"job_payments": { "job_payments": {
"buttons": { "buttons": {
"create_short_link": "",
"goback": "", "goback": "",
"proceedtopayment": "", "proceedtopayment": "",
"refundpayment": "" "refundpayment": ""

View File

@@ -129,6 +129,7 @@ exports.generate_payment_url = async (req, res) => {
//...req.body, //...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,
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. //The postback URL is set at the CP teller global terminal settings page.
@@ -162,7 +163,41 @@ exports.postback = async (req, res) => {
return; return;
} }
if (values.invoice) { if (comment) {
//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.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
} else if (values.invoice) {
//This is a link email that's been sent out. //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
@@ -198,39 +233,6 @@ exports.postback = async (req, res) => {
paymentResult paymentResult
}); });
res.sendStatus(200); res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
} }
} catch (error) { } catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {