diff --git a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx index 301fec91f..eef6dfaf5 100644 --- a/client/src/components/job-totals-table/job-totals.table.totals.component.jsx +++ b/client/src/components/job-totals-table/job-totals.table.totals.component.jsx @@ -7,6 +7,7 @@ import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; +import JobTotalsCashDiscount from "./jobs-totals.cash-discount-display.component"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser @@ -169,13 +170,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { }, { key: t("jobs.labels.total_cust_payable"), - total: Dinero(job.job_totals.totals.custPayable.total) - .add( - Dinero(job.job_totals.totals.custPayable.total).percentage( - bodyshop.intellipay_config?.cash_discount_percentage || 0 - ) - ) - .toJSON(), + render: , bold: true } ] @@ -211,7 +206,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) { dataIndex: "total", key: "total", align: "right", - render: (text, record) => Dinero(record.total).toFormat(), + render: (text, record) => (record.render ? record.render : Dinero(record.total).toFormat()), width: "20%", onCell: (record, rowIndex) => { return { style: { fontWeight: record.bold && "bold" } }; diff --git a/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx b/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx new file mode 100644 index 000000000..9a529866f --- /dev/null +++ b/client/src/components/job-totals-table/jobs-totals.cash-discount-display.component.jsx @@ -0,0 +1,57 @@ +import { notification, Spin } from "antd"; +import axios from "axios"; +import Dinero from "dinero.js"; +import React, { useCallback, useEffect, useState } from "react"; + +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +const mapDispatchToProps = (dispatch) => ({ +}); +export default connect(mapStateToProps, mapDispatchToProps)(JobTotalsCashDiscount); + +export function JobTotalsCashDiscount({ bodyshop, amountDinero }) { + const [loading, setLoading] = useState(true); + const [fee, setFee] = useState(0); + + const fetchData = useCallback(async () => { + if (amountDinero && bodyshop) { + setLoading(true); + let response; + try { + response = await axios.post("/intellipay/checkfee", { + bodyshop: { id: bodyshop.id, imexshopid: bodyshop.imexshopid }, + amount: Dinero(amountDinero).toFormat("0.00") + }); + + if (response?.data?.error) { + notification.open({ + type: "error", + message: response.data?.error || "Error encountered contacting IntelliPay service." + }); + } else { + setFee(response.data?.fee || 0); + } + } catch (error) { + notification.open({ + type: "error", + message: error.response?.data?.error || "Error encountered contacting IntelliPay service." + }); + } finally { + setLoading(false); + } + } + }, [amountDinero, bodyshop]); + + useEffect(() => { + fetchData(); + }, [fetchData, bodyshop, amountDinero]); + + if (loading) return ; + return Dinero(amountDinero) + .add(Dinero({ amount: Math.round(fee * 100) })) + .toFormat(); +} diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js index 080deec90..43758f97e 100644 --- a/server/intellipay/intellipay.js +++ b/server/intellipay/intellipay.js @@ -149,6 +149,52 @@ exports.generate_payment_url = async (req, res) => { } }; +//Reference: https://intellipay.com/dist/webapi26.html#operation/fee +exports.checkfee = async (req, res) => { + logger.log("intellipay-fee-check", "DEBUG", req.user?.email, null, null); + + //If there's no amount, there can't be a fee. Skip the call. + if (!req.body.amount || req.body.amount <= 0) { + res.json({ fee: 0 }); + return; + } + + const shopCredentials = await getShopCredentials(req.body.bodyshop); + + try { + const options = { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + //TODO: Move these to environment variables/database. + data: qs.stringify( + { + method: "fee", + ...shopCredentials, + amount: req.body.amount, + paymenttype: `"CC"` + }, + { sort: false } //ColdFusion Query Strings depend on order. This preserves it. + ), + url: `https://${domain}.cpteller.com/api/26/webapi.cfc` + }; + + const response = await axios(options); + if (response.data?.error) { + res.status(400).json({ error: response.data.error }); + } else if (response.data < 0) { + res.json({ error: "Fee amount negative. Check API credentials & account configuration." }); + } else { + res.json({ fee: response.data }); + } + } catch (error) { + //console.log(error); + logger.log("intellipay-fee-check-error", "ERROR", req.user?.email, null, { + error: error.message + }); + res.status(400).json({ error }); + } +}; + exports.postback = async (req, res) => { try { logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body); diff --git a/server/routes/intellipayRoutes.js b/server/routes/intellipayRoutes.js index 47f09ffb0..ad0b87323 100644 --- a/server/routes/intellipayRoutes.js +++ b/server/routes/intellipayRoutes.js @@ -1,11 +1,12 @@ const express = require("express"); const router = express.Router(); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); -const { lightbox_credentials, payment_refund, generate_payment_url, postback } = require("../intellipay/intellipay"); +const { lightbox_credentials, payment_refund, generate_payment_url, postback, checkfee } = require("../intellipay/intellipay"); router.post("/lightbox_credentials", validateFirebaseIdTokenMiddleware, lightbox_credentials); router.post("/payment_refund", validateFirebaseIdTokenMiddleware, payment_refund); router.post("/generate_payment_url", validateFirebaseIdTokenMiddleware, generate_payment_url); +router.post("/checkfee", validateFirebaseIdTokenMiddleware, checkfee); router.post("/postback", postback); module.exports = router;