Move intellipay to server side processing.

This commit is contained in:
Patrick Fic
2024-04-25 16:43:49 -07:00
parent 1620b94a7b
commit a6b825ffdf
9 changed files with 536 additions and 179 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
@@ -3540,6 +3540,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>nobilllines</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>noneselected</name> <name>noneselected</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -14476,6 +14497,27 @@
<folder_node> <folder_node>
<name>titles</name> <name>titles</name>
<children> <children>
<concept_node>
<name>joblifecycle</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>labhours</name> <name>labhours</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -18101,6 +18143,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>media</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>message</name> <name>message</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19992,6 +20055,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>human_readable</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>
<name>percentage</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>relative_end</name> <name>relative_end</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20055,6 +20160,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>status</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>
<name>status_count</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>value</name> <name>value</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20081,6 +20228,27 @@
<folder_node> <folder_node>
<name>content</name> <name>content</name>
<children> <children>
<concept_node>
<name>calculated_based_on</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>current_status_accumulated_time</name> <name>current_status_accumulated_time</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20123,6 +20291,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>joblifecycle</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>
<name>jobs_in_since</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>legend_title</name> <name>legend_title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20319,6 +20529,53 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>titles</name>
<children>
<concept_node>
<name>dashboard</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>
<name>top_durations</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>
</children>
</folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -20531,6 +20788,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hint</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>payer</name> <name>payer</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -31013,6 +31291,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>labor_hrs</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>labor_rates_subtotal</name> <name>labor_rates_subtotal</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -39283,6 +39582,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>paymentupdate</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>stripe</name> <name>stripe</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -45374,6 +45694,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>job_lifecycle_date_detail</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>
<name>job_lifecycle_date_summary</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>jobs_completed_not_invoiced</name> <name>jobs_completed_not_invoiced</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -13,7 +13,6 @@ import {
notification, notification,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -22,7 +21,6 @@ import {
INSERT_PAYMENT_RESPONSE, INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS, QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries"; } from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors"; import { selectCardPayment } from "../../redux/modals/modals.selectors";
@@ -48,12 +46,12 @@ const CardPaymentModalComponent = ({
toggleModalVisible, toggleModalVisible,
insertAuditTrail, insertAuditTrail,
}) => { }) => {
const { context } = cardPaymentModal; const { context, actions } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
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);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -65,7 +63,6 @@ const CardPaymentModalComponent = ({
} }
); );
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window. //Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => { const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions."); console.log("*** Set IntelliPay callback functions.");
@@ -74,16 +71,20 @@ const CardPaymentModalComponent = ({
}); });
window.intellipay.runOnApproval(async function (response) { window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***"); //2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
form.setFieldValue("paymentResponse", response); //Add a slight delay to allow the refetch to properly get the data.
form.submit(); setTimeout(() => {
if (actions && actions.refetch && typeof actions.refetch === "function")
actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
}); });
window.intellipay.runOnNonApproval(async function (response) { window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
await insertPaymentResponse({ await insertPaymentResponse({
variables: { variables: {
paymentResponse: payments.map((payment) => ({ paymentResponse: payments.map((payment) => ({
@@ -108,50 +109,9 @@ const CardPaymentModalComponent = ({
}); });
}; };
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: moment(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 () => { const handleIntelliPayCharge = async () => {
setLoading(true); setLoading(true);
//Validate //Validate
try { try {
await form.validateFields(); await form.validateFields();
@@ -164,6 +124,7 @@ const CardPaymentModalComponent = ({
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(),
}); });
if (window.intellipay) { if (window.intellipay) {
@@ -192,7 +153,6 @@ const CardPaymentModalComponent = ({
<Card title="Card Payment"> <Card title="Card Payment">
<Spin spinning={loading}> <Spin spinning={loading}>
<Form <Form
onFinish={handleFinish}
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={{ initialValues={{
@@ -273,23 +233,14 @@ const CardPaymentModalComponent = ({
} }
> >
{() => { {() => {
console.log("Updating the owner info section.");
//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?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length payments?.filter((p) => p?.jobid).length === payments?.length
) { ) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) }); 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 ( return (
<> <>
<Input <Input
@@ -344,6 +295,13 @@ const CardPaymentModalComponent = ({
value={totalAmountToCharge?.toFixed(2)} value={totalAmountToCharge?.toFixed(2)}
hidden hidden
/> />
<Input
className="ipayfield"
data-ipayname="comment"
//type="hidden"
value={btoa(JSON.stringify(payments))}
hidden
/>
<Button <Button
type="primary" type="primary"
// data-ipayname="submit" // data-ipayname="submit"
@@ -358,11 +316,6 @@ const CardPaymentModalComponent = ({
); );
}} }}
</Form.Item> </Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form> </Form>
</Spin> </Spin>
</Card> </Card>

View File

@@ -304,7 +304,7 @@ export function JobsDetailHeaderActions({
disabled={!job.converted} disabled={!job.converted}
onClick={() => { onClick={() => {
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: { refetch },
context: { jobid: job.id }, context: { jobid: job.id },
}); });
}} }}

View File

@@ -139,8 +139,8 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
contentStyle={{ fontWeight: "600" }} contentStyle={{ fontWeight: "600" }}
column={4} column={4}
> >
<Descriptions.Item label={t("job_payments.titles.payer")}> <Descriptions.Item label={t("job_payments.titles.hint")}>
{record.payer} {payment_response?.response?.methodhint}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}> <Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""} {payment_response?.response?.nameOnCard ?? ""}
@@ -155,7 +155,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
{record.transactionid} {record.transactionid}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentid")}> <Descriptions.Item label={t("job_payments.titles.paymentid")}>
{payment_response?.response?.paymentreferenceid ?? ""} {payment_response?.ext_paymentid ?? ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}> <Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type} {record.type}

View File

@@ -885,6 +885,7 @@
"refhrs": "Refinish Hrs" "refhrs": "Refinish Hrs"
}, },
"titles": { "titles": {
"joblifecycle": "Job Lifecycle",
"labhours": "Total Body Hours", "labhours": "Total Body Hours",
"larhours": "Total Refinish Hours", "larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -899,8 +900,7 @@
"scheduledindate": "Sheduled In Today: {{date}}", "scheduledindate": "Sheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today", "scheduledintoday": "Sheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}", "scheduledoutdate": "Sheduled Out Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today", "scheduledouttoday": "Sheduled Out Today"
"joblifecycle": "Job Lifecycle"
} }
}, },
"dms": { "dms": {
@@ -1236,22 +1236,21 @@
"columns": { "columns": {
"duration": "Duration", "duration": "Duration",
"end": "End", "end": "End",
"human_readable": "Human Readable",
"percentage": "Percentage",
"relative_end": "Relative End", "relative_end": "Relative End",
"relative_start": "Relative Start", "relative_start": "Relative Start",
"start": "Start", "start": "Start",
"value": "Value",
"status": "Status", "status": "Status",
"percentage": "Percentage", "status_count": "In Status",
"human_readable": "Human Readable", "value": "Value"
"status_count": "In Status"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
}, },
"content": { "content": {
"calculated_based_on": "Calculated based on",
"current_status_accumulated_time": "Current Status Accumulated Time", "current_status_accumulated_time": "Current Status Accumulated Time",
"data_unavailable": " There is currently no Lifecycle data for this Job.", "data_unavailable": " There is currently no Lifecycle data for this Job.",
"joblifecycle": "",
"jobs_in_since": "Jobs in since",
"legend_title": "Legend", "legend_title": "Legend",
"loading": "Loading Job Timelines....", "loading": "Loading Job Timelines....",
"not_available": "N/A", "not_available": "N/A",
@@ -1259,12 +1258,14 @@
"title": "Job Lifecycle Component", "title": "Job Lifecycle Component",
"title_durations": "Historical Status Durations", "title_durations": "Historical Status Durations",
"title_loading": "Loading", "title_loading": "Loading",
"title_transitions": "Transitions", "title_transitions": "Transitions"
"calculated_based_on": "Calculated based on",
"jobs_in_since": "Jobs in since"
}, },
"errors": { "errors": {
"fetch": "Error getting Job Lifecycle Data" "fetch": "Error getting Job Lifecycle Data"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
} }
}, },
"job_payments": { "job_payments": {
@@ -1284,6 +1285,7 @@
"amount": "Amount", "amount": "Amount",
"dateOfPayment": "Date of Payment", "dateOfPayment": "Date of Payment",
"descriptions": "Payment Details", "descriptions": "Payment Details",
"hint": "Hint",
"payer": "Payer", "payer": "Payer",
"payername": "Payer Name", "payername": "Payer Name",
"paymentid": "Payment Reference ID", "paymentid": "Payment Reference ID",

View File

@@ -217,6 +217,7 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
@@ -884,6 +885,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -898,8 +900,7 @@
"scheduledindate": "", "scheduledindate": "",
"scheduledintoday": "", "scheduledintoday": "",
"scheduledoutdate": "", "scheduledoutdate": "",
"scheduledouttoday": "", "scheduledouttoday": ""
"joblifecycle": ""
} }
}, },
"dms": { "dms": {
@@ -1114,6 +1115,7 @@
"loadingshop": "Cargando datos de la tienda ...", "loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...", "loggingin": "Iniciando sesión ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1234,22 +1236,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1257,12 +1258,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": ""
}, },
"errors": { "errors": {
"fetch": "Error al obtener los datos del ciclo de vida del trabajo" "fetch": "Error al obtener los datos del ciclo de vida del trabajo"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1285,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",

View File

@@ -217,6 +217,7 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
@@ -884,6 +885,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -1113,6 +1115,7 @@
"loadingshop": "Chargement des données de la boutique ...", "loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...", "loggingin": "Vous connecter ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1233,22 +1236,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1256,13 +1258,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": "",
"joblifecycle": ""
}, },
"errors": { "errors": {
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches" "fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1285,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",

View File

@@ -2175,4 +2175,12 @@ exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: c
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
affected_rows affected_rows
} }
}`; }`;
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
}
}
`;

View File

@@ -8,21 +8,15 @@ const moment = require("moment");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
}); });
const domain = process.env.NODE_ENV ? "secure" : "test"; const domain = process.env.NODE_ENV ? "secure" : "test";
const { const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
SecretsManagerClient,
GetSecretValueCommand,
} = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({ const client = new SecretsManagerClient({
region: "ca-central-1", region: "ca-central-1" //TODO-AIO: instance manager required when merged to master-AIO
}); });
const gqlClient = require("../graphql-client/graphql-client").client; const gqlClient = require("../graphql-client/graphql-client").client;
@@ -32,7 +26,7 @@ const getShopCredentials = async (bodyshop) => {
if (process.env.NODE_ENV === undefined) { if (process.env.NODE_ENV === undefined) {
return { return {
merchantkey: process.env.INTELLIPAY_MERCHANTKEY, merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY, apikey: process.env.INTELLIPAY_APIKEY
}; };
} }
@@ -42,26 +36,20 @@ const getShopCredentials = async (bodyshop) => {
const secret = await client.send( const secret = await client.send(
new GetSecretValueCommand({ new GetSecretValueCommand({
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified
}) })
); );
return JSON.parse(secret.SecretString); return JSON.parse(secret.SecretString);
} catch (error) { } catch (error) {
return { return {
error: error.message, error: error.message
}; };
} }
} }
}; };
exports.lightbox_credentials = async (req, res) => { exports.lightbox_credentials = async (req, res) => {
logger.log( logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
"intellipay-lightbox-credentials",
"DEBUG",
req.user?.email,
null,
null
);
const shopCredentials = await getShopCredentials(req.body.bodyshop); const shopCredentials = await getShopCredentials(req.body.bodyshop);
@@ -75,11 +63,9 @@ exports.lightbox_credentials = async (req, res) => {
headers: { "content-type": "application/x-www-form-urlencoded" }, headers: { "content-type": "application/x-www-form-urlencoded" },
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
operatingenv: "businessattended", operatingenv: "businessattended"
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${ url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh
req.body.refresh ? "_refresh" : ""
}`, //autoterminal_refresh
}; };
const response = await axios(options); const response = await axios(options);
@@ -87,13 +73,9 @@ exports.lightbox_credentials = async (req, res) => {
res.send(response.data); res.send(response.data);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log( logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
"intellipay-lightbox-credentials-error", error: JSON.stringify(error)
"ERROR", });
req.user?.email,
null,
{ error: JSON.stringify(error) }
);
res.json({ error }); res.json({ error });
} }
}; };
@@ -112,9 +94,9 @@ exports.payment_refund = async (req, res) => {
method: "payment_refund", method: "payment_refund",
...shopCredentials, ...shopCredentials,
paymentid: req.body.paymentid, paymentid: req.body.paymentid,
amount: req.body.amount, amount: req.body.amount
}), }),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`, url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
}; };
const response = await axios(options); const response = await axios(options);
@@ -123,7 +105,7 @@ exports.payment_refund = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
@@ -141,15 +123,13 @@ exports.generate_payment_url = async (req, res) => {
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
//...req.body, //...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat( amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
"0.00"
),
account: req.body.account, account: req.body.account,
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.
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`, url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`
}; };
const response = await axios(options); const response = await axios(options);
@@ -158,56 +138,100 @@ exports.generate_payment_url = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
}; };
exports.postback = async (req, res) => { exports.postback = async (req, res) => {
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body); logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
const { body: values } = req; const { body: values } = req;
if (!values.invoice) { const comment = Buffer.from(values?.comment, "base64").toString();
if ((!values.invoice || values.invoice === "") && !comment) {
//invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
res.sendStatus(200); res.sendStatus(200);
return; return;
} }
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice,
});
// TODO add mutation to database
try { try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { if (values.invoice) {
paymentInput: { //This is a link email that's been sent out.
amount: values.total, const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
transactionid: `C00 ${values.authcode}`, id: values.invoice
payer: "Customer", });
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now()),
},
});
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentResponse: { paymentInput: {
amount: values.total, amount: values.total,
bodyshopid: job.jobs_by_pk.shopid, transactionid: values.authcode,
paymentid: paymentResult.id, payer: "Customer",
jobid: values.invoice, type: values.cardtype,
declinereason: "Approved", jobid: values.invoice,
ext_paymentid: values.paymentid, date: moment(Date.now())
successful: true, }
response: values, });
},
});
res.send({ message: "Postback Successful" }); const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
iprequest: values,
responseResults,
paymentResult
});
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, {
error: JSON.stringify(error), error: JSON.stringify(error),
body: req.body, body: req.body
}); });
res.status(400).json({ succesful: false, error: error.message }); res.status(400).json({ succesful: false, error: error.message });
} }