From 06b3f3e11f7879b9aa291e9693e7530b05a0d1fd Mon Sep 17 00:00:00 2001 From: Praful Koppalkar <126236898+prafulkoppalkar@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:46:59 +0530 Subject: [PATCH] Feature/boa apple pay (#75) --- public/icons/orca.svg | 100 +++++++++---- src/LoaderController.res | 22 ++- src/PaymentElement.res | 21 ++- src/Payments/ApplePay.res | 142 ++++++++++++++----- src/Payments/ApplePay.resi | 7 +- src/Payments/ApplePayLazy.res | 2 + src/Payments/PaymentMethodsRecord.res | 25 ++-- src/Payments/PaymentRequestButtonElement.res | 3 +- src/Types/PaymentModeType.res | 2 + src/Utilities/PaymentUtils.res | 17 ++- src/Utilities/RecoilAtoms.res | 1 + src/orca-loader/Elements.res | 84 ++++++----- 12 files changed, 307 insertions(+), 119 deletions(-) diff --git a/public/icons/orca.svg b/public/icons/orca.svg index 691aeed5..1b22cfd3 100644 --- a/public/icons/orca.svg +++ b/public/icons/orca.svg @@ -1097,56 +1097,104 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL - + - + - - + + - + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - + + + - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/LoaderController.res b/src/LoaderController.res index 013ad025..2a042ea2 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -13,6 +13,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { let setBlockConfirm = Recoil.useSetRecoilState(isConfirmBlocked) let setSwitchToCustomPod = Recoil.useSetRecoilState(switchToCustomPod) let setIsGooglePayReady = Recoil.useSetRecoilState(isGooglePayReady) + let setIsApplePayReady = Recoil.useSetRecoilState(isApplePayReady) let (divH, setDivH) = React.useState(_ => 0.0) let {showCardFormByDefault, paymentMethodOrder} = optionsPayment @@ -66,7 +67,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { setConfig(._ => { config: { - appearance: appearance, + appearance, locale: config.locale, fonts: config.fonts, clientSecret: config.clientSecret, @@ -82,6 +83,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { React.useEffect0(() => { handlePostMessage([("iframeMounted", true->Js.Json.boolean)]) + handlePostMessage([("applePayMounted", true->Js.Json.boolean)]) logger.setLogInitiated() Window.addEventListener("click", ev => handleOnClickPostMessage(~targetOrigin=keys.parentURL, ev) @@ -296,9 +298,15 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { let customerPaymentMethods = dict->PaymentType.createCustomerObjArr setOptionsPayment(.prev => { ...prev, - customerPaymentMethods: customerPaymentMethods, + customerPaymentMethods, }) } + if dict->Js.Dict.get("applePayCanMakePayments")->Belt.Option.isSome { + setIsApplePayReady(._ => true) + } + if dict->Js.Dict.get("applePaySessionObjNotPresent")->Belt.Option.isSome { + setIsApplePayReady(.prev => prev && false) + } } catch { | _ => setIntegrateErrorError(_ => true) } @@ -319,10 +327,12 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger) => { ]->Js.Array2.forEach(val => { let (value, setValue) = val if value != "" { - setValue(.prev => { - ...prev, - value: value, - }) + setValue(. + prev => { + ...prev, + value, + }, + ) } }) if optionsPayment.defaultValues.billingDetails.address.country === "" { diff --git a/src/PaymentElement.res b/src/PaymentElement.res index eafeb72f..3d189bcc 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -18,6 +18,8 @@ let make = ( let {showCardFormByDefault, paymentMethodOrder, layout} = Recoil.useRecoilValueFromAtom( optionAtom, ) + let isApplePayReady = Recoil.useRecoilValueFromAtom(isApplePayReady) + let isGooglePayReady = Recoil.useRecoilValueFromAtom(isGooglePayReady) let pmList = Recoil.useRecoilValueFromAtom(list) let methodslist = Recoil.useRecoilValueFromAtom(list) let paymentOrder = paymentMethodOrder->Utils.getOptionalArr->Utils.removeDuplicate @@ -35,12 +37,17 @@ let make = ( let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom) let isShowOrPayUsing = Recoil.useRecoilValueFromAtom(isShowOrPayUsing) - let (walletList, paymentOptionsList, actualList) = React.useMemo2(() => { + let (walletList, paymentOptionsList, actualList) = React.useMemo4(() => { switch methodslist { | Loaded(paymentlist) => let paymentOrder = paymentOrder->Js.Array2.length > 0 ? paymentOrder : defaultOrder let plist = paymentlist->Utils.getDictFromJson->PaymentMethodsRecord.itemToObjMapper - let (wallets, otherOptions) = plist->PaymentUtils.paymentListLookupNew(~order=paymentOrder) + let (wallets, otherOptions) = + plist->PaymentUtils.paymentListLookupNew( + ~order=paymentOrder, + ~showApplePay=isApplePayReady, + ~showGooglePay=isGooglePayReady, + ) ( wallets->Utils.removeDuplicate, paymentOptions->Js.Array2.concat(otherOptions)->Utils.removeDuplicate, @@ -52,7 +59,7 @@ let make = ( : ([], [], []) | _ => ([], [], []) } - }, (methodslist, paymentMethodOrder)) + }, (methodslist, paymentMethodOrder, isApplePayReady, isGooglePayReady)) React.useEffect4(() => { switch methodslist { @@ -175,6 +182,8 @@ let make = ( } let dict = sessions->Utils.getDictFromJson let sessionObj = SessionsType.itemToObjMapper(dict, Others) + let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject) + let applePayToken = SessionsType.getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay) let klarnaTokenObj = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Klarna) let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) let googlePayThirdPartySessionObj = SessionsType.itemToObjMapper(dict, GooglePayThirdPartyObject) @@ -269,6 +278,12 @@ let make = ( } | _ => React.null } + | ApplePay => + switch applePayToken { + | ApplePayTokenOptional(optToken) => + + | _ => React.null + } | _ => diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 63ec66f0..1af342c7 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -1,7 +1,14 @@ +open Utils @react.component -let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => { +let make = ( + ~sessionObj: option, + ~list: PaymentMethodsRecord.list, + ~paymentType: option, + ~walletOptions: array, +) => { let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) let setIsShowOrPayUsing = Recoil.useSetRecoilState(RecoilAtoms.isShowOrPayUsing) let (showApplePay, setShowApplePay) = React.useState(() => false) let (showApplePayLoader, setShowApplePayLoader) = React.useState(() => false) @@ -10,6 +17,11 @@ let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let (applePayClicked, setApplePayClicked) = React.useState(_ => false) let isApplePaySDKFlow = sessionObj->Belt.Option.isSome + let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Js.Dict.empty()) + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let isWallet = walletOptions->Js.Array2.includes("apple_pay") let applePayPaymentMethodType = React.useMemo1(() => { switch PaymentMethodsRecord.getPaymentMethodTypeFromList( @@ -37,15 +49,34 @@ let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => : list->PaymentUtils.getConnectors(Wallets(ApplePay(Redirect))) let processPayment = bodyArr => { - intent( - ~bodyArr, - ~confirmParam={ - return_url: options.wallets.walletReturnUrl, - publishableKey, - }, - ~handleUserError=true, - (), - ) + if isWallet { + intent( + ~bodyArr, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=true, + (), + ) + } else { + let requiredFieldsBodyArr = + bodyArr + ->Js.Dict.fromArray + ->Js.Json.object_ + ->OrcaUtils.flattenObject(true) + ->OrcaUtils.mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->OrcaUtils.getArrayOfTupleFromDict + intent( + ~bodyArr=requiredFieldsBodyArr, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=true, + (), + ) + } } let syncPayment = () => { @@ -229,7 +260,6 @@ let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => } React.useEffect1(() => { - Utils.handlePostMessage([("applePayMounted", true->Js.Json.boolean)]) let handleApplePayMessages = (ev: Window.event) => { let json = try { ev.data->Js.Json.parseExn @@ -239,11 +269,7 @@ let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => try { let dict = json->Utils.getDictFromJson - if dict->Js.Dict.get("applePayCanMakePayments")->Belt.Option.isSome { - if isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL { - setShowApplePay(_ => true) - } - } else if dict->Js.Dict.get("applePayProcessPayment")->Belt.Option.isSome { + if dict->Js.Dict.get("applePayProcessPayment")->Belt.Option.isSome { let token = dict ->Js.Dict.get("applePayProcessPayment") @@ -268,26 +294,72 @@ let make = (~sessionObj: option, ~list: PaymentMethodsRecord.list) => ) }, [isInvokeSDKFlow]) -
- - {if showApplePay { - if showApplePayLoader { -
-
-
- } else { - + React.useEffect1(() => { + if ( + (isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) && + isApplePayReady + ) { + setShowApplePay(_ => true) + } + None + }, [isApplePayReady]) + + let submitCallback = React.useCallback((ev: Window.event) => { + if !isWallet { + let json = ev.data->Js.Json.parseExn + let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper + if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { + options.readOnly + ? () + : handlePostMessage([("applePayButtonClicked", true->Js.Json.boolean)]) + } else if areRequiredFieldsEmpty { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterFieldsText, + ) + } else if !areRequiredFieldsValid { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterValidDetailsText, + ) } - } else { - React.null - }} -
+ } + }) + submitPaymentData(submitCallback) + + { + isWallet + ?
+ + {if showApplePay { + if showApplePayLoader { +
+
+
+ } else { + + } + } else { + React.null + }} +
+ : val + | _ => NONE + }} + list + paymentMethod="wallet" + paymentMethodType="apple_pay" + setRequiredFieldsBody + /> + } } let default = make diff --git a/src/Payments/ApplePay.resi b/src/Payments/ApplePay.resi index 6ddcd43c..875fc52e 100644 --- a/src/Payments/ApplePay.resi +++ b/src/Payments/ApplePay.resi @@ -1,2 +1,7 @@ @react.component -let default: (~sessionObj: option, ~list: PaymentMethodsRecord.list) => React.element +let default: ( + ~sessionObj: option, + ~list: PaymentMethodsRecord.list, + ~paymentType: option, + ~walletOptions: array, +) => React.element diff --git a/src/Payments/ApplePayLazy.res b/src/Payments/ApplePayLazy.res index fdfbd7b4..df921bd0 100644 --- a/src/Payments/ApplePayLazy.res +++ b/src/Payments/ApplePayLazy.res @@ -3,6 +3,8 @@ open LazyUtils type props = { sessionObj: option, list: PaymentMethodsRecord.list, + paymentType: CardThemeType.mode, + walletOptions: array, } let make: props => React.element = reactLazy(.() => import_("./ApplePay.bs.js")) diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 84aee17c..bf172471 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -109,6 +109,13 @@ let paymentMethodsFields = [ displayName: "Google Pay", miniIcon: None, }, + { + paymentMethodName: "apple_pay", + fields: [], + icon: Some(icon("apple_pay", ~size=19, ~width=25)), + displayName: "Apple Pay", + miniIcon: None, + }, { paymentMethodName: "mb_way", fields: [SpecialField(), InfoElement], @@ -540,6 +547,7 @@ let dynamicFieldsEnabledPaymentMethods = [ "credit", "blik", "google_pay", + "apple_pay", ] let getPaymentMethodFields = ( @@ -742,7 +750,7 @@ let getSurchargeDetails = dict => { if displayTotalSurchargeAmount !== 0.0 { Some({ - displayTotalSurchargeAmount, + displayTotalSurchargeAmount: displayTotalSurchargeAmount, }) } else { None @@ -864,21 +872,22 @@ let buildFromPaymentList = (plist: list) => { paymentMethodObject.payment_method_types->Js.Array2.map(individualPaymentMethod => { let paymentMethodName = individualPaymentMethod.payment_method_type let bankNames = individualPaymentMethod.bank_names - let paymentExperience = - individualPaymentMethod.payment_experience->Js.Array2.map(experience => { + let paymentExperience = individualPaymentMethod.payment_experience->Js.Array2.map( + experience => { (experience.payment_experience_type, experience.eligible_connectors) - }) + }, + ) { - paymentMethodName: paymentMethodName, + paymentMethodName, fields: getPaymentMethodFields( paymentMethodName, individualPaymentMethod.required_fields, (), ), paymentFlow: paymentExperience, - handleUserError: handleUserError, - methodType: methodType, - bankNames: bankNames, + handleUserError, + methodType, + bankNames, } }) }) diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 70c88e9d..863ec405 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -80,7 +80,8 @@ let make = (~sessions, ~walletOptions, ~list: PaymentMethodsRecord.list) => { | ApplePayWallet => switch applePayToken { - | ApplePayTokenOptional(optToken) => + | ApplePayTokenOptional(optToken) => + | _ => React.null } diff --git a/src/Types/PaymentModeType.res b/src/Types/PaymentModeType.res index 6715d011..48d0d17d 100644 --- a/src/Types/PaymentModeType.res +++ b/src/Types/PaymentModeType.res @@ -17,6 +17,7 @@ type payment = | BecsBankDebit | BanContactCard | GooglePay + | ApplePay | NONE let paymentMode = str => { @@ -39,6 +40,7 @@ let paymentMode = str => { | "bacs_transfer" => BacsTransfer | "bancontact_card" => BanContactCard | "google_pay" => GooglePay + | "apple_pay" => ApplePay | _ => NONE } } diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index b14e2acc..ad36c435 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -1,4 +1,9 @@ -let paymentListLookupNew = (list: PaymentMethodsRecord.list, ~order) => { +let paymentListLookupNew = ( + list: PaymentMethodsRecord.list, + ~order, + ~showApplePay, + ~showGooglePay, +) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] let walletToBeDisplayedInTabs = [ @@ -18,13 +23,21 @@ let paymentListLookupNew = (list: PaymentMethodsRecord.list, ~order) => { ] let otherPaymentList = [] let googlePayFields = pmList->Js.Array2.find(item => item.paymentMethodName === "google_pay") + let applePayFields = pmList->Js.Array2.find(item => item.paymentMethodName === "apple_pay") switch googlePayFields { | Some(val) => - if val.fields->Js.Array2.length > 0 { + if val.fields->Js.Array2.length > 0 && showGooglePay { walletToBeDisplayedInTabs->Js.Array2.push("google_pay")->ignore } | None => () } + switch applePayFields { + | Some(val) => + if val.fields->Js.Array2.length > 0 && showApplePay { + walletToBeDisplayedInTabs->Js.Array2.push("apple_pay")->ignore + } + | None => () + } pmList->Js.Array2.forEach(item => { if walletToBeDisplayedInTabs->Js.Array2.includes(item.paymentMethodName) { diff --git a/src/Utilities/RecoilAtoms.res b/src/Utilities/RecoilAtoms.res index 4f14856f..7018702e 100644 --- a/src/Utilities/RecoilAtoms.res +++ b/src/Utilities/RecoilAtoms.res @@ -36,6 +36,7 @@ let userPhoneNumber = Recoil.atom(. }, ) let isGooglePayReady = Recoil.atom(. "isGooglePayReady", false) +let isApplePayReady = Recoil.atom(. "isApplePayReady", false) let userCountry = Recoil.atom(. "userCountry", "") let userBank = Recoil.atom(. "userBank", "") let userAddressline1 = Recoil.atom(. "userAddressline1", defaultFieldValues) diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 81093900..1be3f769 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -341,20 +341,28 @@ let make = ( let x = item ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => { - x->Js.Dict.get("wallet_name") - }) + ->Belt.Option.flatMap( + x => { + x->Js.Dict.get("wallet_name") + }, + ) ->Belt.Option.flatMap(Js.Json.decodeString) ->Belt.Option.getWithDefault("") x === "apple_pay" || x === "applepay" }) + if !(applePayPresent->Belt.Option.isSome) { + let msg = [("applePaySessionObjNotPresent", true->Js.Json.boolean)]->Js.Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + } let googlePayPresent = sessionsArr->Js.Array2.find(item => { let x = item ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => { - x->Js.Dict.get("wallet_name") - }) + ->Belt.Option.flatMap( + x => { + x->Js.Dict.get("wallet_name") + }, + ) ->Belt.Option.flatMap(Js.Json.decodeString) ->Belt.Option.getWithDefault("") x === "google_pay" || x === "googlepay" @@ -366,7 +374,6 @@ let make = ( let (json, applePayPresent, googlePayPresent) = res if componentType === "payment" && applePayPresent->Belt.Option.isSome { //do operations here - let processPayment = (token: Js.Json.t) => { //let body = PaymentBody.applePayBody(~token) let msg = [("applePayProcessPayment", token)]->Js.Dict.fromArray @@ -378,7 +385,6 @@ let make = ( (event: Types.event) => { let json = event.data->eventToJson let dict = json->getDictFromJson - switch dict->Js.Dict.get("applePayButtonClicked") { | Some(val) => if val->Js.Json.decodeBoolean->Belt.Option.getWithDefault(false) { @@ -627,31 +633,35 @@ let make = ( if gpayClicked { Js.Global.setTimeout(() => { gPayClient.loadPaymentData(. paymentDataRequest->toJson) - ->then(json => { - logger.setLogInfo( - ~value=json->toJson->Js.Json.stringify, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - ~logType=DEBUG, - (), - ) - let msg = [("gpayResponse", json->toJson)]->Js.Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) - ->catch(err => { - logger.setLogInfo( - ~value=err->toJson->Js.Json.stringify, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - ~logType=DEBUG, - (), - ) - - let msg = [("gpayError", err->toJson)]->Js.Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) + ->then( + json => { + logger.setLogInfo( + ~value=json->toJson->Js.Json.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + let msg = [("gpayResponse", json->toJson)]->Js.Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }, + ) + ->catch( + err => { + logger.setLogInfo( + ~value=err->toJson->Js.Json.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + + let msg = [("gpayError", err->toJson)]->Js.Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }, + ) ->ignore }, 0)->ignore } @@ -686,10 +696,10 @@ let make = ( paymentElement } { - getElement: getElement, - update: update, - fetchUpdates: fetchUpdates, - create: create, + getElement, + update, + fetchUpdates, + create, } } catch { | e => {