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 index 3c3b397c1..8e19c2a61 100644 --- a/client/src/components/card-payment-modal/card-payment-modal.component..jsx +++ b/client/src/components/card-payment-modal/card-payment-modal.component..jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useCallback, useEffect } from "react"; import axios from "axios"; import { useTranslation } from "react-i18next"; import { Button, Card, Form, Input, InputNumber, Row, Select } from "antd"; @@ -41,13 +41,35 @@ const CardPaymentModalComponent = ({ variables: { jobid: context?.jobid ?? "" }, }); - useEffect(() => { - axios.get("/intellipay/lightbox_credentials").then((response) => { - var rg = document.createRange(); - let node = rg.createContextualFragment(response.data); + const nonApproval = useCallback( + async (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, + }, + }, + }); - document.documentElement.appendChild(node); - window.intellipay.initialize(); + // Insert failed payment to audit trail + insertAuditTrail({ + jobid: jobid || context?.jobid, + operation: AuditTrailMapping.failedpayment(), + }); + }, + [bodyshop, context, insertAuditTrail, insertPaymentResponse, jobid] + ); + + const initIntellipayFunctions = useCallback(() => { + if (window.intellipay !== undefined && typeof jobid !== "undefined") { + console.log("intellipay init functions"); window.intellipay.runOnClose(() => { window.intellipay.initialize(); @@ -60,35 +82,32 @@ const CardPaymentModalComponent = ({ 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(), - }); - }); - }); + window.intellipay.runOnNonApproval(nonApproval); + } + }, [form, jobid, nonApproval, toggleModalVisible]); + const initJobId = useCallback(() => { if (context?.jobid) { form.setFieldValue("jobid", context.jobid); } form.setFieldValue("payer", t("payments.labels.customer")); + }, [context?.jobid, form, t]); + + useEffect(() => { + initJobId(); + + axios + .post("/intellipay/lightbox_credentials", { bodyshop }) + .then((response) => { + var rg = document.createRange(); + let node = rg.createContextualFragment(response.data); + + document.documentElement.appendChild(node); + window.intellipay.initialize(); + + initIntellipayFunctions(); + }); function handleEvents(...props) { const operation = props[0].data.operation; @@ -101,7 +120,7 @@ const CardPaymentModalComponent = ({ window.addEventListener("message", handleEvents, false); return () => window.removeEventListener("message", handleEvents, false); - }, []); + }, [bodyshop, initJobId, initIntellipayFunctions]); const handleFinish = async (values) => { const paymentResult = await insertPayment({ diff --git a/client/src/components/job-payments/job-payments.component.jsx b/client/src/components/job-payments/job-payments.component.jsx index 196ef0baa..ad56ebc61 100644 --- a/client/src/components/job-payments/job-payments.component.jsx +++ b/client/src/components/job-payments/job-payments.component.jsx @@ -174,12 +174,15 @@ export function JobPayments({ const response = await axios.post( "/intellipay/generate_payment_url", { + bodyshop, amount: balance.getAmount(), account: job.ro_number, } ); setGeneratingtURL(false); + console.log("SMS", response); + openChatByPhone({ phone_num: p.formatInternational(), jobid: job.id, @@ -236,7 +239,7 @@ export function JobPayments({ }} expandable={{ expandedRowRender: (record) => ( - + ), }} summary={() => ( diff --git a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx index da3e79e20..6266457a7 100644 --- a/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx +++ b/client/src/components/payment-expanded-row/payment-expanded-row.component.jsx @@ -20,7 +20,7 @@ const openNotificationWithIcon = (type, t) => { }); }; -const PaymentExpandedRowComponent = ({ record }) => { +const PaymentExpandedRowComponent = ({ record, bodyshop }) => { const [refundAmount, setRefundAmount] = useState(0); const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); @@ -96,6 +96,7 @@ const PaymentExpandedRowComponent = ({ record }) => { "The payment will be refunded. Click OK to confirm and Cancel to dismiss.", async onOk() { const refundResponse = await axios.post("/intellipay/payment_refund", { + bodyshop, amount: refundAmount, paymentid: payment_response.ext_paymentid, }); diff --git a/server.js b/server.js index 78c140670..5067be835 100644 --- a/server.js +++ b/server.js @@ -233,7 +233,7 @@ app.post( ); var intellipay = require("./server/intellipay/intellipay"); -app.get( +app.post( "/intellipay/lightbox_credentials", fb.validateFirebaseIdToken, intellipay.lightbox_credentials diff --git a/server/graphql-client/queries.js b/server/graphql-client/queries.js index 650d6d76a..63a0b8f91 100644 --- a/server/graphql-client/queries.js +++ b/server/graphql-client/queries.js @@ -12,6 +12,39 @@ query FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID( } `; +exports.GET_JOB_BY_RO_NUMBER = ` + query GET_JOB_BY_RO_NUMBER($ro_number: String!) { + jobs(where:{ro_number:{_eq:$ro_number}}) { + id + bodyshop { + id + } + } + } +`; + +exports.INSERT_NEW_PAYMENT = ` + mutation INSERT_NEW_PAYMENT($paymentInput: [payments_insert_input!]!) { + insert_payments(objects: $paymentInput) { + returning { + id + } + } + } +`; + +exports.INSERT_PAYMENT_RESPONSE = ` + mutation INSERT_PAYMENT_RESPONSE( + $paymentResponse: [payment_response_insert_input!]! + ) { + insert_payment_response(objects: $paymentResponse) { + returning { + id + } + } + } +`; + exports.UNARCHIVE_CONVERSATION = ` mutation UNARCHIVE_CONVERSATION($id: uuid!) { update_conversations_by_pk(pk_columns: {id: $id}, _set: {archived: false}) { diff --git a/server/intellipay/aws-secrets-manager.js b/server/intellipay/aws-secrets-manager.js new file mode 100644 index 000000000..598fc9090 --- /dev/null +++ b/server/intellipay/aws-secrets-manager.js @@ -0,0 +1,46 @@ +"use strict"; + +const AWS = require("aws-sdk"); + +class SecretsManager { + /** + * Uses AWS Secrets Manager to retrieve a secret + */ + static async getSecret(secretName, region) { + const config = { region: region }; + let secretsManager = new AWS.SecretsManager(config); + try { + let secretValue = await secretsManager + .getSecretValue({ SecretId: secretName }) + .promise(); + if ("SecretString" in secretValue) { + return secretValue.SecretString; + } else { + let buff = new Buffer(secretValue.SecretBinary, "base64"); + return buff.toString("ascii"); + } + } catch (err) { + if (err.code === "DecryptionFailureException") + // Secrets Manager can't decrypt the protected secret text using the provided KMS key. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InternalServiceErrorException") + // An error occurred on the server side. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InvalidParameterException") + // You provided an invalid value for a parameter. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "InvalidRequestException") + // You provided a parameter value that is not valid for the current state of the resource. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + else if (err.code === "ResourceNotFoundException") + // We can't find the resource that you asked for. + // Deal with the exception here, and/or rethrow at your discretion. + throw err; + } + } +} +module.exports = SecretsManager; diff --git a/server/intellipay/intellipay.js b/server/intellipay/intellipay.js index 97d9771af..367303926 100644 --- a/server/intellipay/intellipay.js +++ b/server/intellipay/intellipay.js @@ -12,23 +12,32 @@ require("dotenv").config({ }); const domain = process.env.NODE_ENV ? "secure" : "test"; +const SecretsManager = require("./aws-secrets-manager"); -const getShopCredentials = () => { - // add parametes for the request - // TODO: Implement retrieval logic later. +const gqlClient = require("../graphql-client/graphql-client").client; - return { - merchantkey: "3B8068", //This should be dynamic - apikey: "Oepn2B.XqRgzAqHqvOOmYUxD2VW.vGSipi", //This should be dynamic - }; +const getShopCredentials = async (bodyshop) => { + // Development only + if (process.env.NODE_ENV === undefined) { + return { + merchantkey: process.env.DEV_INTELLIPAY_MERCHANTKEY, + apikey: process.env.DEV_INTELLIPAY_APIKEY, + }; + } + + // Production code + if (bodyshop?.imexshopid) { + const secret = await SecretsManager.getSecret( + `intellipay-credentials-${bodyshop.imexshopid}`, + process.env.REGION + ); + + return JSON.parse(secret); + } }; 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(); + const shopCredentials = await getShopCredentials(req.body.bodyshop); try { const options = { @@ -55,7 +64,7 @@ exports.lightbox_credentials = async (req, res) => { }; exports.payment_refund = async (req, res) => { - const shopCredentials = getShopCredentials(); + const shopCredentials = await getShopCredentials(req.body.bodyshop); try { const options = { @@ -81,8 +90,7 @@ exports.payment_refund = async (req, res) => { }; exports.generate_payment_url = async (req, res) => { - const shopCredentials = getShopCredentials(); - + const shopCredentials = await getShopCredentials(req.body.bodyshop); try { const options = { method: "POST", @@ -108,5 +116,69 @@ exports.generate_payment_url = async (req, res) => { exports.postback = async (req, res) => { console.log("postback as", req.body); - res.send({ message: "postback" }); + const { body: values } = req; + + // TODO query job by account name + const job = await gqlClient.request(queries.GET_JOB_BY_RO_NUMBER, { + ro_number: values.account, + }); + // TODO add mutation to database + + const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { + paymentInput: { + amount: values.total, + transactionid: `C00 ${values.authcode}`, + payer: "Customer", + type: values.cardtype, + jobid: job.jobs[0].id, + date: moment(Date.now()), + }, + }); + + await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { + paymentResponse: { + amount: values.total, + bodyshopid: job.jobs[0].bodyshop.id, + paymentid: paymentResult.id, + jobid: job.jobs[0].id, + declinereason: "Approved", + ext_paymentid: values.paymentid, + successful: true, + response: values, + }, + }); + + res.send({ message: "Postback Successful" }); }; + +`{ + ipaddress: '136.158.34.242', + firstname: 'JC', + notes: '', + city: '', + fee: ' 0.00', + origin: 'OneLink', + total: '5061.36', + avsdata: 'N', + arglist: '""', + state: ' ', + cardtype: 'Visa', + department: '', + email: '', + timestamp: "{ts '2023-03-23 09:52:23'}", + op: 'Kh6Pa6AT9keg', + amount: '5061.36', + method: 'CARD', + address2: '', + address1: '', + lastname: 'Tolentino', + zipcode: '1742 ', + authcode: '367885', + phone: '', + merchantid: '7114', + paymentid: '24205435', + customerid: '19610104', + comment: '', + invoice: '', + account: 'QBD241' +}`;