From 24c57c4731064d4d53784036e2beacc8924668c2 Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:53:03 +0100 Subject: [PATCH 01/18] #35153 added card fields component --- src/App.tsx | 16 +- src/components/CardFields/CardFields.tsx | 40 +++ .../CardFields/CardFieldsButton.tsx | 20 ++ .../CardFields/CardFieldsInvalid.tsx | 5 + src/components/CardFields/CardFieldsMask.tsx | 244 ++++++++++++++++++ src/components/CardFields/SubmitPayment.tsx | 139 ++++++++++ src/components/CardFields/constants.tsx | 5 + src/components/CardFields/index.ts | 1 + 8 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 src/components/CardFields/CardFields.tsx create mode 100644 src/components/CardFields/CardFieldsButton.tsx create mode 100644 src/components/CardFields/CardFieldsInvalid.tsx create mode 100644 src/components/CardFields/CardFieldsMask.tsx create mode 100644 src/components/CardFields/SubmitPayment.tsx create mode 100644 src/components/CardFields/constants.tsx create mode 100644 src/components/CardFields/index.ts diff --git a/src/App.tsx b/src/App.tsx index aaf84fa..a21b844 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,10 +8,11 @@ import { PayPal } from "./components/PayPal"; import { PayPalMessages } from "./components/PayPalMessages"; import { HostedFields } from "./components/HostedFields"; import { PaymentTokens } from "./components/PaymentTokens"; +import { CardFields } from "./components/CardFields"; -const CC_FRONTEND_EXTENSION_VERSION: string = "devmajidabbasi"; +const CC_FRONTEND_EXTENSION_VERSION: string = "devjonathanyeboah"; const FRONTASTIC_SESSION: string = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiI3ZGI0YmJmOC1mMTMwLTRlYjQtODMyZS1mMWZkZmUxYjQ1YWIiLCJ3aXNobGlzdElkIjoiOGI0NDVjMWMtNDliOC00ODlkLTk4NDgtZGQ2ZmNiOTZmNzljIiwiYWNjb3VudCI6eyJhY2NvdW50SWQiOiJmMjJhNGZlMy1jMmI4LTQ4MDEtODIwOC00MTRkMjA2MjBlMGIiLCJlbWFpbCI6Im1hamlkLmFiYmFzaUBtZWRpYW9wdC5kZSIsInNhbHV0YXRpb24iOiIiLCJmaXJzdE5hbWUiOiJNYWppZCIsImxhc3ROYW1lIjoiQWJiYXNpIiwiYmlydGhkYXkiOiIxOTg5LTAzLTA1VDAwOjAwOjAwLjAwMFoiLCJjb25maXJtZWQiOnRydWUsImFkZHJlc3NlcyI6W3siYWRkcmVzc0lkIjoiamJUSlhtM00iLCJmaXJzdE5hbWUiOiJNYWppZCIsImxhc3ROYW1lIjoiQWJiYXNpIiwic3RyZWV0TmFtZSI6IkhvY2hzdHJhXHUwMGRmZSAzNyIsInN0cmVldE51bWJlciI6IkhvY2hzdHJhXHUwMGRmZSAzNyIsInBvc3RhbENvZGUiOiIxMzM1NyIsImNpdHkiOiJERSIsImNvdW50cnkiOiJERSIsInBob25lIjoiNTk5MzU3NTYyIiwiaXNEZWZhdWx0QmlsbGluZ0FkZHJlc3MiOmZhbHNlLCJpc0RlZmF1bHRTaGlwcGluZ0FkZHJlc3MiOmZhbHNlfSx7ImFkZHJlc3NJZCI6ImtyelI3bTBRIiwiZmlyc3ROYW1lIjoiTWFqaWQiLCJsYXN0TmFtZSI6IkFiYmFzaSIsInN0cmVldE5hbWUiOiJDb3VudHkgU3QuIE1pYW1pIiwic3RyZWV0TnVtYmVyIjoiNDMyIiwicG9zdGFsQ29kZSI6IjMzMDE4IiwiY2l0eSI6IlVTIiwiY291bnRyeSI6IkRFIiwicGhvbmUiOiI1OTkzNTc1NjIiLCJpc0RlZmF1bHRCaWxsaW5nQWRkcmVzcyI6dHJ1ZSwiaXNEZWZhdWx0U2hpcHBpbmdBZGRyZXNzIjp0cnVlfV19fQ.WgszPIO2zvdPMHyRFpJdocBFxZLrtds4Ls4BP8TxjSU"; + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIyOGVhMjMxYS00Mzg3LTQwMTctYWQ3Yi03YTMxMWFiMTdhNzkifQ.oq6K9l8oZZbGy2JAyCFb4TPtfiy14k4K9Q8Kz7JSgxM"; function App() { const [choosenPaymentMethod, setChoosenPaymentMethod] = useState(""); @@ -209,6 +210,17 @@ function App() { }} /> ), + CardFields: ( + + ), HostedFieldsVault: ( = ({ + options, + getSettingsUrl, + createPaymentUrl, + requestHeader, + getClientTokenUrl, + getUserInfoUrl, + shippingMethodId, + cartInformation, + createOrderUrl, + onApproveUrl, + purchaseCallback, + enableVaulting, +}) => { + return ( + + + + ); +}; diff --git a/src/components/CardFields/CardFieldsButton.tsx b/src/components/CardFields/CardFieldsButton.tsx new file mode 100644 index 0000000..2fbdd47 --- /dev/null +++ b/src/components/CardFields/CardFieldsButton.tsx @@ -0,0 +1,20 @@ +import React from "react"; + +import { usePayment } from "../../app/usePayment"; +import { CardFieldsMask } from "./CardFieldsMask"; +import { HostedFieldsProps } from "../../types"; +import { useHandleCreatePayment } from "../../app/useHandleCreatePayment"; + +export const CardFieldsButton: React.FC = ({ + options, + enableVaulting, +}) => { + const { paymentInfo } = usePayment(); + useHandleCreatePayment(); + + return paymentInfo.id && window.paypal ? ( + + ) : ( + <> + ); +}; diff --git a/src/components/CardFields/CardFieldsInvalid.tsx b/src/components/CardFields/CardFieldsInvalid.tsx new file mode 100644 index 0000000..264c3b6 --- /dev/null +++ b/src/components/CardFields/CardFieldsInvalid.tsx @@ -0,0 +1,5 @@ +const CardFieldsInvalid = () => { + return *; +}; + +export default CardFieldsInvalid; diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx new file mode 100644 index 0000000..1f9e6af --- /dev/null +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -0,0 +1,244 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; + +import { + PayPalHostedField, + PayPalHostedFieldsProvider, + PayPalScriptProvider, +} from "@paypal/react-paypal-js"; +import { usePayment } from "../../app/usePayment"; +import { useSettings } from "../../app/useSettings"; +import { HostedFieldsProps } from "../../types"; +import CardFieldsInvalid from "./CardFieldsInvalid"; +import { CARD_FIELDS_INPUTS, CARD_FIELDS_BUTTON } from "./constants"; +import { SubmitPayment } from "./SubmitPayment"; + +const CUSTOM_FIELD_STYLE = { + border: "1px solid #606060", + boxShadow: "2px 2px 10px 2px rgba(0,0,0,0.1)", +}; + +export const CardFieldsMask: React.FC = ({ + options, + enableVaulting, +}) => { + const { handleCreateOrder } = usePayment(); + const { settings, paymentTokens } = useSettings(); + const { clientToken } = usePayment(); + const [addNew, setAddNew] = useState(false); + const [vaultId, setVaultId] = useState(); + const nameField = useRef(null); + const numberField = useRef(null); + const cvvField = useRef(null); + const expiryField = useRef(null); + + let saveCard = false; + + const cardPaymentTokens = paymentTokens?.payment_tokens?.filter( + (paymentToken) => paymentToken.payment_source.card !== undefined + ); + + const hostedFieldClasses = useMemo(() => { + const hostedFieldsPayButtonClasses = + settings?.hostedFieldsPayButtonClasses || CARD_FIELDS_BUTTON; + const hostedFieldsInputFieldClasses = + settings?.hostedFieldsInputFieldClasses || CARD_FIELDS_INPUTS; + return { hostedFieldsPayButtonClasses, hostedFieldsInputFieldClasses }; + }, [settings]); + + const cardField = useMemo(() => { + // @ts-ignore + return window.paypal!.CardFields({ + createOrder: function (data: Record) { + return fetch("myserver.com/api/orders", { + method: "post", + + body: { + // @ts-ignore + paymentSource: data.paymentSource, + }, + }) + .then((res) => { + return res.json(); + }) + + .then((orderData) => { + return orderData.id; + }); + }, + onApprove: function (data: Record) { + const { orderID } = data; + + return fetch(`myserver.com/api/orders/${orderID}/capture`, { + method: "post", + }) + .then((res) => { + return res.json(); + }) + + .then((orderData) => { + // Redirect to success page + }); + }, + onError: function (error: Record) { + // Do something with the error from the SDK + }, + }); + }, []); + + useEffect(() => { + if (!settings) return; + if (cardField && cardField.isEligible()) { + const cardNameField = cardField.NameField(); + cardNameField.render(nameField.current); + + const cardNumberField = cardField.NumberField(); + cardNumberField.render(numberField.current); + + const cardCvvField = cardField.CVVField(); + cardCvvField.render(cvvField.current); + + const cardExpiryField = cardField.ExpiryField(); + cardExpiryField.render(expiryField.current); + } + }, [cardField, settings]); + + return !settings ? ( + <> + ) : ( +
+
+ +
+ +
+ +
+ + +
+ /* + {cardPaymentTokens && + cardPaymentTokens?.length > 0 && + addNew === false ? ( + <> + {cardPaymentTokens.map((paymentToken) => { + const { id, payment_source } = paymentToken; + const { brand, last_digits } = payment_source.card; + + return ( +
+ + { + setVaultId(e.target.value); + }} + /> + {brand} ending in {last_digits} + +
+ ); + })} + {vaultId && ( +
+ +
+ )} + + + + ) : ( + { + return handleCreateOrder({ + paymentSource: "card", + storeInVault: saveCard, + }); + }} + notEligibleError={

hosted fields not available

} + > + + + + + + + { + saveCard = target.checked; + }} + /> +
+ )} +
*/ + ); +}; diff --git a/src/components/CardFields/SubmitPayment.tsx b/src/components/CardFields/SubmitPayment.tsx new file mode 100644 index 0000000..39e644b --- /dev/null +++ b/src/components/CardFields/SubmitPayment.tsx @@ -0,0 +1,139 @@ +import { ChangeEvent, useRef, useState, useMemo } from "react"; + +import { usePayPalHostedFields } from "@paypal/react-paypal-js"; +import { usePayment } from "../../app/usePayment"; +import { useSettings } from "../../app/useSettings"; +import { CustomOnApproveData } from "../../types"; +import { useNotifications } from "../../app/useNotifications"; +import { useLoader } from "../../app/useLoader"; +import { CARD_FIELDS_INPUTS, CARD_FIELDS_BUTTON } from "./constants"; + +type SubmitPaymentProps = { + enableVaulting?: boolean; + handleSaveCard: (event: ChangeEvent) => void; +}; + +export const SubmitPayment: React.FC = ({ + enableVaulting, + handleSaveCard, +}) => { + const customStyle = { + border: "1px solid #606060", + boxShadow: "2px 2px 10px 2px rgba(0,0,0,0.1)", + }; + const { handleOnApprove } = usePayment(); + const { settings } = useSettings(); + const { notify } = useNotifications(); + const { isLoading } = useLoader(); + const [paying, setPaying] = useState(false); + const cardHolderName = useRef(null); + + const save = useRef(null); + const hostedField = usePayPalHostedFields(); + const threeDSAuth = settings?.threeDSOption; + const hostedFieldClasses = useMemo(() => { + const hostedFieldsPayButtonClasses = + settings?.hostedFieldsPayButtonClasses || CARD_FIELDS_BUTTON; + const hostedFieldsInputFieldClasses = + settings?.hostedFieldsInputFieldClasses || CARD_FIELDS_INPUTS; + return { hostedFieldsPayButtonClasses, hostedFieldsInputFieldClasses }; + }, [settings]); + + const approveTransaction = (approveData: CustomOnApproveData) => { + handleOnApprove(approveData).catch((err) => { + setPaying(false); + isLoading(false); + notify("Error", err.message); + }); + }; + + const handleClick = () => { + if (!hostedField?.cardFields) { + const childErrorMessage = + "Unable to find any child components in the "; + + notify("Error", childErrorMessage); + throw new Error(childErrorMessage); + } + const isFormInvalid = + Object.values(hostedField.cardFields.getState().fields).some( + (field) => !field.isValid + ) || !cardHolderName?.current?.value; + + if (isFormInvalid) { + notify("Error", "The form is invalid"); + return; + } + setPaying(true); + isLoading(true); + + const hostedFieldsOptions: Record = { + cardholderName: cardHolderName?.current?.value, + }; + if (threeDSAuth) { + hostedFieldsOptions.contingencies = [threeDSAuth]; + } + hostedField.cardFields + .submit(hostedFieldsOptions) + .then((data) => { + const approveData: CustomOnApproveData = { + orderID: data.orderId, + saveCard: save.current?.checked, + }; + + if (threeDSAuth) { + if (data.liabilityShift === "POSSIBLE") { + approveTransaction(approveData); + } else { + notify("Error", "3D Secure check has failed"); + isLoading(false); + setPaying(false); + } + } else { + approveTransaction(approveData); + } + }) + .catch((err) => { + notify("Error", err.message); + isLoading(false); + setPaying(false); + }); + }; + + return ( + <> + + {enableVaulting && ( + + )} + + + + ); +}; diff --git a/src/components/CardFields/constants.tsx b/src/components/CardFields/constants.tsx new file mode 100644 index 0000000..3ef22de --- /dev/null +++ b/src/components/CardFields/constants.tsx @@ -0,0 +1,5 @@ +export const CARD_FIELDS_INPUTS: string = + "w-full p-3 mt-1.5 mb-4 h-10 text-base bg-white text-neutral-700 border border-gray-300 rounded box-border resize-y"; + +export const CARD_FIELDS_BUTTON: string = + "float-right text-center whitespace-nowrap inline-block font-normal align-middle select-none cursor-pointer text-white text-base rounded py-1.5 px-3 bg-sky-500 border-sky-500"; diff --git a/src/components/CardFields/index.ts b/src/components/CardFields/index.ts new file mode 100644 index 0000000..281ce45 --- /dev/null +++ b/src/components/CardFields/index.ts @@ -0,0 +1 @@ +export { CardFields } from "./CardFields"; From 380724828c7f6135a1ca19056f10177008c466c1 Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:44:37 +0100 Subject: [PATCH 02/18] #35153 PyPals CardFields --- src/components/CardFields/CardFields.tsx | 2 +- .../CardFields/CardFieldsButton.tsx | 7 +- .../CardFields/CardFieldsInvalid.tsx | 5 - src/components/CardFields/CardFieldsMask.tsx | 312 ++++++++---------- src/components/CardFields/SubmitPayment.tsx | 139 -------- src/types/index.ts | 10 +- 6 files changed, 152 insertions(+), 323 deletions(-) delete mode 100644 src/components/CardFields/CardFieldsInvalid.tsx delete mode 100644 src/components/CardFields/SubmitPayment.tsx diff --git a/src/components/CardFields/CardFields.tsx b/src/components/CardFields/CardFields.tsx index 7706701..11527d4 100644 --- a/src/components/CardFields/CardFields.tsx +++ b/src/components/CardFields/CardFields.tsx @@ -34,7 +34,7 @@ export const CardFields: React.FC = ({ enableVaulting={enableVaulting} getUserInfoUrl={getUserInfoUrl} > - + ); }; diff --git a/src/components/CardFields/CardFieldsButton.tsx b/src/components/CardFields/CardFieldsButton.tsx index 2fbdd47..6a9a3bc 100644 --- a/src/components/CardFields/CardFieldsButton.tsx +++ b/src/components/CardFields/CardFieldsButton.tsx @@ -2,18 +2,17 @@ import React from "react"; import { usePayment } from "../../app/usePayment"; import { CardFieldsMask } from "./CardFieldsMask"; -import { HostedFieldsProps } from "../../types"; +import { CardFieldsProps } from "../../types"; import { useHandleCreatePayment } from "../../app/useHandleCreatePayment"; -export const CardFieldsButton: React.FC = ({ - options, +export const CardFieldsButton: React.FC = ({ enableVaulting, }) => { const { paymentInfo } = usePayment(); useHandleCreatePayment(); return paymentInfo.id && window.paypal ? ( - + ) : ( <> ); diff --git a/src/components/CardFields/CardFieldsInvalid.tsx b/src/components/CardFields/CardFieldsInvalid.tsx deleted file mode 100644 index 264c3b6..0000000 --- a/src/components/CardFields/CardFieldsInvalid.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const CardFieldsInvalid = () => { - return *; -}; - -export default CardFieldsInvalid; diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index 1f9e6af..d5700d0 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -1,35 +1,31 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; -import { - PayPalHostedField, - PayPalHostedFieldsProvider, - PayPalScriptProvider, -} from "@paypal/react-paypal-js"; import { usePayment } from "../../app/usePayment"; import { useSettings } from "../../app/useSettings"; -import { HostedFieldsProps } from "../../types"; -import CardFieldsInvalid from "./CardFieldsInvalid"; +import { CardFieldsProps, CustomOnApproveData } from "../../types"; import { CARD_FIELDS_INPUTS, CARD_FIELDS_BUTTON } from "./constants"; -import { SubmitPayment } from "./SubmitPayment"; +import { useNotifications } from "../../app/useNotifications"; +import { useLoader } from "../../app/useLoader"; -const CUSTOM_FIELD_STYLE = { - border: "1px solid #606060", - boxShadow: "2px 2px 10px 2px rgba(0,0,0,0.1)", -}; - -export const CardFieldsMask: React.FC = ({ - options, +export const CardFieldsMask: React.FC = ({ enableVaulting, }) => { - const { handleCreateOrder } = usePayment(); + const { handleCreateOrder, handleOnApprove } = usePayment(); const { settings, paymentTokens } = useSettings(); - const { clientToken } = usePayment(); + const { notify } = useNotifications(); + const { isLoading } = useLoader(); + const [addNew, setAddNew] = useState(false); const [vaultId, setVaultId] = useState(); + const [paying, setPaying] = useState(false); + const nameField = useRef(null); const numberField = useRef(null); const cvvField = useRef(null); const expiryField = useRef(null); + const save = useRef(null); + + const threeDSAuth = settings?.threeDSOption; let saveCard = false; @@ -45,46 +41,73 @@ export const CardFieldsMask: React.FC = ({ return { hostedFieldsPayButtonClasses, hostedFieldsInputFieldClasses }; }, [settings]); + const approveTransaction = (approveData: CustomOnApproveData) => { + handleOnApprove(approveData).catch((err) => { + console.log("ERROR 1"); + setPaying(false); + isLoading(false); + notify("Error", err.message); + }); + }; + const cardField = useMemo(() => { // @ts-ignore return window.paypal!.CardFields({ - createOrder: function (data: Record) { - return fetch("myserver.com/api/orders", { - method: "post", - - body: { - // @ts-ignore - paymentSource: data.paymentSource, - }, - }) - .then((res) => { - return res.json(); - }) - - .then((orderData) => { - return orderData.id; - }); + createOrder: () => { + return handleCreateOrder({ + paymentSource: "card", + storeInVault: saveCard, + verificationMethod: threeDSAuth || undefined, + }); }, - onApprove: function (data: Record) { - const { orderID } = data; - - return fetch(`myserver.com/api/orders/${orderID}/capture`, { - method: "post", - }) - .then((res) => { - return res.json(); - }) - - .then((orderData) => { - // Redirect to success page - }); + onApprove: (data: CustomOnApproveData) => { + console.log(data); + const approveData: CustomOnApproveData = { + orderID: data.orderID, + saveCard: save.current?.checked, + }; + + if (threeDSAuth) { + if (data.liabilityShift === "POSSIBLE") { + console.log("approve from click func"); + approveTransaction(approveData); + } else { + notify("Error", "3D Secure check has failed"); + console.log("ERROR 2"); + isLoading(false); + setPaying(false); + } + } else { + console.log("approve from click func"); + approveTransaction(approveData); + } }, - onError: function (error: Record) { - // Do something with the error from the SDK + onError: function (error: Record) { + setPaying(false); + isLoading(false); + notify("Error", error.message); + console.log("ERROR 4"); }, }); }, []); + const handleClick = () => { + setPaying(true); + isLoading(true); + + cardField + .submit() + .then(() => { + console.log("success"); + }) + .catch((err: Record) => { + notify("Error", err.message); + console.log("ERROR 3"); + isLoading(false); + setPaying(false); + }); + }; + useEffect(() => { if (!settings) return; if (cardField && cardField.isEligible()) { @@ -102,9 +125,54 @@ export const CardFieldsMask: React.FC = ({ } }, [cardField, settings]); - return !settings ? ( - <> - ) : ( + if (!settings) return <>; + + if (cardPaymentTokens && cardPaymentTokens?.length > 0 && !addNew) { + return ( + <> + {cardPaymentTokens.map((paymentToken) => { + const { id, payment_source } = paymentToken; + const { brand, last_digits } = payment_source.card; + + return ( +
+ + { + setVaultId(e.target.value); + }} + /> + {brand} ending in {last_digits} + +
+ ); + })} + {vaultId && ( +
+ +
+ )} + + + + ); + } + + return (
@@ -114,131 +182,29 @@ export const CardFieldsMask: React.FC = ({
- -
- /* - {cardPaymentTokens && - cardPaymentTokens?.length > 0 && - addNew === false ? ( - <> - {cardPaymentTokens.map((paymentToken) => { - const { id, payment_source } = paymentToken; - const { brand, last_digits } = payment_source.card; - - return ( -
- - { - setVaultId(e.target.value); - }} - /> - {brand} ending in {last_digits} - -
- ); - })} - {vaultId && ( -
- -
- )} - - - - ) : ( - { - return handleCreateOrder({ - paymentSource: "card", - storeInVault: saveCard, - }); - }} - notEligibleError={

hosted fields not available

} - > - - - - - - - { + {enableVaulting && ( +
+ Save this card for future purchases + )} -
*/ + + + ); }; diff --git a/src/components/CardFields/SubmitPayment.tsx b/src/components/CardFields/SubmitPayment.tsx deleted file mode 100644 index 39e644b..0000000 --- a/src/components/CardFields/SubmitPayment.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { ChangeEvent, useRef, useState, useMemo } from "react"; - -import { usePayPalHostedFields } from "@paypal/react-paypal-js"; -import { usePayment } from "../../app/usePayment"; -import { useSettings } from "../../app/useSettings"; -import { CustomOnApproveData } from "../../types"; -import { useNotifications } from "../../app/useNotifications"; -import { useLoader } from "../../app/useLoader"; -import { CARD_FIELDS_INPUTS, CARD_FIELDS_BUTTON } from "./constants"; - -type SubmitPaymentProps = { - enableVaulting?: boolean; - handleSaveCard: (event: ChangeEvent) => void; -}; - -export const SubmitPayment: React.FC = ({ - enableVaulting, - handleSaveCard, -}) => { - const customStyle = { - border: "1px solid #606060", - boxShadow: "2px 2px 10px 2px rgba(0,0,0,0.1)", - }; - const { handleOnApprove } = usePayment(); - const { settings } = useSettings(); - const { notify } = useNotifications(); - const { isLoading } = useLoader(); - const [paying, setPaying] = useState(false); - const cardHolderName = useRef(null); - - const save = useRef(null); - const hostedField = usePayPalHostedFields(); - const threeDSAuth = settings?.threeDSOption; - const hostedFieldClasses = useMemo(() => { - const hostedFieldsPayButtonClasses = - settings?.hostedFieldsPayButtonClasses || CARD_FIELDS_BUTTON; - const hostedFieldsInputFieldClasses = - settings?.hostedFieldsInputFieldClasses || CARD_FIELDS_INPUTS; - return { hostedFieldsPayButtonClasses, hostedFieldsInputFieldClasses }; - }, [settings]); - - const approveTransaction = (approveData: CustomOnApproveData) => { - handleOnApprove(approveData).catch((err) => { - setPaying(false); - isLoading(false); - notify("Error", err.message); - }); - }; - - const handleClick = () => { - if (!hostedField?.cardFields) { - const childErrorMessage = - "Unable to find any child components in the "; - - notify("Error", childErrorMessage); - throw new Error(childErrorMessage); - } - const isFormInvalid = - Object.values(hostedField.cardFields.getState().fields).some( - (field) => !field.isValid - ) || !cardHolderName?.current?.value; - - if (isFormInvalid) { - notify("Error", "The form is invalid"); - return; - } - setPaying(true); - isLoading(true); - - const hostedFieldsOptions: Record = { - cardholderName: cardHolderName?.current?.value, - }; - if (threeDSAuth) { - hostedFieldsOptions.contingencies = [threeDSAuth]; - } - hostedField.cardFields - .submit(hostedFieldsOptions) - .then((data) => { - const approveData: CustomOnApproveData = { - orderID: data.orderId, - saveCard: save.current?.checked, - }; - - if (threeDSAuth) { - if (data.liabilityShift === "POSSIBLE") { - approveTransaction(approveData); - } else { - notify("Error", "3D Secure check has failed"); - isLoading(false); - setPaying(false); - } - } else { - approveTransaction(approveData); - } - }) - .catch((err) => { - notify("Error", err.message); - isLoading(false); - setPaying(false); - }); - }; - - return ( - <> - - {enableVaulting && ( - - )} - - - - ); -}; diff --git a/src/types/index.ts b/src/types/index.ts index a0a7ab3..b8a18c4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -29,6 +29,7 @@ export type CreateOrderData = { paymentSource?: FUNDING_SOURCE; storeInVault?: boolean; vaultId?: string; + verificationMethod?: ThreeDSVerification; }; export type CreateOrderResponse = { @@ -89,8 +90,10 @@ export type GeneralComponentsProps = { enableVaulting?: boolean; } & CartInformationProps; +type ThreeDSVerification = "SCA_ALWAYS" | "SCA_WHEN_REQUIRED"; + export type HostedFieldsThreeDSAuth = { - threeDSAuth?: "SCA_ALWAYS" | "SCA_WHEN_REQUIRED"; + threeDSAuth?: ThreeDSVerification; }; export type HostedFieldsProps = { @@ -98,6 +101,10 @@ export type HostedFieldsProps = { enableVaulting?: boolean; }; +export type CardFieldsProps = { + enableVaulting?: boolean; +}; + export type HostedFieldsSmartComponentProps = SmartComponentsProps & HostedFieldsThreeDSAuth; @@ -335,6 +342,7 @@ export type CustomOnApproveData = { subscriptionID?: string | null; authCode?: string | null; saveCard?: boolean; + liabilityShift?: string; }; export type SettingsProviderProps = { From 96d72c24fd10a4304350d7a7eaecee30cc5d1a89 Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Thu, 23 Nov 2023 15:15:39 +0100 Subject: [PATCH 03/18] #35153 authenticate 3DS --- src/App.tsx | 5 +-- src/app/usePayment.tsx | 32 +++++++++++++++++ src/components/CardFields/CardFields.tsx | 2 ++ src/components/CardFields/CardFieldsMask.tsx | 10 ++++-- src/components/CardFields/constants.tsx | 29 +++++++++++++++ .../RenderTemplate/RenderTemplate.tsx | 2 ++ src/services/authenticateThreeDSOrder.ts | 36 +++++++++++++++++++ src/services/index.ts | 1 + src/types/index.ts | 2 ++ storybook/stories/constants.jsx | 4 +-- 10 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 src/services/authenticateThreeDSOrder.ts diff --git a/src/App.tsx b/src/App.tsx index a21b844..89318ec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import { CardFields } from "./components/CardFields"; const CC_FRONTEND_EXTENSION_VERSION: string = "devjonathanyeboah"; const FRONTASTIC_SESSION: string = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIyOGVhMjMxYS00Mzg3LTQwMTctYWQ3Yi03YTMxMWFiMTdhNzkifQ.oq6K9l8oZZbGy2JAyCFb4TPtfiy14k4K9Q8Kz7JSgxM"; + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIyZWIzMjQwZC1jM2FmLTRlMmUtYTIwOC0wM2E4YWJhZjhlMjcifQ.PU-DvJWqU_Cy1iJCOf1hVrklzDmVuvWPACkSAh6v64c"; function App() { const [choosenPaymentMethod, setChoosenPaymentMethod] = useState(""); @@ -51,8 +51,9 @@ function App() { const params = { createPaymentUrl: `${ENDPOINT_URL}/payment/createPayment`, + authenticateThreeDSOrderUrl: `${ENDPOINT_URL}/payment/authenticateThreeDSOrder`, getSettingsUrl: `${ENDPOINT_URL}/settings/getPayPalSettings`, - getClientTokenUrl: `${ENDPOINT_URL}/payment/getClientToken`, + /*getClientTokenUrl: `${ENDPOINT_URL}/payment/getClientToken`,*/ createOrderUrl: `${ENDPOINT_URL}/payment/createPayPalOrder`, authorizeOrderUrl: `${ENDPOINT_URL}/payment/authorizePayPalOrder`, onApproveUrl: `${ENDPOINT_URL}/payment/capturePayPalOrder`, diff --git a/src/app/usePayment.tsx b/src/app/usePayment.tsx index 9b58612..d27e67c 100644 --- a/src/app/usePayment.tsx +++ b/src/app/usePayment.tsx @@ -27,12 +27,14 @@ import { onApprove, createVaultSetupToken, approveVaultSetupToken, + authenticateThreeDSOrder, } from "../services"; import { useLoader } from "./useLoader"; import { useNotifications } from "./useNotifications"; import { useSettings } from "./useSettings"; import { getClientToken } from "../services/getClientToken"; +import { getActionIndex } from "../components/CardFields/constants"; const PaymentInfoInitialObject = { version: 0, @@ -62,6 +64,7 @@ type PaymentContextT = { handleApproveVaultSetupToken: ( data: ApproveVaultSetupTokenData ) => Promise; + handleAuthenticateThreeDSOrder: (orderID: string) => Promise; }; const PaymentContext = createContext({ @@ -77,6 +80,7 @@ const PaymentContext = createContext({ Promise.resolve(""), handleApproveVaultSetupToken: (data?: ApproveVaultSetupTokenData) => Promise.resolve(), + handleAuthenticateThreeDSOrder: (orderID: string) => Promise.resolve(0), }); export const PaymentProvider: FC< @@ -89,6 +93,7 @@ export const PaymentProvider: FC< createOrderUrl, onApproveUrl, authorizeOrderUrl, + authenticateThreeDSOrderUrl, createVaultSetupTokenUrl, approveVaultSetupTokenUrl, @@ -277,6 +282,32 @@ export const PaymentProvider: FC< let vaultOnly: boolean = createVaultSetupTokenUrl && approveVaultSetupTokenUrl ? true : false; + const handleAuthenticateThreeDSOrder = async ( + orderID: string + ): Promise => { + if (!authenticateThreeDSOrderUrl) { + return 0; + } + const result = await authenticateThreeDSOrder( + requestHeader, + authenticateThreeDSOrderUrl, + orderID, + latestPaymentVersion, + paymentInfo.id + ); + + if (!result) { + return 0; + } + + const action = getActionIndex( + result.approve.three_d_secure.enrollment_status, + result.approve.three_d_secure.authentication_status, + result.approve.liability_shift + ); + return settings?.threeDSAction[action]; + }; + return { setSuccess, requestHeader, @@ -288,6 +319,7 @@ export const PaymentProvider: FC< vaultOnly, handleCreateVaultSetupToken, handleApproveVaultSetupToken, + handleAuthenticateThreeDSOrder, }; }, [ paymentInfo, diff --git a/src/components/CardFields/CardFields.tsx b/src/components/CardFields/CardFields.tsx index 11527d4..76b64f6 100644 --- a/src/components/CardFields/CardFields.tsx +++ b/src/components/CardFields/CardFields.tsx @@ -18,6 +18,7 @@ export const CardFields: React.FC = ({ onApproveUrl, purchaseCallback, enableVaulting, + authenticateThreeDSOrderUrl, }) => { return ( = ({ purchaseCallback={purchaseCallback} enableVaulting={enableVaulting} getUserInfoUrl={getUserInfoUrl} + authenticateThreeDSOrderUrl={authenticateThreeDSOrderUrl} > diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index d5700d0..be30198 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -10,7 +10,8 @@ import { useLoader } from "../../app/useLoader"; export const CardFieldsMask: React.FC = ({ enableVaulting, }) => { - const { handleCreateOrder, handleOnApprove } = usePayment(); + const { handleCreateOrder, handleOnApprove, handleAuthenticateThreeDSOrder } = + usePayment(); const { settings, paymentTokens } = useSettings(); const { notify } = useNotifications(); const { isLoading } = useLoader(); @@ -68,7 +69,10 @@ export const CardFieldsMask: React.FC = ({ }; if (threeDSAuth) { - if (data.liabilityShift === "POSSIBLE") { + handleAuthenticateThreeDSOrder(data.orderID).then((result) => { + console.log(result); + }); + /*if (data.liabilityShift === "POSSIBLE") { console.log("approve from click func"); approveTransaction(approveData); } else { @@ -76,7 +80,7 @@ export const CardFieldsMask: React.FC = ({ console.log("ERROR 2"); isLoading(false); setPaying(false); - } + }*/ } else { console.log("approve from click func"); approveTransaction(approveData); diff --git a/src/components/CardFields/constants.tsx b/src/components/CardFields/constants.tsx index 3ef22de..32733ba 100644 --- a/src/components/CardFields/constants.tsx +++ b/src/components/CardFields/constants.tsx @@ -3,3 +3,32 @@ export const CARD_FIELDS_INPUTS: string = export const CARD_FIELDS_BUTTON: string = "float-right text-center whitespace-nowrap inline-block font-normal align-middle select-none cursor-pointer text-white text-base rounded py-1.5 px-3 bg-sky-500 border-sky-500"; + +const ACTIONS: Record = { + YYPOSSIBLE: "threeDSAction_1", + YYYES: "threeDSAction_2", + YNNO: "threeDSAction_3", + YRNO: "threeDSAction_4", + YAPOSSIBLE: "threeDSAction_5", + YUUNKNOWN: "threeDSAction_6", + YUNO: "threeDSAction_7", + YCUNKNOWN: "threeDSAction_8", + YNO: "threeDSAction_9", + NNO: "threeDSAction_10", + UNO: "threeDSAction_11", + UUNKNOWN: "threeDSAction_12", + BNO: "threeDSAction_13", + UNKNOWN: "threeDSAction_14", +}; + +export const getActionIndex = ( + enrollmentStatus: string, + authenticationStatus: string, + liabilityShift: string +): string => { + const selector = enrollmentStatus.concat( + authenticationStatus, + liabilityShift + ); + return ACTIONS[selector]; +}; diff --git a/src/components/RenderTemplate/RenderTemplate.tsx b/src/components/RenderTemplate/RenderTemplate.tsx index 900d598..2bbea06 100644 --- a/src/components/RenderTemplate/RenderTemplate.tsx +++ b/src/components/RenderTemplate/RenderTemplate.tsx @@ -23,6 +23,7 @@ export const RenderTemplate: FC< authorizeOrderUrl, getUserInfoUrl, removePaymentTokenUrl, + authenticateThreeDSOrderUrl, createVaultSetupTokenUrl, approveVaultSetupTokenUrl, @@ -58,6 +59,7 @@ export const RenderTemplate: FC< enableVaulting={enableVaulting} createVaultSetupTokenUrl={createVaultSetupTokenUrl} approveVaultSetupTokenUrl={approveVaultSetupTokenUrl} + authenticateThreeDSOrderUrl={authenticateThreeDSOrderUrl} > {children} diff --git a/src/services/authenticateThreeDSOrder.ts b/src/services/authenticateThreeDSOrder.ts new file mode 100644 index 0000000..bf6d298 --- /dev/null +++ b/src/services/authenticateThreeDSOrder.ts @@ -0,0 +1,36 @@ +import { makeRequest } from "../api"; + +import { RequestHeader } from "../types"; + +export const authenticateThreeDSOrder = async ( + requestHeader: RequestHeader, + url: string, + orderID: string, + paymentVersion: number, + paymentId: string +) => { + try { + const data: Record = { + orderID, + paymentVersion, + paymentId, + }; + + return await makeRequest< + { + version: number; + approve: { + liability_shift: string; + three_d_secure: { + enrollment_status: string; + authentication_status: string; + }; + }; + }, + Record + >(requestHeader, url, "POST", data); + } catch (error) { + console.warn(error); + return false; + } +}; diff --git a/src/services/index.ts b/src/services/index.ts index afc6391..78a049f 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -6,3 +6,4 @@ export { getUserInfo } from "./getUserInfo"; export { removePaymentToken } from "./removePaymentToken"; export { createVaultSetupToken } from "./createVaultSetupToken"; export { approveVaultSetupToken } from "./approveVaultSetupToken"; +export { authenticateThreeDSOrder } from "./authenticateThreeDSOrder"; diff --git a/src/types/index.ts b/src/types/index.ts index b8a18c4..a406298 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -80,6 +80,7 @@ export type GeneralComponentsProps = { authorizeOrderUrl?: string; getUserInfoUrl?: string; removePaymentTokenUrl?: string; + authenticateThreeDSOrderUrl?: string; createVaultSetupTokenUrl?: string; approveVaultSetupTokenUrl?: string; @@ -331,6 +332,7 @@ export type GetSettingsResponse = { }; hostedFieldsPayButtonClasses: string; hostedFieldsInputFieldClasses: string; + threeDSAction: Record; }; export type CustomOnApproveData = { diff --git a/storybook/stories/constants.jsx b/storybook/stories/constants.jsx index 658a264..9f8aa51 100644 --- a/storybook/stories/constants.jsx +++ b/storybook/stories/constants.jsx @@ -30,8 +30,8 @@ export const options = { export const requestHeader = { "Frontastic-Session": - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ3aXNobGlzdElkIjoiOWIzZGY0M2UtNjc4MC00Yzk1LWJkNjktYmIyYWZhNDMxOGE4IiwiY2FydElkIjoiNWQ4Y2YyZDgtYzAwMC00MDI4LWI0NTUtNDcyNDIwNTJlMzVhIn0.dfUq-ZvWNEUaJny40BJbXv9fK25CK90sYQCqHkXX3sw", - "Commercetools-Frontend-Extension-Version": "devmajidabbasi", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIyOGVhMjMxYS00Mzg3LTQwMTctYWQ3Yi03YTMxMWFiMTdhNzkifQ.oq6K9l8oZZbGy2JAyCFb4TPtfiy14k4K9Q8Kz7JSgxM", + "Commercetools-Frontend-Extension-Version": "devjonathanyeboah", }; const baseUrl = "https://poc-mediaopt2.frontastic.rocks/frontastic/action/"; From d5ba5cac98b33d3c269843c6df8c898740c1c0bf Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Mon, 27 Nov 2023 10:02:30 +0100 Subject: [PATCH 04/18] #35153 fix for vaulting and 3ds check --- src/App.tsx | 5 ++- src/app/usePayment.tsx | 2 + src/components/CardFields/CardFieldsMask.tsx | 46 +++++++++----------- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 89318ec..8924542 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import { CardFields } from "./components/CardFields"; const CC_FRONTEND_EXTENSION_VERSION: string = "devjonathanyeboah"; const FRONTASTIC_SESSION: string = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIyZWIzMjQwZC1jM2FmLTRlMmUtYTIwOC0wM2E4YWJhZjhlMjcifQ.PU-DvJWqU_Cy1iJCOf1hVrklzDmVuvWPACkSAh6v64c"; + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NvdW50Ijp7ImFjY291bnRJZCI6ImUyYzc3MzgxLTkyMzgtNGYzOS04NmM5LTRlMGFlNmE2M2JkNCIsImVtYWlsIjoiam9uYXRoYW4ueWVib2FoQG1lZGlhb3B0LmRlIiwiY29uZmlybWVkIjp0cnVlLCJhZGRyZXNzZXMiOltdfSwiY2FydElkIjoiYzc1OTI1MGMtNjQ1OS00MWNkLTlmNGYtNWE2ZmU2YjlhODY4Iiwid2lzaGxpc3RJZCI6IjIwYWEzYjhhLTc5MmUtNDNkYy1hMzFjLWRlMDA3NmZjZjVlNyJ9.p9vw9C6c8I_1rFgLMzcGrvxYs0skTYFgWy1JJ2WEKMk"; function App() { const [choosenPaymentMethod, setChoosenPaymentMethod] = useState(""); @@ -218,8 +218,9 @@ function App() { options={{ ...options, components: "card-fields,buttons", - vault: false, + vault: true, }} + {...vaultParams} /> ), HostedFieldsVault: ( diff --git a/src/app/usePayment.tsx b/src/app/usePayment.tsx index d27e67c..11b0f92 100644 --- a/src/app/usePayment.tsx +++ b/src/app/usePayment.tsx @@ -300,6 +300,8 @@ export const PaymentProvider: FC< return 0; } + latestPaymentVersion = result.version; + const action = getActionIndex( result.approve.three_d_secure.enrollment_status, result.approve.three_d_secure.authentication_status, diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index be30198..3b3bed7 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -44,7 +44,6 @@ export const CardFieldsMask: React.FC = ({ const approveTransaction = (approveData: CustomOnApproveData) => { handleOnApprove(approveData).catch((err) => { - console.log("ERROR 1"); setPaying(false); isLoading(false); notify("Error", err.message); @@ -62,7 +61,6 @@ export const CardFieldsMask: React.FC = ({ }); }, onApprove: (data: CustomOnApproveData) => { - console.log(data); const approveData: CustomOnApproveData = { orderID: data.orderID, saveCard: save.current?.checked, @@ -70,19 +68,24 @@ export const CardFieldsMask: React.FC = ({ if (threeDSAuth) { handleAuthenticateThreeDSOrder(data.orderID).then((result) => { - console.log(result); + switch (result.toString(10)) { + case "2": + approveTransaction(approveData); + break; + case "1": + notify("Warning", "Try again"); + isLoading(false); + setPaying(false); + break; + case "0": + default: + notify("Error", "Please select different payment method"); + isLoading(false); + setPaying(false); + break; + } }); - /*if (data.liabilityShift === "POSSIBLE") { - console.log("approve from click func"); - approveTransaction(approveData); - } else { - notify("Error", "3D Secure check has failed"); - console.log("ERROR 2"); - isLoading(false); - setPaying(false); - }*/ } else { - console.log("approve from click func"); approveTransaction(approveData); } }, @@ -90,7 +93,6 @@ export const CardFieldsMask: React.FC = ({ setPaying(false); isLoading(false); notify("Error", error.message); - console.log("ERROR 4"); }, }); }, []); @@ -99,17 +101,11 @@ export const CardFieldsMask: React.FC = ({ setPaying(true); isLoading(true); - cardField - .submit() - .then(() => { - console.log("success"); - }) - .catch((err: Record) => { - notify("Error", err.message); - console.log("ERROR 3"); - isLoading(false); - setPaying(false); - }); + cardField.submit().catch((err: Record) => { + notify("Error", err.message); + isLoading(false); + setPaying(false); + }); }; useEffect(() => { From e05e17bdcc1dd5eced0e95e261da23e2c82bfd29 Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:42:54 +0100 Subject: [PATCH 05/18] #35153 combined vaulted cards and new card mask --- src/App.tsx | 2 +- src/components/CardFields/CardFieldsMask.tsx | 79 ++++++++++---------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8924542..8098f32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import { CardFields } from "./components/CardFields"; const CC_FRONTEND_EXTENSION_VERSION: string = "devjonathanyeboah"; const FRONTASTIC_SESSION: string = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NvdW50Ijp7ImFjY291bnRJZCI6ImUyYzc3MzgxLTkyMzgtNGYzOS04NmM5LTRlMGFlNmE2M2JkNCIsImVtYWlsIjoiam9uYXRoYW4ueWVib2FoQG1lZGlhb3B0LmRlIiwiY29uZmlybWVkIjp0cnVlLCJhZGRyZXNzZXMiOltdfSwiY2FydElkIjoiYzc1OTI1MGMtNjQ1OS00MWNkLTlmNGYtNWE2ZmU2YjlhODY4Iiwid2lzaGxpc3RJZCI6IjIwYWEzYjhhLTc5MmUtNDNkYy1hMzFjLWRlMDA3NmZjZjVlNyJ9.p9vw9C6c8I_1rFgLMzcGrvxYs0skTYFgWy1JJ2WEKMk"; + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIxMTM1NTVlZi1jOGVlLTRhMWItYTFiMi01NjI4ZjYwODNkOTYiLCJhY2NvdW50Ijp7ImFjY291bnRJZCI6Ijk2ZmM1YzE2LTg5OWEtNGYwNS1hMWU5LTkyMjY3NmY3MDU1MCIsImVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJmaXJzdE5hbWUiOiJKb2huIiwibGFzdE5hbWUiOiJEb2UiLCJiaXJ0aGRheSI6IjE5NDMtMTItMThUMDA6MDA6MDAuMDAwWiIsImNvbmZpcm1lZCI6dHJ1ZSwiYWRkcmVzc2VzIjpbeyJhZGRyZXNzSWQiOiI0MXU5VlozeCIsImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSIsInN0cmVldE5hbWUiOiJTZWNvbmQgU3RyZWV0Iiwic3RyZWV0TnVtYmVyIjoiMTMiLCJwb3N0YWxDb2RlIjoiMTIzNDUiLCJjaXR5IjoiRXhhbXBsZSBDaXR5IiwiY291bnRyeSI6Ik5MIiwicGhvbmUiOiIrMzExMjM0NTY3ODAiLCJpc0RlZmF1bHRCaWxsaW5nQWRkcmVzcyI6ZmFsc2UsImlzRGVmYXVsdFNoaXBwaW5nQWRkcmVzcyI6ZmFsc2V9XX0sIndpc2hsaXN0SWQiOiJlNzFhYTEyNC1kZGZmLTQxYzktYmYxOC0wZDk2ZjNhODI0Y2QifQ.18fv-aNJc-Ft4BRPoF5ReFYGjg576pVOkTUqbDVe1Tw"; function App() { const [choosenPaymentMethod, setChoosenPaymentMethod] = useState(""); diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index 3b3bed7..8a86649 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -16,7 +16,6 @@ export const CardFieldsMask: React.FC = ({ const { notify } = useNotifications(); const { isLoading } = useLoader(); - const [addNew, setAddNew] = useState(false); const [vaultId, setVaultId] = useState(); const [paying, setPaying] = useState(false); @@ -127,8 +126,8 @@ export const CardFieldsMask: React.FC = ({ if (!settings) return <>; - if (cardPaymentTokens && cardPaymentTokens?.length > 0 && !addNew) { - return ( + let cardPaymentTokensElement = + cardPaymentTokens && cardPaymentTokens?.length > 0 ? ( <> {cardPaymentTokens.map((paymentToken) => { const { id, payment_source } = paymentToken; @@ -166,45 +165,47 @@ export const CardFieldsMask: React.FC = ({ )} - - + ) : ( + <> ); - } return ( -
-
- -
- -
- -
- - {enableVaulting && ( - - )} - - -
+ <> + {cardPaymentTokensElement} +
+
+ +
+ +
+ +
+ + {enableVaulting && ( + + )} + + +
+ ); }; From df5f3d59a8028f626cd2ce3c86df3d8411b750af Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:27:45 +0100 Subject: [PATCH 06/18] #35153 vault only --- src/App.tsx | 26 ++++++++--- src/components/CardFields/CardFields.tsx | 4 ++ .../CardFields/CardFieldsButton.tsx | 4 +- src/components/CardFields/CardFieldsMask.tsx | 43 ++++++++++++++----- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8098f32..ab8140f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,7 @@ import { CardFields } from "./components/CardFields"; const CC_FRONTEND_EXTENSION_VERSION: string = "devjonathanyeboah"; const FRONTASTIC_SESSION: string = - "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiIxMTM1NTVlZi1jOGVlLTRhMWItYTFiMi01NjI4ZjYwODNkOTYiLCJhY2NvdW50Ijp7ImFjY291bnRJZCI6Ijk2ZmM1YzE2LTg5OWEtNGYwNS1hMWU5LTkyMjY3NmY3MDU1MCIsImVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJmaXJzdE5hbWUiOiJKb2huIiwibGFzdE5hbWUiOiJEb2UiLCJiaXJ0aGRheSI6IjE5NDMtMTItMThUMDA6MDA6MDAuMDAwWiIsImNvbmZpcm1lZCI6dHJ1ZSwiYWRkcmVzc2VzIjpbeyJhZGRyZXNzSWQiOiI0MXU5VlozeCIsImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSIsInN0cmVldE5hbWUiOiJTZWNvbmQgU3RyZWV0Iiwic3RyZWV0TnVtYmVyIjoiMTMiLCJwb3N0YWxDb2RlIjoiMTIzNDUiLCJjaXR5IjoiRXhhbXBsZSBDaXR5IiwiY291bnRyeSI6Ik5MIiwicGhvbmUiOiIrMzExMjM0NTY3ODAiLCJpc0RlZmF1bHRCaWxsaW5nQWRkcmVzcyI6ZmFsc2UsImlzRGVmYXVsdFNoaXBwaW5nQWRkcmVzcyI6ZmFsc2V9XX0sIndpc2hsaXN0SWQiOiJlNzFhYTEyNC1kZGZmLTQxYzktYmYxOC0wZDk2ZjNhODI0Y2QifQ.18fv-aNJc-Ft4BRPoF5ReFYGjg576pVOkTUqbDVe1Tw"; + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYXJ0SWQiOiI3OGMxODdmZC03OWU4LTQ3ZmMtYjJhOS1jZTI1NTYyNTVjNTYiLCJhY2NvdW50Ijp7ImFjY291bnRJZCI6Ijk2ZmM1YzE2LTg5OWEtNGYwNS1hMWU5LTkyMjY3NmY3MDU1MCIsImVtYWlsIjoiam9obi5kb2VAZXhhbXBsZS5jb20iLCJmaXJzdE5hbWUiOiJKb2huIiwibGFzdE5hbWUiOiJEb2UiLCJiaXJ0aGRheSI6IjE5NDMtMTItMThUMDA6MDA6MDAuMDAwWiIsImNvbmZpcm1lZCI6dHJ1ZSwiYWRkcmVzc2VzIjpbeyJhZGRyZXNzSWQiOiI0MXU5VlozeCIsImZpcnN0TmFtZSI6IkpvaG4iLCJsYXN0TmFtZSI6IkRvZSIsInN0cmVldE5hbWUiOiJTZWNvbmQgU3RyZWV0Iiwic3RyZWV0TnVtYmVyIjoiMTMiLCJwb3N0YWxDb2RlIjoiMTIzNDUiLCJjaXR5IjoiRXhhbXBsZSBDaXR5IiwiY291bnRyeSI6Ik5MIiwicGhvbmUiOiIrMzExMjM0NTY3ODAiLCJpc0RlZmF1bHRCaWxsaW5nQWRkcmVzcyI6ZmFsc2UsImlzRGVmYXVsdFNoaXBwaW5nQWRkcmVzcyI6ZmFsc2V9XX0sIndpc2hsaXN0SWQiOiJlNzFhYTEyNC1kZGZmLTQxYzktYmYxOC0wZDk2ZjNhODI0Y2QifQ.ZIYl_YDppcyf27xmCqAF8yUf2wrbmr46nbqw1qmX9Qo"; function App() { const [choosenPaymentMethod, setChoosenPaymentMethod] = useState(""); @@ -200,27 +200,39 @@ function App() { options={{ ...options, components: "messages" }} /> ), - HostedFields: ( - ), - CardFields: ( + CardFieldsVaultOnly: ( + ), + HostedFields: ( + ), HostedFieldsVault: ( diff --git a/src/components/CardFields/CardFields.tsx b/src/components/CardFields/CardFields.tsx index 76b64f6..80a0650 100644 --- a/src/components/CardFields/CardFields.tsx +++ b/src/components/CardFields/CardFields.tsx @@ -19,6 +19,8 @@ export const CardFields: React.FC = ({ purchaseCallback, enableVaulting, authenticateThreeDSOrderUrl, + createVaultSetupTokenUrl, + approveVaultSetupTokenUrl, }) => { return ( = ({ enableVaulting={enableVaulting} getUserInfoUrl={getUserInfoUrl} authenticateThreeDSOrderUrl={authenticateThreeDSOrderUrl} + createVaultSetupTokenUrl={createVaultSetupTokenUrl} + approveVaultSetupTokenUrl={approveVaultSetupTokenUrl} > diff --git a/src/components/CardFields/CardFieldsButton.tsx b/src/components/CardFields/CardFieldsButton.tsx index 6a9a3bc..4119c6c 100644 --- a/src/components/CardFields/CardFieldsButton.tsx +++ b/src/components/CardFields/CardFieldsButton.tsx @@ -8,10 +8,10 @@ import { useHandleCreatePayment } from "../../app/useHandleCreatePayment"; export const CardFieldsButton: React.FC = ({ enableVaulting, }) => { - const { paymentInfo } = usePayment(); + const { paymentInfo, vaultOnly } = usePayment(); useHandleCreatePayment(); - return paymentInfo.id && window.paypal ? ( + return (paymentInfo.id || vaultOnly) && window.paypal ? ( ) : ( <> diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index 8a86649..708de68 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -2,7 +2,11 @@ import React, { useEffect, useMemo, useRef, useState } from "react"; import { usePayment } from "../../app/usePayment"; import { useSettings } from "../../app/useSettings"; -import { CardFieldsProps, CustomOnApproveData } from "../../types"; +import { + ApproveVaultSetupTokenData, + CardFieldsProps, + CustomOnApproveData, +} from "../../types"; import { CARD_FIELDS_INPUTS, CARD_FIELDS_BUTTON } from "./constants"; import { useNotifications } from "../../app/useNotifications"; import { useLoader } from "../../app/useLoader"; @@ -10,8 +14,14 @@ import { useLoader } from "../../app/useLoader"; export const CardFieldsMask: React.FC = ({ enableVaulting, }) => { - const { handleCreateOrder, handleOnApprove, handleAuthenticateThreeDSOrder } = - usePayment(); + const { + handleCreateOrder, + handleOnApprove, + handleAuthenticateThreeDSOrder, + vaultOnly, + handleApproveVaultSetupToken, + handleCreateVaultSetupToken, + } = usePayment(); const { settings, paymentTokens } = useSettings(); const { notify } = useNotifications(); const { isLoading } = useLoader(); @@ -41,18 +51,31 @@ export const CardFieldsMask: React.FC = ({ return { hostedFieldsPayButtonClasses, hostedFieldsInputFieldClasses }; }, [settings]); - const approveTransaction = (approveData: CustomOnApproveData) => { - handleOnApprove(approveData).catch((err) => { - setPaying(false); - isLoading(false); - notify("Error", err.message); - }); + const approveTransaction = (approveData: any) => { + if (vaultOnly) { + handleApproveVaultSetupToken( + approveData as ApproveVaultSetupTokenData + ).catch((err) => { + setPaying(false); + isLoading(false); + notify("Error", err.message); + }); + } else { + handleOnApprove(approveData as CustomOnApproveData).catch((err) => { + setPaying(false); + isLoading(false); + notify("Error", err.message); + }); + } }; const cardField = useMemo(() => { // @ts-ignore return window.paypal!.CardFields({ createOrder: () => { + if (vaultOnly) { + return handleCreateVaultSetupToken("card"); + } return handleCreateOrder({ paymentSource: "card", storeInVault: saveCard, @@ -127,7 +150,7 @@ export const CardFieldsMask: React.FC = ({ if (!settings) return <>; let cardPaymentTokensElement = - cardPaymentTokens && cardPaymentTokens?.length > 0 ? ( + !vaultOnly && cardPaymentTokens && cardPaymentTokens?.length > 0 ? ( <> {cardPaymentTokens.map((paymentToken) => { const { id, payment_source } = paymentToken; From 953c96d04b013d7d4bcb5354c84e873ecdcc4bdb Mon Sep 17 00:00:00 2001 From: Jonathan Yeboah <10155597+JonYeb@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:31:18 +0100 Subject: [PATCH 07/18] #35153 fix loading of card fields, added pure vaulting finally --- .../CardFields/CardFieldsButton.tsx | 17 +++++++++-- src/components/CardFields/CardFieldsMask.tsx | 29 ++++++++++++------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/components/CardFields/CardFieldsButton.tsx b/src/components/CardFields/CardFieldsButton.tsx index 4119c6c..7585db6 100644 --- a/src/components/CardFields/CardFieldsButton.tsx +++ b/src/components/CardFields/CardFieldsButton.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { usePayment } from "../../app/usePayment"; import { CardFieldsMask } from "./CardFieldsMask"; @@ -9,9 +9,22 @@ export const CardFieldsButton: React.FC = ({ enableVaulting, }) => { const { paymentInfo, vaultOnly } = usePayment(); + const [showComponent, setShowComponent] = useState(false); useHandleCreatePayment(); - return (paymentInfo.id || vaultOnly) && window.paypal ? ( + useEffect(() => { + let intervall = setInterval(() => { + if (!!window.paypal && (paymentInfo.id || vaultOnly)) { + clearInterval(intervall); + setShowComponent(true); + } + }, 250); + return () => { + clearInterval(intervall); + }; + }, []); + + return showComponent ? ( ) : ( <> diff --git a/src/components/CardFields/CardFieldsMask.tsx b/src/components/CardFields/CardFieldsMask.tsx index 708de68..e2d7d9a 100644 --- a/src/components/CardFields/CardFieldsMask.tsx +++ b/src/components/CardFields/CardFieldsMask.tsx @@ -68,21 +68,30 @@ export const CardFieldsMask: React.FC = ({ }); } }; + const cardFieldMethods = vaultOnly + ? { createVaultSetupToken: () => handleCreateVaultSetupToken("card") } + : { + createOrder: () => { + if (vaultOnly) { + return handleCreateVaultSetupToken("card"); + } + return handleCreateOrder({ + paymentSource: "card", + storeInVault: saveCard, + verificationMethod: threeDSAuth || undefined, + }); + }, + }; const cardField = useMemo(() => { // @ts-ignore return window.paypal!.CardFields({ - createOrder: () => { + ...cardFieldMethods, + onApprove: (data: CustomOnApproveData) => { if (vaultOnly) { - return handleCreateVaultSetupToken("card"); + approveTransaction(data); + return; } - return handleCreateOrder({ - paymentSource: "card", - storeInVault: saveCard, - verificationMethod: threeDSAuth || undefined, - }); - }, - onApprove: (data: CustomOnApproveData) => { const approveData: CustomOnApproveData = { orderID: data.orderID, saveCard: save.current?.checked, @@ -205,7 +214,7 @@ export const CardFieldsMask: React.FC = ({
- {enableVaulting && ( + {enableVaulting && !vaultOnly && (