diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index f3058c0ef..aaa4398c9 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -12338,6 +12338,27 @@ + + enterpayment + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + entertimeticket false @@ -14184,6 +14205,37 @@ + + payments + + + labels + + + new + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + printcenter diff --git a/client/package.json b/client/package.json index 147409805..57da66dd1 100644 --- a/client/package.json +++ b/client/package.json @@ -5,6 +5,8 @@ "proxy": "https://localhost:5000", "dependencies": { "@nivo/pie": "^0.61.1", + "@stripe/react-stripe-js": "^1.1.2", + "@stripe/stripe-js": "^1.7.0", "@tinymce/tinymce-react": "^3.6.0", "aamva": "^1.2.0", "antd": "^4.2.2", diff --git a/client/src/components/header/header.component.jsx b/client/src/components/header/header.component.jsx index b2ae84283..7d5f2b84d 100644 --- a/client/src/components/header/header.component.jsx +++ b/client/src/components/header/header.component.jsx @@ -26,6 +26,8 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(setModalContext({ context: context, modal: "invoiceEnter" })), setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })), + setPaymentContext: (context) => + dispatch(setModalContext({ context: context, modal: "payment" })), }); function Header({ @@ -37,6 +39,7 @@ function Header({ signOutStart, setInvoiceEnterContext, setTimeTicketContext, + setPaymentContext, }) { const { t } = useTranslation(); //TODO Add @@ -199,6 +202,18 @@ function Header({ } > + { + setPaymentContext({ + actions: {}, + context: {}, + }); + }} + > + {t("menus.header.enterpayment")} + + { diff --git a/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx index da3ba9b57..dfbdcb856 100644 --- a/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx +++ b/client/src/components/invoice-enter-modal/invoice-enter-modal.container.jsx @@ -14,6 +14,7 @@ import { import { handleUpload } from "../documents-upload/documents-upload.utility"; import InvoiceFormContainer from "../invoice-form/invoice-form.container"; import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; + const mapStateToProps = createStructuredSelector({ invoiceEnterModal: selectInvoiceEnterModal, bodyshop: selectBodyshop, diff --git a/client/src/components/payment-form/payment-form.component.jsx b/client/src/components/payment-form/payment-form.component.jsx new file mode 100644 index 000000000..9fdebdd11 --- /dev/null +++ b/client/src/components/payment-form/payment-form.component.jsx @@ -0,0 +1,60 @@ +import { Form } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import CurrencyInput from "../form-items-formatted/currency-form-item.component"; +import JobSearchSelect from "../job-search-select/job-search-select.component"; +import { CardElement } from "@stripe/react-stripe-js"; + +export default function PaymentFormComponent({ + form, + roAutoCompleteOptions, + stripeStateArr, +}) { + const [stripeState, setStripeState] = stripeStateArr; + console.log("stripeState", stripeState); + const { t } = useTranslation(); + const handleStripeChange = (e) => { + setStripeState({ error: e.error, cardComplete: e.complete }); + }; + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/client/src/components/payment-form/payment-form.container.jsx b/client/src/components/payment-form/payment-form.container.jsx new file mode 100644 index 000000000..70e1b7f67 --- /dev/null +++ b/client/src/components/payment-form/payment-form.container.jsx @@ -0,0 +1,28 @@ +import { useQuery } from "@apollo/react-hooks"; +import React from "react"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { ACTIVE_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import PaymentFormComponent from "./payment-form.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function PaymentFormContainer({ bodyshop, form,stripeStateArr }) { + const { data: RoAutoCompleteData } = useQuery(ACTIVE_JOBS_FOR_AUTOCOMPLETE, { + variables: { statuses: bodyshop.md_ro_statuses.open_statuses || ["Open*"] }, + }); + + return ( +
+ +
+ ); +} +export default connect(mapStateToProps, null)(PaymentFormContainer); diff --git a/client/src/components/payment-modal/payment-modal.container.jsx b/client/src/components/payment-modal/payment-modal.container.jsx new file mode 100644 index 000000000..6f3283a1a --- /dev/null +++ b/client/src/components/payment-modal/payment-modal.container.jsx @@ -0,0 +1,125 @@ +import { useElements, useStripe, CardElement } from "@stripe/react-stripe-js"; +import { Form, Modal } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { toggleModalVisible } from "../../redux/modals/modals.actions"; +import { selectPayment } from "../../redux/modals/modals.selectors"; +import { + selectBodyshop, + selectCurrentUser, +} from "../../redux/user/user.selectors"; +import PaymentForm from "../payment-form/payment-form.container"; +import axios from "axios"; +const mapStateToProps = createStructuredSelector({ + paymentModal: selectPayment, + bodyshop: selectBodyshop, + currentUser: selectCurrentUser, +}); + +const mapDispatchToProps = (dispatch) => ({ + toggleModalVisible: () => dispatch(toggleModalVisible("payment")), +}); + +function InvoiceEnterModalContainer({ + paymentModal, + toggleModalVisible, + bodyshop, + currentUser, +}) { + const [form] = Form.useForm(); + const stripe = useStripe(); + const elements = useElements(); + const { t } = useTranslation(); + const { context, actions, visible } = paymentModal; + const [loading, setLoading] = useState(false); + const stripeStateArr = useState({ + error: null, + cardComplete: false, + }); + const [stripeState, setStripeState] = stripeStateArr; + + const cardValid = !!!stripeState.error && stripeState.cardComplete; + + const handleFinish = async (values) => { + if (!cardValid) return; + + if (!stripe || !elements) { + // Stripe.js has not yet loaded. + // Make sure to disable form submission until Stripe.js has loaded. + return; + } + setLoading(true); + try { + const pr = stripe.paymentRequest({ + country: "US", + currency: "usd", + total: { + label: "Demo total", + amount: 1099, + }, + requestPayerName: true, + requestPayerEmail: true, + }); + console.log("handleFinish -> pr", pr); + + // const secretKey = await axios.post("/stripe/payment", { + // amount: Math.round(values.amount * 100), + // stripe_acct_id: bodyshop.stripe_acct_id, + // }); + // console.log("handleFinish -> secretKey", secretKey); + // const result = await stripe.confirmCardPayment( + // secretKey.data.clientSecret, + // { + // payment_method: { + // card: elements.getElement(CardElement), + // billing_details: { + // name: "Jenny Rosen", + // }, + // }, + // } + // ); + // console.log("handleFinish -> result", result); + + if (actions.refetch) actions.refetch(); + } catch (error) { + console.log("error", error); + } + + setLoading(false); + // toggleModalVisible(); + }; + + const handleCancel = () => { + toggleModalVisible(); + }; + + return ( + form.submit()} + onCancel={handleCancel} + afterClose={() => form.resetFields()} + okButtonProps={{ loading: loading, disabled: !cardValid }} + destroyOnClose + > +
+ + +
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(InvoiceEnterModalContainer); diff --git a/client/src/components/payment-modal/payment-modal.wrapper.jsx b/client/src/components/payment-modal/payment-modal.wrapper.jsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/graphql/bodyshop.queries.js b/client/src/graphql/bodyshop.queries.js index 7882513a8..9b03fa1ee 100644 --- a/client/src/graphql/bodyshop.queries.js +++ b/client/src/graphql/bodyshop.queries.js @@ -31,6 +31,7 @@ export const QUERY_BODYSHOP = gql` inhousevendorid accountingconfig appt_length + stripe_acct_id employees { id first_name @@ -80,6 +81,7 @@ export const UPDATE_SHOP = gql` production_config invoice_tax_rates appt_length + stripe_acct_id employees { id first_name @@ -99,3 +101,10 @@ export const QUERY_INTAKE_CHECKLIST = gql` } } `; +export const QUERY_STRIPE_ID = gql` + query QUERY_STRIPE_ID { + bodyshops { + stripe_acct_id + } + } +`; diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index 7f9f5c68d..91725ce21 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -1,22 +1,29 @@ +import { Elements } from "@stripe/react-stripe-js"; import { BackTop, Layout } from "antd"; import React, { lazy, Suspense, useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; import { Route, Switch } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; +import ConflictComponent from "../../components/conflict/conflict.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import FcmNotification from "../../components/fcm-notification/fcm-notification.component"; import FooterComponent from "../../components/footer/footer.component"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { selectInstanceConflict } from "../../redux/user/user.selectors"; //Component Imports import HeaderContainer from "../../components/header/header.container"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; -import ConflictComponent from "../../components/conflict/conflict.component"; - +import { + selectInstanceConflict, + selectBodyshop, +} from "../../redux/user/user.selectors"; import "./manage.page.styles.scss"; +import { loadStripe } from "@stripe/stripe-js"; +import { client } from "../../App/App.container"; +import { QUERY_STRIPE_ID } from "../../graphql/bodyshop.queries"; + const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container") ); @@ -79,6 +86,9 @@ const EnterInvoiceModalContainer = lazy(() => const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container") ); +const PaymentModalContainer = lazy(() => + import("../../components/payment-modal/payment-modal.container") +); const ProductionListPage = lazy(() => import("../production-list/production-list.container") ); @@ -105,13 +115,20 @@ const ShopCsiPageContainer = lazy(() => const { Header, Content, Footer } = Layout; +const stripePromise = new Promise((resolve, reject) => { + client.query({ query: QUERY_STRIPE_ID }).then((resp) => { + resolve( + loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY, { + stripeAccount: resp.data.bodyshops[0].stripe_acct_id || "", + }) + ); + }); +}); + const mapStateToProps = createStructuredSelector({ - //currentUser: selectCurrentUser conflict: selectInstanceConflict, }); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); +const mapDispatchToProps = (dispatch) => ({}); export function Manage({ match, conflict }) { const { t } = useTranslation(); @@ -145,6 +162,10 @@ export function Manage({ match, conflict }) { + + + + { diff --git a/client/src/redux/modals/modals.selectors.js b/client/src/redux/modals/modals.selectors.js index 89d30cd09..a28a6f48d 100644 --- a/client/src/redux/modals/modals.selectors.js +++ b/client/src/redux/modals/modals.selectors.js @@ -46,3 +46,7 @@ export const selectReconciliation = createSelector( [selectModals], (modals) => modals.reconciliation ); +export const selectPayment = createSelector( + [selectModals], + (modals) => modals.payment +); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 9933174a9..3a8435fac 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -754,6 +754,7 @@ "courtesycars-newcontract": "New Contract", "customers": "Customers", "enterinvoices": "Enter Invoices", + "enterpayment": "Enter Payments", "entertimeticket": "Enter Time Tickets", "home": "Home", "invoices": "Invoices", @@ -886,6 +887,11 @@ "created": "Parts order created successfully. " } }, + "payments": { + "labels": { + "new": "New Payment" + } + }, "printcenter": { "errors": { "nocontexttype": "No context type set." diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 984bf9b02..8ec75bea0 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -754,6 +754,7 @@ "courtesycars-newcontract": "", "customers": "Clientes", "enterinvoices": "", + "enterpayment": "", "entertimeticket": "", "home": "Casa", "invoices": "", @@ -886,6 +887,11 @@ "created": "Pedido de piezas creado con éxito." } }, + "payments": { + "labels": { + "new": "" + } + }, "printcenter": { "errors": { "nocontexttype": "" diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index ca95942d5..787815d96 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -754,6 +754,7 @@ "courtesycars-newcontract": "", "customers": "Les clients", "enterinvoices": "", + "enterpayment": "", "entertimeticket": "", "home": "Accueil", "invoices": "", @@ -886,6 +887,11 @@ "created": "Commande de pièces créée avec succès." } }, + "payments": { + "labels": { + "new": "" + } + }, "printcenter": { "errors": { "nocontexttype": "" diff --git a/client/yarn.lock b/client/yarn.lock index 631fd89e4..df8ea0bea 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -1999,6 +1999,18 @@ resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.21.tgz#5264d12019ffb844dc1fc44d55517ded7b580ee2" integrity sha512-Wcu3CFJV+iiqPEIoPVx3/CYnZBRgPeRABo6bLJByRH9ptJXyObn7WYPG7Rv0cg3+55bqcBbG0xEfovzwE2PNXg== +"@stripe/react-stripe-js@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.1.2.tgz#a7f5ef5b4d7dc7fa723501b706644414cfe6dcba" + integrity sha512-07hu8RJXwWKGbvdvd1yt1cYvGtDB8jFX+q10f7FQuItUt9rlSo0am3WIx845iMHANiYgxyRb1PS201Yle9xxPQ== + dependencies: + prop-types "^15.7.2" + +"@stripe/stripe-js@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.7.0.tgz#870885e69b7627e59cd4777da558e494ba5cb698" + integrity sha512-/OreTnc8qWBsNrkpNTOxn67oRlqa+PZXukpiyZTcPo9/DRYyxtBZKvplFbH0C/qC/w0TpN8cueRl3h/dNYO4eg== + "@svgr/babel-plugin-add-jsx-attribute@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" diff --git a/hasura/migrations/1591303854947_create_table_public_payments/down.yaml b/hasura/migrations/1591303854947_create_table_public_payments/down.yaml new file mode 100644 index 000000000..b7ad7a0d6 --- /dev/null +++ b/hasura/migrations/1591303854947_create_table_public_payments/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: DROP TABLE "public"."payments"; + type: run_sql diff --git a/hasura/migrations/1591303854947_create_table_public_payments/up.yaml b/hasura/migrations/1591303854947_create_table_public_payments/up.yaml new file mode 100644 index 000000000..cc211749f --- /dev/null +++ b/hasura/migrations/1591303854947_create_table_public_payments/up.yaml @@ -0,0 +1,23 @@ +- args: + cascade: false + read_only: false + sql: CREATE EXTENSION IF NOT EXISTS pgcrypto; + type: run_sql +- args: + cascade: false + read_only: false + sql: "CREATE TABLE \"public\".\"payments\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(), + \"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz + NOT NULL DEFAULT now(), \"jobid\" uuid NOT NULL, \"amount\" integer NOT NULL, + PRIMARY KEY (\"id\") , FOREIGN KEY (\"jobid\") REFERENCES \"public\".\"jobs\"(\"id\") + ON UPDATE restrict ON DELETE restrict);\nCREATE OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS + TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\" + = NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_payments_updated_at\"\nBEFORE + UPDATE ON \"public\".\"payments\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT + ON TRIGGER \"set_public_payments_updated_at\" ON \"public\".\"payments\" \nIS + 'trigger to set value of column \"updated_at\" to current timestamp on row update';" + type: run_sql +- args: + name: payments + schema: public + type: add_existing_table_or_view diff --git a/hasura/migrations/1591303886596_track_all_relationships/down.yaml b/hasura/migrations/1591303886596_track_all_relationships/down.yaml new file mode 100644 index 000000000..4be842dba --- /dev/null +++ b/hasura/migrations/1591303886596_track_all_relationships/down.yaml @@ -0,0 +1,12 @@ +- args: + relationship: payments + table: + name: jobs + schema: public + type: drop_relationship +- args: + relationship: job + table: + name: payments + schema: public + type: drop_relationship diff --git a/hasura/migrations/1591303886596_track_all_relationships/up.yaml b/hasura/migrations/1591303886596_track_all_relationships/up.yaml new file mode 100644 index 000000000..dc94463eb --- /dev/null +++ b/hasura/migrations/1591303886596_track_all_relationships/up.yaml @@ -0,0 +1,20 @@ +- args: + name: payments + table: + name: jobs + schema: public + using: + foreign_key_constraint_on: + column: jobid + table: + name: payments + schema: public + type: create_array_relationship +- args: + name: job + table: + name: payments + schema: public + using: + foreign_key_constraint_on: jobid + type: create_object_relationship diff --git a/hasura/migrations/1591303935525_update_permission_user_public_table_payments/down.yaml b/hasura/migrations/1591303935525_update_permission_user_public_table_payments/down.yaml new file mode 100644 index 000000000..9830eb5ca --- /dev/null +++ b/hasura/migrations/1591303935525_update_permission_user_public_table_payments/down.yaml @@ -0,0 +1,6 @@ +- args: + role: user + table: + name: payments + schema: public + type: drop_insert_permission diff --git a/hasura/migrations/1591303935525_update_permission_user_public_table_payments/up.yaml b/hasura/migrations/1591303935525_update_permission_user_public_table_payments/up.yaml new file mode 100644 index 000000000..9d5cc8103 --- /dev/null +++ b/hasura/migrations/1591303935525_update_permission_user_public_table_payments/up.yaml @@ -0,0 +1,28 @@ +- args: + permission: + allow_upsert: true + check: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - id + - created_at + - updated_at + - jobid + - amount + localPresets: + - key: "" + value: "" + set: {} + role: user + table: + name: payments + schema: public + type: create_insert_permission diff --git a/hasura/migrations/1591303943431_update_permission_user_public_table_payments/down.yaml b/hasura/migrations/1591303943431_update_permission_user_public_table_payments/down.yaml new file mode 100644 index 000000000..5c9a79d99 --- /dev/null +++ b/hasura/migrations/1591303943431_update_permission_user_public_table_payments/down.yaml @@ -0,0 +1,6 @@ +- args: + role: user + table: + name: payments + schema: public + type: drop_select_permission diff --git a/hasura/migrations/1591303943431_update_permission_user_public_table_payments/up.yaml b/hasura/migrations/1591303943431_update_permission_user_public_table_payments/up.yaml new file mode 100644 index 000000000..7f855df65 --- /dev/null +++ b/hasura/migrations/1591303943431_update_permission_user_public_table_payments/up.yaml @@ -0,0 +1,26 @@ +- args: + permission: + allow_aggregations: false + columns: + - amount + - created_at + - updated_at + - id + - jobid + computed_fields: [] + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + limit: null + role: user + table: + name: payments + schema: public + type: create_select_permission diff --git a/hasura/migrations/1591303949732_update_permission_user_public_table_payments/down.yaml b/hasura/migrations/1591303949732_update_permission_user_public_table_payments/down.yaml new file mode 100644 index 000000000..631fe8779 --- /dev/null +++ b/hasura/migrations/1591303949732_update_permission_user_public_table_payments/down.yaml @@ -0,0 +1,6 @@ +- args: + role: user + table: + name: payments + schema: public + type: drop_update_permission diff --git a/hasura/migrations/1591303949732_update_permission_user_public_table_payments/up.yaml b/hasura/migrations/1591303949732_update_permission_user_public_table_payments/up.yaml new file mode 100644 index 000000000..714a8d9dd --- /dev/null +++ b/hasura/migrations/1591303949732_update_permission_user_public_table_payments/up.yaml @@ -0,0 +1,27 @@ +- args: + permission: + columns: + - amount + - created_at + - updated_at + - id + - jobid + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + localPresets: + - key: "" + value: "" + set: {} + role: user + table: + name: payments + schema: public + type: create_update_permission diff --git a/hasura/migrations/1591303955052_update_permission_user_public_table_payments/down.yaml b/hasura/migrations/1591303955052_update_permission_user_public_table_payments/down.yaml new file mode 100644 index 000000000..7e121fe18 --- /dev/null +++ b/hasura/migrations/1591303955052_update_permission_user_public_table_payments/down.yaml @@ -0,0 +1,6 @@ +- args: + role: user + table: + name: payments + schema: public + type: drop_delete_permission diff --git a/hasura/migrations/1591303955052_update_permission_user_public_table_payments/up.yaml b/hasura/migrations/1591303955052_update_permission_user_public_table_payments/up.yaml new file mode 100644 index 000000000..26cab072d --- /dev/null +++ b/hasura/migrations/1591303955052_update_permission_user_public_table_payments/up.yaml @@ -0,0 +1,17 @@ +- args: + permission: + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + role: user + table: + name: payments + schema: public + type: create_delete_permission diff --git a/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/down.yaml b/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/down.yaml new file mode 100644 index 000000000..214dc8bcb --- /dev/null +++ b/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/down.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "stripe_acct_id"; + type: run_sql diff --git a/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/up.yaml b/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/up.yaml new file mode 100644 index 000000000..0d281d82f --- /dev/null +++ b/hasura/migrations/1591310798527_alter_table_public_bodyshops_add_column_stripe_acct_id/up.yaml @@ -0,0 +1,5 @@ +- args: + cascade: false + read_only: false + sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "stripe_acct_id" text NULL; + type: run_sql diff --git a/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/down.yaml b/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/down.yaml new file mode 100644 index 000000000..26ce89aa9 --- /dev/null +++ b/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/down.yaml @@ -0,0 +1,52 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_length + - city + - country + - created_at + - email + - federal_tax_id + - id + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - invoice_tax_rates + - logo_img_path + - md_order_statuses + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - production_config + - region_config + - shopname + - shoprates + - state + - state_tax_id + - template_header + - textid + - updated_at + - zip_post + computed_fields: [] + filter: + associations: + bodyshop: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/up.yaml b/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/up.yaml new file mode 100644 index 000000000..a412d8d21 --- /dev/null +++ b/hasura/migrations/1591310807506_update_permission_user_public_table_bodyshops/up.yaml @@ -0,0 +1,53 @@ +- args: + role: user + table: + name: bodyshops + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - accountingconfig + - address1 + - address2 + - appt_length + - city + - country + - created_at + - email + - federal_tax_id + - id + - inhousevendorid + - insurance_vendor_id + - intakechecklist + - invoice_tax_rates + - logo_img_path + - md_order_statuses + - md_responsibility_centers + - md_ro_statuses + - messagingservicesid + - production_config + - region_config + - shopname + - shoprates + - state + - state_tax_id + - stripe_acct_id + - template_header + - textid + - updated_at + - zip_post + computed_fields: [] + filter: + associations: + bodyshop: + associations: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: bodyshops + schema: public + type: create_select_permission diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 0f2144f22..803613bdd 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -469,6 +469,7 @@ tables: - shoprates - state - state_tax_id + - stripe_acct_id - template_header - textid - updated_at @@ -1867,6 +1868,13 @@ tables: table: schema: public name: parts_orders + - name: payments + using: + foreign_key_constraint_on: + column: jobid + table: + schema: public + name: payments - name: timetickets using: foreign_key_constraint_on: @@ -3162,6 +3170,83 @@ tables: _eq: X-Hasura-User-Id - active: _eq: true +- table: + schema: public + name: payments + object_relationships: + - name: job + using: + foreign_key_constraint_on: jobid + insert_permissions: + - role: user + permission: + check: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + columns: + - id + - created_at + - updated_at + - jobid + - amount + select_permissions: + - role: user + permission: + columns: + - amount + - created_at + - updated_at + - id + - jobid + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + update_permissions: + - role: user + permission: + columns: + - amount + - created_at + - updated_at + - id + - jobid + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + delete_permissions: + - role: user + permission: + filter: + job: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true - table: schema: public name: productionview diff --git a/package.json b/package.json index 2e5a2af80..0b33a1eff 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "moment": "^2.26.0", "nodemailer": "^6.4.4", "phone": "^2.4.8", + "stripe": "^8.60.0", "twilio": "^3.41.1", "xmlbuilder": "^15.1.1" }, diff --git a/server.js b/server.js index ebd32b252..ee4adb7f6 100644 --- a/server.js +++ b/server.js @@ -80,6 +80,10 @@ app.post("/render", renderHandlebars.render); var fb = require("./server/firebase/firebase-handler"); app.post("/notifications/send", fb.sendNotification); +//Stripe Processing +var stripe = require("./server/stripe/payment"); +app.post("/stripe/payment", stripe.payment); + //Serve React App if in Production if (process.env.NODE_ENV === "production") { app.use(express.static(path.join(__dirname, "client/build"))); diff --git a/server/stripe/payment.js b/server/stripe/payment.js new file mode 100644 index 000000000..bcce561ea --- /dev/null +++ b/server/stripe/payment.js @@ -0,0 +1,60 @@ +const GraphQLClient = require("graphql-request").GraphQLClient; +const path = require("path"); +const queries = require("../graphql-client/queries"); +const Dinero = require("dinero.js"); + +require("dotenv").config({ + path: path.resolve( + process.cwd(), + `.env.${process.env.NODE_ENV || "development"}` + ), +}); +const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); + +exports.payment = async (req, res) => { + const { amount, stripe_acct_id } = req.body; + console.log("exports.payment -> amount", amount); + console.log("exports.payment -> stripe_acct_id", stripe_acct_id); + try { + + const pr = await stripe.paymentRequest.create({ + country: "CA", + currency: "cad", + total: { + label: "Demo total", + amount: 1099, + }, + requestPayerName: true, + requestPayerEmail: true, + }); + + console.log(pr); + + await stripe.paymentIntents + .create( + { + payment_method_types: ["card"], + amount: amount, + currency: "cad", + application_fee_amount: 100, + }, + { + stripeAccount: stripe_acct_id, + } + ) + .then(function (paymentIntent) { + try { + return res.send({ + clientSecret: paymentIntent.client_secret, + }); + } catch (err) { + return res.status(500).send({ + error: err.message, + }); + } + }); + } catch (error) { + console.log("error", error); + res.status(400).send(error); + } +}; diff --git a/yarn.lock b/yarn.lock index 50c539069..ad5e1e3f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -272,6 +272,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.17.tgz#191b71e7f4c325ee0fb23bc4a996477d92b8c39b" integrity sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA== +"@types/node@>=8.1.0": + version "14.0.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3" + integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg== + "@types/node@^13.7.0": version "13.13.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" @@ -2342,6 +2347,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.6.0: + version "6.9.4" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.4.tgz#9090b290d1f91728d3c22e54843ca44aea5ab687" + integrity sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ== + qs@^6.9.1: version "6.9.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.2.tgz#a27b695006544a04bf0e6c6a7e8120778926d5bd" @@ -2759,6 +2769,14 @@ strip-json-comments@^3.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== +stripe@^8.60.0: + version "8.60.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.60.0.tgz#764106ab639338447fddcedcd4c32ba6eb61b8bc" + integrity sha512-FmM0Cy1YLW8Nk3dzWg83tDKROPAJwS2f+Xeezyq15JRUSOUqaSoj1FguBY460GWpzzQcqSXUXkQozYcLU1UtAg== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.6.0" + stubs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"