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/test.page.jsx b/client/src/components/_test/test.page.jsx
index 524a41a45..13195631b 100644
--- a/client/src/components/_test/test.page.jsx
+++ b/client/src/components/_test/test.page.jsx
@@ -3,29 +3,27 @@ import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
-import CardPaymentModalContainer from "../card-payment-modal/card-payment-modal.container.";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
- setCardPaymentContext: (context) =>
- dispatch(setModalContext({ context: context, modal: "cardPayment" })),
+ setRefundPaymentContext: (context) =>
+ dispatch(setModalContext({ context: context, modal: "refund_payment" })),
});
-function Test({ setCardPaymentContext }) {
+function Test({ setRefundPaymentContext, refundPaymentModal }) {
+ console.log("refundPaymentModal", refundPaymentModal);
return (
-
- {/* */}
);
}
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
index a06692dbb..7ba4ed2ac 100644
--- a/client/src/components/card-payment-modal/card-payment-modal.container..jsx
+++ b/client/src/components/card-payment-modal/card-payment-modal.container..jsx
@@ -21,7 +21,7 @@ function CardPaymentModalContainer({
toggleModalVisible,
bodyshop,
}) {
- const { context, visible, actions } = cardPaymentModal;
+ const { context, visible } = cardPaymentModal;
const handleCancel = () => {
toggleModalVisible();
diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx
index 2b470a36c..196ef0baa 100644
--- a/client/src/components/job-payments/job-payments.component.jsx
+++ b/client/src/components/job-payments/job-payments.component.jsx
@@ -15,6 +15,12 @@ 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,
@@ -26,12 +32,16 @@ const mapDispatchToProps = (dispatch) => ({
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,
@@ -41,6 +51,8 @@ export function JobPayments({
sortedInfo: {},
filteredInfo: {},
});
+ const [generatingURL, setGeneratingtURL] = useState(false);
+
const columns = [
{
title: t("payments.fields.date"),
@@ -153,6 +165,36 @@ export function JobPayments({
title={t("payments.labels.title")}
extra={
+
{t("menus.header.enterpayment")}
- {/* TODO: Add Card payment */}
{
+ const [refundAmount, setRefundAmount] = useState(0);
+ const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
+ const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
+
const { loading, error, data } = useQuery(
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
{
@@ -16,12 +28,79 @@ const PaymentExpandedRowComponent = ({ record }) => {
}
);
+ const { data: refundable_amount } = 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,
+ });
+
+ insertPayments(payment_response, refundResponse);
+ },
+ onCancel() {},
+ });
+ };
+
if (loading) return null;
if (error) return Error loading data. Please Reload
;
const payment_response = data.payment_response[0];
- console.log("Record", record);
+ const max_refundable_amount =
+ refundable_amount.payment_response_aggregate.aggregate.sum.amount;
return (
@@ -32,7 +111,7 @@ const PaymentExpandedRowComponent = ({ record }) => {
>
{record.payer}
- {payment_response.response.nameOnCard}
+ {payment_response?.response?.nameOnCard ?? ""}
{record.amount}
@@ -42,11 +121,24 @@ const PaymentExpandedRowComponent = ({ record }) => {
{record.transactionid}
- {payment_response.response.paymentreferenceid}
+ {payment_response?.response?.paymentreferenceid ?? ""}
{record.type}
+ {payment_response && (
+
+
+
+
+
+ )}
);
diff --git a/client/src/graphql/payment_response.queries.js b/client/src/graphql/payment_response.queries.js
index 5a7934824..8722e6cd8 100644
--- a/client/src/graphql/payment_response.queries.js
+++ b/client/src/graphql/payment_response.queries.js
@@ -16,6 +16,9 @@ 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
@@ -26,7 +29,7 @@ export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql`
`;
export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql`
- query GET_JOB_OWNER($jobid: uuid!) {
+ query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) {
jobs_by_pk(id: $jobid) {
ro_number
owner {
@@ -38,3 +41,15 @@ export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql`
}
}
`;
+
+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/translations/en_us/common.json b/client/src/translations/en_us/common.json
index c5eba4867..691918729 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -60,6 +60,7 @@
"nodateselected": "No date has been selected.",
"priorappointments": "Previous Appointments",
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
+ "smspaymentreminder": "This is {{shopname}} reminding you about your remaining balance of {{amount}}. To pay for the said balance click the link {{payment_link}}. Thank you very much.",
"scheduledfor": "Scheduled appointment for: ",
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"smartscheduling": "Smart Scheduling",
@@ -1840,6 +1841,7 @@
"dashboard": "Dashboard",
"enterbills": "Enter Bills",
"enterpayment": "Enter Payments",
+ "paymentremindersms": "Send Payment Reminder via SMS",
"entercardpayment": "Enter Card Payments",
"entertimeticket": "Enter Time Tickets",
"export": "Export",
diff --git a/server.js b/server.js
index 12a90f3c8..531c7813f 100644
--- a/server.js
+++ b/server.js
@@ -232,6 +232,18 @@ app.get(
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
+);
+
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
index 6196dc729..c9646088b 100644
--- a/server/intellipay/intellipay.js
+++ b/server/intellipay/intellipay.js
@@ -11,9 +11,7 @@ require("dotenv").config({
),
});
-const url = process.env.NODE_ENV
- ? "https://secure.cpteller.com/api/custapi.cfc?method=autoterminal"
- : "https://test.cpteller.com/api/custapi.cfc?method=autoterminal";
+const domain = process.env.NODE_ENV ? "secure" : "test";
const getShopCredentials = () => {
// add parametes for the request
@@ -44,7 +42,58 @@ exports.lightbox_credentials = async (req, res) => {
? process.env.NODE_ENV
: "businessattended",
}),
- url,
+ 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);