diff --git a/client/src/components/_test/paymentMethod.jsx b/client/src/components/_test/paymentMethod.jsx
deleted file mode 100644
index 8e722cc0b..000000000
--- a/client/src/components/_test/paymentMethod.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from "react";
-import { connect } from "react-redux";
-import { createStructuredSelector } from "reselect";
-import { logImEXEvent } from "../../firebase/firebase.utils";
-import { setEmailOptions } from "../../redux/email/email.actions";
-import { selectBodyshop } from "../../redux/user/user.selectors";
-import { TemplateList } from "../../utils/TemplateConstants";
-
-const mapStateToProps = createStructuredSelector({
- bodyshop: selectBodyshop,
-});
-
-const mapDispatchToProps = (dispatch) => ({
- setEmailOptions: (e) => dispatch(setEmailOptions(e)),
-});
-
-function Test({ bodyshop, setEmailOptions }) {
- return (
-
-
-
-
- );
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(Test);
diff --git a/client/src/components/_test/payment_response.json b/client/src/components/_test/payment_response.json
new file mode 100644
index 000000000..2087d3e32
--- /dev/null
+++ b/client/src/components/_test/payment_response.json
@@ -0,0 +1,63 @@
+{
+ "status": 24201299,
+ "custid": 19607899,
+ "paymentid": 24201299,
+ "response": "A",
+ "authcode": "498680",
+ "declinereason": "Approved",
+ "fee": 0,
+ "invoice": "",
+ "account": "john",
+ "amount": 1000,
+ "amountincludesfee": false,
+ "total": 1000,
+ "paymenttype": "C",
+ "methodhint": "VI ***1111",
+ "cardbrand": "Visa",
+ "cardnumdisplay": "***1111",
+ "receiptelements": {
+ "authcode": "498680",
+ "cust_srv_ph_num": "1-555-555-5555",
+ "rcpt_pg_ftr_txt": "Thank You\nPlease Come Again",
+ "rcpt_currency": "USD",
+ "responsecode": "A",
+ "rcpt_pay_mthd": "Visa",
+ "transid": "C00 915799",
+ "merch_disp_nm": "CP Devel Test",
+ "rcpt_input_mthd": "Keyed",
+ "rcpt_pg_hdr_txt": "Welcome!",
+ "rcpt_tran_time": "Thursday February 23 2023, 11:25:36 pm +08",
+ "rcpt_trans_type": "Normal Transaction (Sale)",
+ "message": "Approved",
+ "rcpt_dba_addr": "1234 Storefront Ave\nSome City, UT 84111",
+ "avsdata": "N",
+ "receiptrequirements": "S",
+ "rcpt_cardnum": "************1111",
+ "cv2result": "M",
+ "rfnd_policy_txt": "No Refunds\nStore Credit Only",
+ "labels": {
+ "tranref": "REF#",
+ "tid": "TID",
+ "validationcode": "ValCode",
+ "emvapplicationid": "AID",
+ "emvatc": "ATC",
+ "rcpt_pay_mthd": "Pay Method",
+ "transid": "TransID",
+ "rcpt_input_mthd": "IMode",
+ "emvtsi": "TSI",
+ "emvac": "AC",
+ "rcpt_trans_type": "TranType",
+ "emvapplicationname": "PApp",
+ "visarewards": "RewardsProg"
+ }
+ },
+ "receipttoken": "H4sIAAAAAAAAACXMTQuCMBgA4P/ynh3tw_3dBI/ipQ8NOtRN53QiblpBRfTfCzo/8LwhxGAdZCCwFYoJJFQjI2kvHdGu74lVkgmrWyWNhASW5jW7cB87yHjKKePGJODnxrrnMl7dDTKmEJlSOqV/_N30XPpyj2Eddq57_KKZ8FLzmh_G1VQnVfhjiXGK1XYTc/h8AVOkf4qUAAAA",
+ "call": "card_payment",
+ "nonce": "488b5568-b5c1-4f38-8b2f-3b050f3abb11P",
+ "hmac": "JyPAJ9Yx0SlYBTtqns1OxAFRt+xF3l2UiLPO5zTDRBE=",
+ "paymentreferenceid": "C19607899P24201299",
+ "cardnum": "...1111",
+ "email": "",
+ "nameOnCard": "John Allen",
+ "cardType": "visa"
+}
diff --git a/client/src/components/_test/test.component.jsx b/client/src/components/_test/test.component.jsx
deleted file mode 100644
index b6b02b7c0..000000000
--- a/client/src/components/_test/test.component.jsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from "react";
-import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
-export default function Test() {
- return (
-
-
-
- );
-}
diff --git a/client/src/components/_test/test.page.jsx b/client/src/components/_test/test.page.jsx
new file mode 100644
index 000000000..13195631b
--- /dev/null
+++ b/client/src/components/_test/test.page.jsx
@@ -0,0 +1,31 @@
+import { Button } from "antd";
+import React from "react";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { setModalContext } from "../../redux/modals/modals.actions";
+
+const mapStateToProps = createStructuredSelector({});
+
+const mapDispatchToProps = (dispatch) => ({
+ setRefundPaymentContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "refund_payment" })),
+});
+
+function Test({ setRefundPaymentContext, refundPaymentModal }) {
+ console.log("refundPaymentModal", refundPaymentModal);
+ return (
+
+
+
+ );
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Test);
diff --git a/client/src/components/card-payment-modal/card-payment-modal.component..jsx b/client/src/components/card-payment-modal/card-payment-modal.component..jsx
new file mode 100644
index 000000000..3c3b397c1
--- /dev/null
+++ b/client/src/components/card-payment-modal/card-payment-modal.component..jsx
@@ -0,0 +1,258 @@
+import React, { useEffect } from "react";
+import axios from "axios";
+import { useTranslation } from "react-i18next";
+import { Button, Card, Form, Input, InputNumber, Row, Select } from "antd";
+import moment from "moment";
+import { useMutation, useQuery } from "@apollo/client";
+import LayoutFormRow from "../layout-form-row/layout-form-row.component";
+import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
+import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
+import {
+ INSERT_PAYMENT_RESPONSE,
+ QUERY_RO_AND_OWNER_BY_JOB_PK,
+} from "../../graphql/payment_response.queries";
+import DataLabel from "../data-label/data-label.component";
+import { insertAuditTrail } from "../../redux/application/application.actions";
+import AuditTrailMapping from "../../utils/AuditTrailMappings";
+import { connect } from "react-redux";
+import { toggleModalVisible } from "../../redux/modals/modals.actions";
+
+const mapDispatchToProps = (dispatch) => ({
+ insertAuditTrail: ({ jobid, operation }) =>
+ dispatch(insertAuditTrail({ jobid, operation })),
+ toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
+});
+
+const CardPaymentModalComponent = ({
+ bodyshop,
+ context,
+ toggleModalVisible,
+ insertAuditTrail,
+}) => {
+ const [form] = Form.useForm();
+ const amount = Form.useWatch("amount", form);
+ const payer = Form.useWatch("payer", form);
+ const jobid = Form.useWatch("jobid", form);
+ const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
+ const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
+ const { t } = useTranslation();
+
+ const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, {
+ variables: { jobid: context?.jobid ?? "" },
+ });
+
+ useEffect(() => {
+ axios.get("/intellipay/lightbox_credentials").then((response) => {
+ var rg = document.createRange();
+ let node = rg.createContextualFragment(response.data);
+
+ document.documentElement.appendChild(node);
+ window.intellipay.initialize();
+
+ window.intellipay.runOnClose(() => {
+ window.intellipay.initialize();
+ });
+
+ window.intellipay.runOnApproval(async function (response) {
+ form.setFieldValue("paymentResponse", response);
+ form.submit();
+
+ toggleModalVisible();
+ });
+
+ window.intellipay.runOnNonApproval(async function (response) {
+ // Mutate unsuccessful payment
+ await insertPaymentResponse({
+ variables: {
+ paymentResponse: {
+ amount: response.amount,
+ bodyshopid: bodyshop.id,
+ jobid: jobid || context.jobid,
+ declinereason: response.declinereason,
+ ext_paymentid: response.paymentid.toString(),
+ successful: false,
+ response,
+ },
+ },
+ });
+
+ // Insert failed payment to audit trail
+ insertAuditTrail({
+ jobid: jobid || context?.jobid,
+ operation: AuditTrailMapping.failedpayment(),
+ });
+ });
+ });
+
+ if (context?.jobid) {
+ form.setFieldValue("jobid", context.jobid);
+ }
+
+ form.setFieldValue("payer", t("payments.labels.customer"));
+
+ 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);
+ }, []);
+
+ const handleFinish = async (values) => {
+ const paymentResult = await insertPayment({
+ variables: {
+ paymentInput: {
+ amount: values.amount,
+ transactionid: values.paymentResponse.receiptelements.transid,
+ payer: values.payer,
+ type: values.paymentResponse.cardType,
+ jobid: values.jobid,
+ date: moment(Date.now()),
+ },
+ },
+ update(cache, { data }) {
+ cache.modify({
+ id: cache.identify({ id: jobid, __typename: "jobs" }),
+ fields: {
+ payments(payments) {
+ return [...data.insert_payments.returning, ...payments];
+ },
+ },
+ });
+ },
+ });
+
+ 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,
+ },
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {context?.balance && (
+
+ {context?.balance.toFormat()}
+
+ )}
+
+
+
+
+ );
+};
+
+export default connect(null, mapDispatchToProps)(CardPaymentModalComponent);
diff --git a/client/src/components/card-payment-modal/card-payment-modal.container..jsx b/client/src/components/card-payment-modal/card-payment-modal.container..jsx
new file mode 100644
index 000000000..1ce761776
--- /dev/null
+++ b/client/src/components/card-payment-modal/card-payment-modal.container..jsx
@@ -0,0 +1,57 @@
+import { Button, Modal } from "antd";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { connect } from "react-redux";
+import { createStructuredSelector } from "reselect";
+import { toggleModalVisible } from "../../redux/modals/modals.actions";
+import { selectCardPayment } from "../../redux/modals/modals.selectors";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import CardPaymentModalComponent from "./card-payment-modal.component.";
+
+const mapStateToProps = createStructuredSelector({
+ cardPaymentModal: selectCardPayment,
+ bodyshop: selectBodyshop,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
+});
+
+function CardPaymentModalContainer({
+ cardPaymentModal,
+ toggleModalVisible,
+ bodyshop,
+}) {
+ const { context, visible } = cardPaymentModal;
+ const { t } = useTranslation();
+
+ const handleCancel = () => {
+ toggleModalVisible();
+ };
+
+ const handleOK = () => {
+ toggleModalVisible();
+ };
+
+ return (
+
+ {t("job_payments.buttons.goback")}
+ ,
+ ]}
+ width="60%"
+ destroyOnClose
+ >
+
+
+ );
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(CardPaymentModalContainer);
diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx
index 1ed3f526d..7a0679daa 100644
--- a/client/src/components/header/header.component.jsx
+++ b/client/src/components/header/header.component.jsx
@@ -70,6 +70,8 @@ const mapDispatchToProps = (dispatch) => ({
setReportCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()),
+ setCardPaymentContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "cardPayment" })),
});
function Header({
@@ -83,6 +85,7 @@ function Header({
setPaymentContext,
setReportCenterContext,
recentItems,
+ setCardPaymentContext,
}) {
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
@@ -240,6 +243,19 @@ function Header({
>
{t("menus.header.enterpayment")}
+ {/* TODO: Enter Card Payment */}
+ {
+ setCardPaymentContext({
+ actions: {},
+ context: null,
+ });
+ }}
+ icon={}
+ >
+ {t("menus.header.entercardpayment")}
+
}>
diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx
index a67ed047b..196ef0baa 100644
--- a/client/src/components/job-payments/job-payments.component.jsx
+++ b/client/src/components/job-payments/job-payments.component.jsx
@@ -14,6 +14,13 @@ import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
+import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
+import {
+ setMessage,
+ openChatByPhone,
+} from "../../redux/messaging/messaging.actions";
+import { parsePhoneNumber } from "libphonenumber-js";
+import axios from "axios";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -23,13 +30,20 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })),
+ setCardPaymentContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "cardPayment" })),
+ openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
+ setMessage: (text) => dispatch(setMessage(text)),
});
export function JobPayments({
job,
jobRO,
bodyshop,
+ setMessage,
+ openChatByPhone,
setPaymentContext,
+ setCardPaymentContext,
refetch,
}) {
const { t } = useTranslation();
@@ -37,6 +51,8 @@ export function JobPayments({
sortedInfo: {},
filteredInfo: {},
});
+ const [generatingURL, setGeneratingtURL] = useState(false);
+
const columns = [
{
title: t("payments.fields.date"),
@@ -149,6 +165,36 @@ export function JobPayments({
title={t("payments.labels.title")}
extra={
+
+
(
+
+ ),
+ }}
summary={() => (
<>
diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
index 5002a0e5d..9ce78821e 100644
--- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
+++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx
@@ -38,6 +38,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
setTimeTicketContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
+ setCardPaymentContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "cardPayment" })),
});
export function JobsDetailHeaderActions({
@@ -51,6 +53,7 @@ export function JobsDetailHeaderActions({
setJobCostingContext,
jobRO,
setTimeTicketContext,
+ setCardPaymentContext,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -221,6 +224,18 @@ export function JobsDetailHeaderActions({
>
{t("menus.header.enterpayment")}
+ {
+ setCardPaymentContext({
+ actions: {},
+ context: { jobid: job.id },
+ });
+ }}
+ >
+ {t("menus.header.entercardpayment")}
+
{
+ notification[type]({
+ message: t("job_payments.notifications.error.title"),
+ description: t("job_payments.notifications.error.description"),
+ });
+};
+
+const PaymentExpandedRowComponent = ({ record }) => {
+ const [refundAmount, setRefundAmount] = useState(0);
+ const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
+ const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
+ const { t } = useTranslation();
+
+ const { loading, error, data } = useQuery(
+ QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
+ {
+ variables: {
+ paymentid: record.id,
+ fetchPolicy: "network-only",
+ nextFetchPolicy: "network-only",
+ },
+ }
+ );
+
+ const { data: refundable_amount, refetch } = useQuery(
+ GET_REFUNDABLE_AMOUNT_BY_JOBID,
+ {
+ variables: {
+ jobid: record.jobid,
+ },
+ }
+ );
+
+ const insertPayments = async (payment_response, refund_response) => {
+ await insertPayment({
+ variables: {
+ paymentInput: {
+ amount: -refund_response.data.amount,
+ transactionid: payment_response.response.receiptelements.transid,
+ payer: record.payer,
+ type: "Refund",
+ jobid: payment_response.jobid,
+ date: moment(Date.now()),
+ },
+ },
+ update(cache, { data }) {
+ cache.modify({
+ id: cache.identify({
+ id: payment_response.jobid,
+ __typename: "jobs",
+ }),
+ fields: {
+ payments(payments) {
+ return [...data.insert_payments.returning, ...payments];
+ },
+ },
+ });
+ },
+ });
+
+ await insertPaymentResponse({
+ variables: {
+ paymentResponse: {
+ amount: -refund_response.data.amount,
+ bodyshopid: payment_response.bodyshopid,
+ paymentid: payment_response.paymentid,
+ jobid: payment_response.jobid,
+ declinereason: "Refund",
+ ext_paymentid: payment_response.ext_paymentid,
+ successful: true,
+ response: refund_response.data,
+ },
+ },
+ });
+ };
+
+ const showConfirm = (payment_response) => {
+ confirm({
+ title: "Do you want to refund payment?",
+ content:
+ "The payment will be refunded. Click OK to confirm and Cancel to dismiss.",
+ async onOk() {
+ const refundResponse = await axios.post("/intellipay/payment_refund", {
+ amount: refundAmount,
+ paymentid: payment_response.ext_paymentid,
+ });
+
+ if (refundResponse.data.status < 0) {
+ openNotificationWithIcon("error", t);
+ return;
+ }
+
+ insertPayments(payment_response, refundResponse);
+
+ // refetch refundable amount
+ refetch();
+ },
+ onCancel() {},
+ });
+ };
+
+ if (loading) return null;
+
+ if (error) return Error loading data. Please Reload
;
+
+ const payment_response = data.payment_response[0];
+ const max_refundable_amount =
+ refundable_amount?.payment_response_aggregate.aggregate.sum.amount;
+
+ return (
+
+
+
+ {record.payer}
+
+
+ {payment_response?.response?.nameOnCard ?? ""}
+
+
+ {record.amount}
+
+
+ {moment(record.created_at).format("YYYY-MM-DD HH:mm:ss")}
+
+
+ {record.transactionid}
+
+
+ {payment_response?.response?.paymentreferenceid ?? ""}
+
+
+ {record.type}
+
+ {payment_response && (
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default PaymentExpandedRowComponent;
diff --git a/client/src/components/payment-modal/payment-modal.container.jsx b/client/src/components/payment-modal/payment-modal.container.jsx
index 4b270bca5..9f31807e9 100644
--- a/client/src/components/payment-modal/payment-modal.container.jsx
+++ b/client/src/components/payment-modal/payment-modal.container.jsx
@@ -7,14 +7,14 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
INSERT_NEW_PAYMENT,
- UPDATE_PAYMENT
+ UPDATE_PAYMENT,
} from "../../graphql/payments.queries";
import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
- selectCurrentUser
+ selectCurrentUser,
} from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
diff --git a/client/src/graphql/payment_response.queries.js b/client/src/graphql/payment_response.queries.js
new file mode 100644
index 000000000..8722e6cd8
--- /dev/null
+++ b/client/src/graphql/payment_response.queries.js
@@ -0,0 +1,55 @@
+import { gql } from "@apollo/client";
+
+export const INSERT_PAYMENT_RESPONSE = gql`
+ mutation INSERT_PAYMENT_RESPONSE(
+ $paymentResponse: [payment_response_insert_input!]!
+ ) {
+ insert_payment_response(objects: $paymentResponse) {
+ returning {
+ id
+ }
+ }
+ }
+`;
+
+export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql`
+ query QUERY_PAYMENT_RESPONSE_BY_PK($paymentid: uuid!) {
+ payment_response(where: { paymentid: { _eq: $paymentid } }) {
+ id
+ jobid
+ bodyshopid
+ paymentid
+ amount
+ declinereason
+ ext_paymentid
+ successful
+ response
+ }
+ }
+`;
+
+export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql`
+ query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) {
+ jobs_by_pk(id: $jobid) {
+ ro_number
+ owner {
+ ownr_fn
+ ownr_ln
+ ownr_ea
+ ownr_zip
+ }
+ }
+ }
+`;
+
+export const GET_REFUNDABLE_AMOUNT_BY_JOBID = gql`
+ query GET_REFUNDABLE_AMOUNT_BY_JOBID($jobid: uuid!) {
+ payment_response_aggregate(where: { jobid: { _eq: $jobid } }) {
+ aggregate {
+ sum {
+ amount
+ }
+ }
+ }
+ }
+`;
diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx
index d4c239eb9..edf77292d 100644
--- a/client/src/pages/manage/manage.page.component.jsx
+++ b/client/src/pages/manage/manage.page.component.jsx
@@ -16,7 +16,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
-import TestComponent from "../../components/_test/test.component";
+import TestComponent from "../../components/_test/test.page";
import { requestForToken } from "../../firebase/firebase.utils";
import {
selectBodyshop,
@@ -31,6 +31,10 @@ const ManageRootPage = lazy(() =>
);
const JobsPage = lazy(() => import("../jobs/jobs.page"));
+const CardPaymentModalContainer = lazy(() =>
+ import("../../components/card-payment-modal/card-payment-modal.container.")
+);
+
const JobsDetailPage = lazy(() =>
import("../jobs-detail/jobs-detail.page.container")
);
@@ -195,6 +199,8 @@ export function Manage({ match, conflict, bodyshop }) {
>
+
+
diff --git a/client/src/redux/modals/modals.reducer.js b/client/src/redux/modals/modals.reducer.js
index 72859b08f..3209ae53d 100644
--- a/client/src/redux/modals/modals.reducer.js
+++ b/client/src/redux/modals/modals.reducer.js
@@ -25,6 +25,7 @@ const INITIAL_STATE = {
contractFinder: { ...baseModal },
inventoryUpsert: { ...baseModal },
ca_bc_eftTableConvert: { ...baseModal },
+ cardPayment: { ...baseModal },
};
const modalsReducer = (state = INITIAL_STATE, action) => {
diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js
index 82d4f706d..8a4cfee77 100644
--- a/client/src/redux/modals/modals.selectors.js
+++ b/client/src/redux/modals/modals.selectors.js
@@ -79,3 +79,8 @@ export const selectCaBcEtfTableConvert = createSelector(
[selectModals],
(modals) => modals.ca_bc_eftTableConvert
);
+
+export const selectCardPayment = createSelector(
+ [selectModals],
+ (modals) => modals.cardPayment
+);
diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js
index e3019c731..3a117518d 100644
--- a/client/src/utils/AuditTrailMappings.js
+++ b/client/src/utils/AuditTrailMappings.js
@@ -40,7 +40,7 @@ const AuditTrailMapping = {
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
admin_jobmarkexported: () =>
i18n.t("audit_trail.messages.admin_jobmarkexported"),
-
+ failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
};
export default AuditTrailMapping;
diff --git a/server.js b/server.js
index d0b1bde24..06344cbfc 100644
--- a/server.js
+++ b/server.js
@@ -230,6 +230,31 @@ app.post(
mixdataUpload.mixdataUpload
);
+var intellipay = require("./server/intellipay/intellipay");
+app.get(
+ "/intellipay/lightbox_credentials",
+ fb.validateFirebaseIdToken,
+ intellipay.lightbox_credentials
+);
+
+app.post(
+ "/intellipay/payment_refund",
+ fb.validateFirebaseIdToken,
+ intellipay.payment_refund
+);
+
+app.post(
+ "/intellipay/generate_payment_url",
+ fb.validateFirebaseIdToken,
+ intellipay.generate_payment_url
+);
+
+app.post(
+ "/intellipay/postback",
+ // fb.validateFirebaseIdToken,
+ intellipay.postback
+);
+
var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => {
diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js
new file mode 100644
index 000000000..97d9771af
--- /dev/null
+++ b/server/intellipay/intellipay.js
@@ -0,0 +1,112 @@
+const GraphQLClient = require("graphql-request").GraphQLClient;
+const path = require("path");
+const queries = require("../graphql-client/queries");
+const Dinero = require("dinero.js");
+const qs = require("query-string");
+const axios = require("axios");
+require("dotenv").config({
+ path: path.resolve(
+ process.cwd(),
+ `.env.${process.env.NODE_ENV || "development"}`
+ ),
+});
+
+const domain = process.env.NODE_ENV ? "secure" : "test";
+
+const getShopCredentials = () => {
+ // add parametes for the request
+ // TODO: Implement retrieval logic later.
+
+ return {
+ merchantkey: "3B8068", //This should be dynamic
+ apikey: "Oepn2B.XqRgzAqHqvOOmYUxD2VW.vGSipi", //This should be dynamic
+ };
+};
+
+exports.lightbox_credentials = async (req, res) => {
+ //req.user contains firebase decoded credentials
+ // can add bodyshopid to req.body
+ //Server side query to get API credentials for that shop and generatae link
+
+ const shopCredentials = getShopCredentials();
+
+ try {
+ const options = {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" },
+ //TODO: Move these to environment variables/database.
+ data: qs.stringify({
+ ...shopCredentials,
+ operatingenv:
+ process.env.NODE_ENV === undefined
+ ? process.env.NODE_ENV
+ : "businessattended",
+ }),
+ url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal`,
+ };
+
+ const response = await axios(options);
+
+ res.send(response.data);
+ } catch (error) {
+ console.log(error);
+ res.json({ error });
+ }
+};
+
+exports.payment_refund = async (req, res) => {
+ const shopCredentials = getShopCredentials();
+
+ try {
+ const options = {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" },
+
+ data: qs.stringify({
+ method: "payment_refund",
+ ...shopCredentials,
+ paymentid: req.body.paymentid,
+ amount: req.body.amount,
+ }),
+ url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`,
+ };
+
+ const response = await axios(options);
+
+ res.send(response.data);
+ } catch (error) {
+ console.log(error);
+ res.json({ error });
+ }
+};
+
+exports.generate_payment_url = async (req, res) => {
+ const shopCredentials = getShopCredentials();
+
+ try {
+ const options = {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" },
+ //TODO: Move these to environment variables/database.
+ data: qs.stringify({
+ ...shopCredentials,
+ ...req.body,
+ createshorturl: true,
+ }),
+ url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`,
+ };
+
+ const response = await axios(options);
+
+ res.send(response.data);
+ } catch (error) {
+ console.log(error);
+ res.json({ error });
+ }
+};
+
+exports.postback = async (req, res) => {
+ console.log("postback as", req.body);
+
+ res.send({ message: "postback" });
+};