From a7c7d191aa3df3de3293f8dade14c0448415a913 Mon Sep 17 00:00:00 2001 From: sakksham7 <130480324+sakksham7@users.noreply.github.com> Date: Thu, 21 Nov 2024 14:41:35 +0530 Subject: [PATCH] feat: added paypal tabs flow support for billing details (#792) Co-authored-by: Pritish Budhiraja --- src/PaymentElement.res | 33 +++++++- src/Payments/PayPal.res | 85 +++++++++++++++----- src/Payments/PayPal.resi | 2 +- src/Payments/PayPalHelpers.res | 31 +++++++ src/Payments/PaymentMethodsRecord.res | 7 ++ src/Payments/PaymentRequestButtonElement.res | 24 +++--- src/Types/PaymentModeType.res | 2 + src/Utilities/DynamicFieldsUtils.res | 1 + src/Utilities/PaymentUtils.res | 54 ++++++++++++- 9 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 src/Payments/PayPalHelpers.res diff --git a/src/PaymentElement.res b/src/PaymentElement.res index c4580a648..d5fd4364a 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -17,6 +17,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod layout, customerPaymentMethods, displaySavedPaymentMethods, + sdkHandleConfirmPayment, } = Recoil.useRecoilValueFromAtom(optionAtom) let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(configAtom) let optionAtomValue = Recoil.useRecoilValueFromAtom(optionAtom) @@ -24,10 +25,8 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let (sessions, setSessions) = React.useState(_ => Dict.make()->JSON.Encode.object) let (paymentOptions, setPaymentOptions) = React.useState(_ => []) let (walletOptions, setWalletOptions) = React.useState(_ => []) - let {sdkHandleConfirmPayment} = Recoil.useRecoilValueFromAtom(optionAtom) - - let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) - let isGPayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) + let isApplePayReady = Recoil.useRecoilValueFromAtom(isApplePayReady) + let isGPayReady = Recoil.useRecoilValueFromAtom(isGooglePayReady) let (paymentMethodListValue, setPaymentMethodListValue) = Recoil.useRecoilState( PaymentUtils.paymentMethodListValue, @@ -158,6 +157,11 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod googlePayThirdPartySessionObj.sessionsToken, Gpay, ) + let { + paypalToken, + isPaypalSDKFlow, + isPaypalRedirectFlow, + } = PayPalHelpers.usePaymentMethodExperience(~paymentMethodListValue, ~sessionObj) React.useEffect(() => { switch paymentMethodList { @@ -355,6 +359,27 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod | _ => React.null }} + | PayPal => + + {switch paypalToken { + | OtherTokenOptional(optToken) => + switch (optToken, isPaypalSDKFlow, isPaypalRedirectFlow) { + | (Some(_token), true, _) => { + loggerState.setLogInfo( + ~value="PayPal Invoke SDK Flow in Tabs", + ~eventName=PAYPAL_SDK_FLOW, + ) + React.null + } + | (_, _, true) => + | _ => React.null + } + | _ => + + + + }} + | _ => diff --git a/src/Payments/PayPal.res b/src/Payments/PayPal.res index f7edbb5ff..32e27aaac 100644 --- a/src/Payments/PayPal.res +++ b/src/Payments/PayPal.res @@ -11,7 +11,7 @@ module Loader = { let payPalIcon = @react.component -let make = () => { +let make = (~paymentType, ~walletOptions) => { let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom) let (paypalClicked, setPaypalClicked) = React.useState(_ => false) let sdkHandleIsThere = Recoil.useRecoilValueFromAtom(isPaymentButtonHandlerProvidedAtom) @@ -19,6 +19,8 @@ let make = () => { let options = Recoil.useRecoilValueFromAtom(optionAtom) let areOneClickWalletsRendered = Recoil.useSetRecoilState(areOneClickWalletsRendered) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + let isWallet = walletOptions->Array.includes("paypal") + let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) let (_, _, labelType) = options.wallets.style.type_ let _label = switch labelType { @@ -56,12 +58,16 @@ let make = () => { let (connectors, _) = paymentMethodListValue->PaymentUtils.getConnectors(Wallets(Paypal(Redirect))) let body = PaymentBody.paypalRedirectionBody(~connectors) - - let modifiedPaymentBody = PaymentUtils.appendedCustomerAcceptance( + let basePaymentBody = PaymentUtils.appendedCustomerAcceptance( ~isGuestCustomer, ~paymentType=paymentMethodListValue.payment_type, ~body, ) + let modifiedPaymentBody = if isWallet { + basePaymentBody + } else { + basePaymentBody->Utils.mergeAndFlattenToTuples(requiredFieldsBody) + } intent( ~bodyArr=modifiedPaymentBody, @@ -88,24 +94,61 @@ let make = () => { None }) - + let useSubmitCallback = (~isWallet) => { + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + + React.useCallback((ev: Window.event) => { + if !isWallet { + let json = ev.data->Utils.safeParse + let confirm = json->Utils.getDictFromJson->ConfirmType.itemToObjMapper + if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { + onPaypalClick(ev) + } else if areRequiredFieldsEmpty { + Utils.postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterFieldsText, + ) + } else if !areRequiredFieldsValid { + Utils.postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterValidDetailsText, + ) + } + } + }, (areRequiredFieldsValid, areRequiredFieldsEmpty, isWallet, iframeId)) + } + + let submitCallback = useSubmitCallback(~isWallet) + Utils.useSubmitPaymentData(submitCallback) + + if isWallet { + + } else { + + } } let default = make diff --git a/src/Payments/PayPal.resi b/src/Payments/PayPal.resi index 47fa1ac2e..fcd18f442 100644 --- a/src/Payments/PayPal.resi +++ b/src/Payments/PayPal.resi @@ -1,2 +1,2 @@ @react.component -let default: unit => React.element +let default: (~paymentType: CardThemeType.mode, ~walletOptions: array) => React.element diff --git a/src/Payments/PayPalHelpers.res b/src/Payments/PayPalHelpers.res new file mode 100644 index 000000000..dc405d121 --- /dev/null +++ b/src/Payments/PayPalHelpers.res @@ -0,0 +1,31 @@ +open PaymentMethodsRecord +open SessionsType + +type paypalExperienceData = { + paypalToken: optionalTokenType, + isPaypalSDKFlow: bool, + isPaypalRedirectFlow: bool, +} + +let usePaymentMethodExperience = (~paymentMethodListValue, ~sessionObj: sessions) => { + let paypalPaymentMethodExperience = React.useMemo(() => { + getPaymentExperienceTypeFromPML( + ~paymentMethodList=paymentMethodListValue, + ~paymentMethodName="wallet", + ~paymentMethodType="paypal", + ) + }, [paymentMethodListValue]) + + let paypalToken = React.useMemo( + () => getPaymentSessionObj(sessionObj.sessionsToken, Paypal), + [sessionObj], + ) + let isPaypalSDKFlow = paypalPaymentMethodExperience->Array.includes(InvokeSDK) + let isPaypalRedirectFlow = paypalPaymentMethodExperience->Array.includes(RedirectToURL) + + { + paypalToken, + isPaypalSDKFlow, + isPaypalRedirectFlow, + } +} diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 15cbfee7f..2a3c30e35 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -534,6 +534,13 @@ let paymentMethodsFields = [ fields: [InfoElement], miniIcon: None, }, + { + paymentMethodName: "paypal", + icon: Some(icon("paypal", ~size=21, ~width=25)), + displayName: "Paypal", + fields: [], + miniIcon: None, + }, { paymentMethodName: "local_bank_transfer_transfer", fields: [InfoElement], diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 72a8e0efe..7ea122073 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -44,17 +44,13 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) let sessionObj = React.useMemo(() => itemToObjMapper(dict, Others), [dict]) - let paypalToken = React.useMemo( - () => getPaymentSessionObj(sessionObj.sessionsToken, Paypal), - [sessionObj], - ) - let paypalPaymentMethodExperience = React.useMemo(() => { - PaymentMethodsRecord.getPaymentExperienceTypeFromPML( - ~paymentMethodList=paymentMethodListValue, - ~paymentMethodName="wallet", - ~paymentMethodType="paypal", - ) - }, [paymentMethodListValue]) + + let { + paypalToken, + isPaypalSDKFlow, + isPaypalRedirectFlow, + } = PayPalHelpers.usePaymentMethodExperience(~paymentMethodListValue, ~sessionObj) + let gPayToken = getPaymentSessionObj(sessionObj.sessionsToken, Gpay) let applePaySessionObj = itemToObjMapper(dict, ApplePayObject) let applePayToken = getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay) @@ -70,8 +66,6 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { let {clientSecret} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) - let isPaypalSDKFlow = paypalPaymentMethodExperience->Array.includes(InvokeSDK) - let isPaypalRedirectFlow = paypalPaymentMethodExperience->Array.includes(RedirectToURL)
{walletOptions @@ -113,12 +107,12 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { | OtherTokenOptional(optToken) => switch (optToken, isPaypalSDKFlow, isPaypalRedirectFlow) { | (Some(token), true, _) => - | (_, _, true) => + | (_, _, true) => | _ => React.null } | _ => - + }} diff --git a/src/Types/PaymentModeType.res b/src/Types/PaymentModeType.res index 14d29b396..074f872fd 100644 --- a/src/Types/PaymentModeType.res +++ b/src/Types/PaymentModeType.res @@ -19,6 +19,7 @@ type payment = | GooglePay | ApplePay | Boleto + | PayPal | NONE let paymentMode = str => { @@ -43,6 +44,7 @@ let paymentMode = str => { | "google_pay" => GooglePay | "apple_pay" => ApplePay | "boleto" => Boleto + | "paypal" => PayPal | _ => NONE } } diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 0f3c56859..203c836ae 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -25,6 +25,7 @@ let dynamicFieldsEnabledPaymentMethods = [ "bacs", "pay_bright", "multibanco_transfer", + "paypal", ] let getName = (item: PaymentMethodsRecord.required_fields, field: RecoilAtomTypes.field) => { diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index b1c071b66..4a4c8a92a 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -10,6 +10,7 @@ let paymentListLookupNew = ( ~areAllGooglePayRequiredFieldsPrefilled, ~isGooglePayReady, ~shouldDisplayApplePayInTabs, + ~shouldDisplayPayPalInTabs, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] @@ -36,6 +37,10 @@ let paymentListLookupNew = ( walletToBeDisplayedInTabs->Array.push("apple_pay") } + if shouldDisplayPayPalInTabs { + walletToBeDisplayedInTabs->Array.push("paypal") + } + if ( !paymentMethodListValue.collect_billing_details_from_wallets && !areAllGooglePayRequiredFieldsPrefilled && @@ -305,6 +310,32 @@ let getIsKlarnaSDKFlow = sessions => { } } +let usePaypalFlowStatus = (~sessions, ~paymentMethodListValue) => { + open Utils + + let sessionObj = + sessions + ->getDictFromJson + ->SessionsType.itemToObjMapper(Others) + + let { + paypalToken, + isPaypalSDKFlow, + isPaypalRedirectFlow, + } = PayPalHelpers.usePaymentMethodExperience(~paymentMethodListValue, ~sessionObj) + + let isPaypalTokenExist = switch paypalToken { + | OtherTokenOptional(optToken) => + switch optToken { + | Some(_) => true + | _ => false + } + | _ => false + } + + (isPaypalSDKFlow, isPaypalRedirectFlow, isPaypalTokenExist) +} + let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { open Utils let methodslist = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentMethodList) @@ -332,9 +363,20 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { ~paymentMethodType="google_pay", ) + let areAllPaypalRequiredFieldsPreFilled = useAreAllRequiredFieldsPrefilled( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="paypal", + ) + let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) let isGooglePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) + let (isPaypalSDKFlow, isPaypalRedirectFlow, isPaypalTokenExist) = usePaypalFlowStatus( + ~sessions, + ~paymentMethodListValue, + ) + React.useMemo(() => { switch methodslist { | Loaded(paymentlist) => @@ -347,16 +389,26 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { !areAllApplePayRequiredFieldsPrefilled && isApplePayReady + let isShowPaypal = optionAtomValue.wallets.payPal === Auto + + let shouldDisplayPayPalInTabs = + isShowPaypal && + !paymentMethodListValue.collect_billing_details_from_wallets && + !areAllPaypalRequiredFieldsPreFilled && + isPaypalRedirectFlow && + (!isPaypalSDKFlow || !isPaypalTokenExist) + let (wallets, otherOptions) = plist->paymentListLookupNew( ~order=paymentOrder, - ~isShowPaypal=optionAtomValue.wallets.payPal === Auto, + ~isShowPaypal, ~isShowKlarnaOneClick=optionAtomValue.wallets.klarna === Auto, ~isKlarnaSDKFlow, ~paymentMethodListValue=plist, ~areAllGooglePayRequiredFieldsPrefilled, ~isGooglePayReady, ~shouldDisplayApplePayInTabs, + ~shouldDisplayPayPalInTabs, ) let klarnaPaymentMethodExperience = PaymentMethodsRecord.getPaymentExperienceTypeFromPML(