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 ( + +
+ + + { + refetch({ jobid: e }); + }} + /> + + + + {/* Lighbox Input amount needs to be hidden */} + + + + {/* Lightbox payment response when it is completed */} + + + + + + + + + + + + + + {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" }); +};