initial intellipay implementation
Co-authored-by: Patrick Fic <patrick@thinkimex.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import { Modal } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
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 IntellipayTestPage from "./intellipay-test";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
cardPaymentModal: selectCardPayment,
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||||
|
});
|
||||||
|
|
||||||
|
function CardPaymentModalComponent({
|
||||||
|
cardPaymentModal,
|
||||||
|
toggleModalVisible,
|
||||||
|
bodyshop,
|
||||||
|
}) {
|
||||||
|
const { context, visible } = cardPaymentModal;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
onOk={() => toggleModalVisible()}
|
||||||
|
onCancel={() => toggleModalVisible()}
|
||||||
|
cancelButtonProps={{ style: { display: "none" } }}
|
||||||
|
width="90%"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<IntellipayTestPage bodyshop={bodyshop} context={context} />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(CardPaymentModalComponent);
|
||||||
161
client/src/components/_test/intellipay-test.jsx
Normal file
161
client/src/components/_test/intellipay-test.jsx
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button, Card, Form, Input, InputNumber, Select } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useMutation } 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 } from "./payment_response.queries";
|
||||||
|
|
||||||
|
const IntellpayTestPage = ({ bodyshop, context }) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const amount = Form.useWatch("amount", form);
|
||||||
|
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||||
|
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
console.log("Modal: Intellipay Initialized");
|
||||||
|
|
||||||
|
window.intellipay.runOnClose(() => {
|
||||||
|
// Apparently it automatically removes itself in the document when closed
|
||||||
|
// so we just need to reintialize it
|
||||||
|
window.intellipay.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.intellipay.runOnApproval(async function (response) {
|
||||||
|
console.log("Modal - Payment Response: ", response);
|
||||||
|
// TODO store the response
|
||||||
|
form.setFieldValue("paymentResponse", response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const disabled = false;
|
||||||
|
|
||||||
|
const handleFinish = async (values) => {
|
||||||
|
console.log("Modal: Handle Finish Values", 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()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentResponseResult = 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Modal - Insert Payment Result: ", paymentResult);
|
||||||
|
console.log(
|
||||||
|
"Modal - Insert Respose Payment Result: ",
|
||||||
|
paymentResponseResult
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card title="Card Payment">
|
||||||
|
<Form onFinish={handleFinish} form={form}>
|
||||||
|
<LayoutFormRow grow>
|
||||||
|
<Form.Item
|
||||||
|
name="jobid"
|
||||||
|
label={t("bills.fields.ro_number")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<JobSearchSelectComponent
|
||||||
|
disabled={disabled}
|
||||||
|
notExported={false}
|
||||||
|
clm_no
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
{/* Lighbox Input amount needs to be hidden */}
|
||||||
|
<Input
|
||||||
|
className="ipayfield"
|
||||||
|
data-ipayname="amount"
|
||||||
|
type="hidden"
|
||||||
|
value={amount}
|
||||||
|
hidden
|
||||||
|
/>
|
||||||
|
<Form.Item name="paymentResponse" hidden>
|
||||||
|
<Input type="hidden" value={amount} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<LayoutFormRow grow>
|
||||||
|
<Form.Item
|
||||||
|
label={t("payments.fields.payer")}
|
||||||
|
name="payer"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select disabled={disabled}>
|
||||||
|
<Select.Option value={t("payments.labels.customer")}>
|
||||||
|
{t("payments.labels.customer")}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option value={t("payments.labels.insurance")}>
|
||||||
|
{t("payments.labels.insurance")}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Amount"
|
||||||
|
name="amount"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
// message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
<Button type="primary" data-ipayname="submit" className="ipayfield">
|
||||||
|
Proceed to Payment
|
||||||
|
</Button>
|
||||||
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
<Button htmlType="submit">Submit Payment</Button>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IntellpayTestPage;
|
||||||
63
client/src/components/_test/payment_response.json
Normal file
63
client/src/components/_test/payment_response.json
Normal file
@@ -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": "<b>No Refunds</b>\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"
|
||||||
|
}
|
||||||
29
client/src/components/_test/payment_response.queries.js
Normal file
29
client/src/components/_test/payment_response.queries.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
|
export const QUERY_ALL_PAYMENT_RESPONSE = gql`
|
||||||
|
query QUERY_ALL_PAYMENT_RESPONSE {
|
||||||
|
payment_response {
|
||||||
|
amount
|
||||||
|
bodyshopid
|
||||||
|
declinereason
|
||||||
|
ext_paymentid
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
paymentid
|
||||||
|
response
|
||||||
|
successful
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const INSERT_PAYMENT_RESPONSE = gql`
|
||||||
|
mutation INSERT_PAYMENT_RESPONSE(
|
||||||
|
$paymentResponse: [payment_response_insert_input!]!
|
||||||
|
) {
|
||||||
|
insert_payment_response(objects: $paymentResponse) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
|
||||||
export default function Test() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<QboAuthorizeComponent />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
35
client/src/components/_test/test.page.jsx
Normal file
35
client/src/components/_test/test.page.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { Button } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import CardPaymentModalComponent from "./card-payment-modal.component.";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setCardPaymentContext: (context) =>
|
||||||
|
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||||
|
});
|
||||||
|
|
||||||
|
function Test({ setCardPaymentContext }) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CardPaymentModalComponent />
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
setCardPaymentContext({
|
||||||
|
context: {
|
||||||
|
test: "Test String",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Open Modal
|
||||||
|
</Button>
|
||||||
|
{/* <IntellipayTestPage /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Test);
|
||||||
@@ -7,14 +7,14 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import {
|
||||||
INSERT_NEW_PAYMENT,
|
INSERT_NEW_PAYMENT,
|
||||||
UPDATE_PAYMENT
|
UPDATE_PAYMENT,
|
||||||
} from "../../graphql/payments.queries";
|
} from "../../graphql/payments.queries";
|
||||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectPayment } from "../../redux/modals/modals.selectors";
|
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
|||||||
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
||||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
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 { requestForToken } from "../../firebase/firebase.utils";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
@@ -193,9 +193,8 @@ export function Manage({ match, conflict, bodyshop }) {
|
|||||||
<Suspense
|
<Suspense
|
||||||
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
|
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
|
||||||
>
|
>
|
||||||
|
<PaymentModalContainer />
|
||||||
<PaymentModalContainer />
|
|
||||||
|
|
||||||
<BreadCrumbs />
|
<BreadCrumbs />
|
||||||
<BillEnterModalContainer />
|
<BillEnterModalContainer />
|
||||||
<JobCostingModal />
|
<JobCostingModal />
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const INITIAL_STATE = {
|
|||||||
contractFinder: { ...baseModal },
|
contractFinder: { ...baseModal },
|
||||||
inventoryUpsert: { ...baseModal },
|
inventoryUpsert: { ...baseModal },
|
||||||
ca_bc_eftTableConvert: { ...baseModal },
|
ca_bc_eftTableConvert: { ...baseModal },
|
||||||
|
cardPayment: { ...baseModal },
|
||||||
};
|
};
|
||||||
|
|
||||||
const modalsReducer = (state = INITIAL_STATE, action) => {
|
const modalsReducer = (state = INITIAL_STATE, action) => {
|
||||||
|
|||||||
@@ -79,3 +79,8 @@ export const selectCaBcEtfTableConvert = createSelector(
|
|||||||
[selectModals],
|
[selectModals],
|
||||||
(modals) => modals.ca_bc_eftTableConvert
|
(modals) => modals.ca_bc_eftTableConvert
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectCardPayment = createSelector(
|
||||||
|
[selectModals],
|
||||||
|
(modals) => modals.cardPayment
|
||||||
|
);
|
||||||
|
|||||||
@@ -225,6 +225,13 @@ app.post(
|
|||||||
mixdataUpload.mixdataUpload
|
mixdataUpload.mixdataUpload
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var intellipay = require("./server/intellipay/intellipay");
|
||||||
|
app.get(
|
||||||
|
"/intellipay/lightbox_credentials",
|
||||||
|
fb.validateFirebaseIdToken,
|
||||||
|
intellipay.lightbox_credentials
|
||||||
|
);
|
||||||
|
|
||||||
var ioevent = require("./server/ioevent/ioevent");
|
var ioevent = require("./server/ioevent/ioevent");
|
||||||
app.post("/ioevent", ioevent.default);
|
app.post("/ioevent", ioevent.default);
|
||||||
app.post("/newlog", (req, res) => {
|
app.post("/newlog", (req, res) => {
|
||||||
|
|||||||
36
server/intellipay/intellipay.js
Normal file
36
server/intellipay/intellipay.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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"}`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.lightbox_credentials = async (req, res) => {
|
||||||
|
console.log("In API Lightbox Credential route.");
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
||||||
|
//TODO: Move these to environment variables/database.
|
||||||
|
data: qs.stringify({
|
||||||
|
merchantkey: "3B8068",
|
||||||
|
apikey: "Oepn2B.XqRgzAqHqvOOmYUxD2VW.vGSipi",
|
||||||
|
operatingenv: "businessattended", // add these for EMV Swipe
|
||||||
|
}),
|
||||||
|
url: "https://test.cpteller.com/api/custapi.cfc?method=autoterminal", //added .test
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axios(options);
|
||||||
|
|
||||||
|
res.send(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.json({ error });
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user