diff --git a/src/PaymentElement.res b/src/PaymentElement.res index 8a0ca1eb5..99b21f7b3 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -148,6 +148,17 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod ~sessions, ) + let dict = sessions->getDictFromJson + let sessionObj = SessionsType.itemToObjMapper(dict, Others) + let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject) + let applePayToken = SessionsType.getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay) + let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) + let googlePayThirdPartySessionObj = SessionsType.itemToObjMapper(dict, GooglePayThirdPartyObject) + let googlePayThirdPartyToken = SessionsType.getPaymentSessionObj( + googlePayThirdPartySessionObj.sessionsToken, + Gpay, + ) + React.useEffect(() => { switch paymentMethodList { | Loaded(paymentlist) => @@ -317,6 +328,30 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod + | ApplePay => + switch applePayToken { + | ApplePayTokenOptional(optToken) => + + | _ => React.null + } + | GooglePay => + + {switch gPayToken { + | OtherTokenOptional(optToken) => + switch googlePayThirdPartyToken { + | GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) => + + | _ => + + } + | _ => React.null + }} + | _ => diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 6f66c5b50..8b3fe9821 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -1,7 +1,7 @@ open Utils open Promise @react.component -let make = (~sessionObj: option) => { +let make = (~sessionObj: option, ~walletOptions, ~paymentType: CardThemeType.mode) => { let url = RescriptReactRouter.useUrl() let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName") let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) @@ -21,6 +21,17 @@ let make = (~sessionObj: option) => { let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) + let isWallet = walletOptions->Array.includes("apple_pay") + + UtilityHooks.useHandlePostMessages( + ~complete=areRequiredFieldsValid, + ~empty=areRequiredFieldsEmpty, + ~paymentType="apple_pay", + ) + let applePayPaymentMethodType = React.useMemo(() => { switch PaymentMethodsRecord.getPaymentMethodTypeFromList( ~paymentMethodListValue, @@ -260,12 +271,15 @@ let make = (~sessionObj: option) => { ~setApplePayClicked, ~syncPayment, ~isInvokeSDKFlow, + ~isWallet, + ~requiredFieldsBody, ) React.useEffect(() => { if ( (isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) && - isApplePayReady + isApplePayReady && + isWallet ) { setShowApplePay(_ => true) areOneClickWalletsRendered(prev => { @@ -275,26 +289,35 @@ let make = (~sessionObj: option) => { setIsShowOrPayUsing(_ => true) } None - }, (isApplePayReady, isInvokeSDKFlow, paymentExperience)) + }, (isApplePayReady, isInvokeSDKFlow, paymentExperience, isWallet)) - -
- - {if showApplePayLoader { -
-
-
- } else { - - }} -
- + let submitCallback = ApplePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName) + useSubmitPaymentData(submitCallback) + + if isWallet { + +
+ + {if showApplePayLoader { +
+
+
+ } else { + + }} +
+ + } else { + + } } let default = make diff --git a/src/Payments/ApplePay.resi b/src/Payments/ApplePay.resi index 05c6aaf1b..d5b7ab2f4 100644 --- a/src/Payments/ApplePay.resi +++ b/src/Payments/ApplePay.resi @@ -1,2 +1,6 @@ @react.component -let default: (~sessionObj: option) => React.element +let default: ( + ~sessionObj: option, + ~walletOptions: array, + ~paymentType: CardThemeType.mode, +) => React.element diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index bf85fdeb4..82e8d4e6a 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -5,7 +5,12 @@ open GooglePayType open Promise @react.component -let make = (~sessionObj: option, ~thirdPartySessionObj: option) => { +let make = ( + ~sessionObj: option, + ~thirdPartySessionObj: option, + ~walletOptions, + ~paymentType: CardThemeType.mode, +) => { let url = RescriptReactRouter.useUrl() let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName") let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom) @@ -28,6 +33,17 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered) + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) + let isWallet = walletOptions->Array.includes("google_pay") + + UtilityHooks.useHandlePostMessages( + ~complete=areRequiredFieldsValid, + ~empty=areRequiredFieldsEmpty, + ~paymentType="google_pay", + ) + let googlePayPaymentMethodType = switch PaymentMethodsRecord.getPaymentMethodTypeFromList( ~paymentMethodListValue, ~paymentMethod="wallet", @@ -58,7 +74,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti ->Option.getOr(false) }, [thirdPartySessionObj]) - GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent) + GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent, ~isWallet, ~requiredFieldsBody) let (_, buttonType, _) = options.wallets.style.type_ let (_, heightType, _, _) = options.wallets.style.height @@ -155,9 +171,10 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti React.useEffect(() => { if ( status == "ready" && - (isGPayReady || - isDelayedSessionToken || - paymentExperience == PaymentMethodsRecord.RedirectToURL) + (isGPayReady || + isDelayedSessionToken || + paymentExperience == PaymentMethodsRecord.RedirectToURL) && + isWallet ) { setIsShowOrPayUsing(_ => true) addGooglePayButton() @@ -190,7 +207,9 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti }) let isRenderGooglePayButton = - isGPayReady || paymentExperience == PaymentMethodsRecord.RedirectToURL || isDelayedSessionToken + (isGPayReady || + paymentExperience == PaymentMethodsRecord.RedirectToURL || + isDelayedSessionToken) && isWallet React.useEffect(() => { areOneClickWalletsRendered(prev => { @@ -200,13 +219,22 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti None }, [isRenderGooglePayButton]) - -
Int.toString}px`} - id="google-pay-button" - className={`w-full flex flex-row justify-center rounded-md`} + let submitCallback = GooglePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName) + useSubmitPaymentData(submitCallback) + + if isWallet { + +
Int.toString}px`} + id="google-pay-button" + className={`w-full flex flex-row justify-center rounded-md`} + /> + + } else { + - + } } let default = make diff --git a/src/Payments/GPay.resi b/src/Payments/GPay.resi index d03d071d4..11bfe1246 100644 --- a/src/Payments/GPay.resi +++ b/src/Payments/GPay.resi @@ -2,4 +2,6 @@ let default: ( ~sessionObj: option, ~thirdPartySessionObj: option, + ~walletOptions: array, + ~paymentType: CardThemeType.mode, ) => React.element diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 1d3855e32..338ffb5f9 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -802,6 +802,7 @@ type paymentMethodList = { mandate_payment: option, payment_type: payment_type, merchant_name: string, + collect_billing_details_from_wallets: bool, } let defaultPaymentMethodType = { @@ -823,6 +824,7 @@ let defaultList = { mandate_payment: None, payment_type: NONE, merchant_name: "", + collect_billing_details_from_wallets: true, } let getMethod = str => { switch str { @@ -1030,6 +1032,11 @@ let itemToObjMapper = dict => { mandate_payment: getMandate(dict, "mandate_payment"), payment_type: getString(dict, "payment_type", "")->paymentTypeMapper, merchant_name: getString(dict, "merchant_name", ""), + collect_billing_details_from_wallets: getBool( + dict, + "collect_billing_details_from_wallets", + true, + ), } } diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 2814f9901..f78578493 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -77,9 +77,15 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { switch googlePayThirdPartyToken { | GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) => + | _ => + - | _ => } | _ => React.null }} @@ -97,7 +103,8 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { | ApplePayWallet => switch applePayToken { - | ApplePayTokenOptional(optToken) => + | ApplePayTokenOptional(optToken) => + | _ => React.null } | KlarnaWallet => diff --git a/src/Utilities/ApplePayHelpers.res b/src/Utilities/ApplePayHelpers.res index 624f0f4eb..667b39682 100644 --- a/src/Utilities/ApplePayHelpers.res +++ b/src/Utilities/ApplePayHelpers.res @@ -142,6 +142,8 @@ let useHandleApplePayResponse = ( ~syncPayment=() => (), ~isInvokeSDKFlow=true, ~isSavedMethodsFlow=false, + ~isWallet=true, + ~requiredFieldsBody=Dict.make(), ) => { let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) @@ -187,8 +189,18 @@ let useHandleApplePayResponse = ( ~isSavedMethodsFlow, ) + let bodyArr = if isWallet { + applePayBody + } else { + applePayBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict + } + processPayment( - ~bodyArr=applePayBody, + ~bodyArr, ~isThirdPartyFlow=false, ~isGuestCustomer, ~paymentMethodListValue, @@ -199,7 +211,7 @@ let useHandleApplePayResponse = ( ) } else if dict->Dict.get("showApplePayButton")->Option.isSome { setApplePayClicked(_ => false) - if isSavedMethodsFlow { + if isSavedMethodsFlow || !isWallet { postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong") } } else if dict->Dict.get("applePaySyncPayment")->Option.isSome { @@ -216,7 +228,14 @@ let useHandleApplePayResponse = ( Window.removeEventListener("message", handleApplePayMessages) }, ) - }, (isInvokeSDKFlow, processPayment, stateJson, isManualRetryEnabled)) + }, ( + isInvokeSDKFlow, + processPayment, + stateJson, + isManualRetryEnabled, + isWallet, + requiredFieldsBody, + )) } let handleApplePayButtonClicked = (~sessionObj, ~componentName) => { @@ -227,3 +246,30 @@ let handleApplePayButtonClicked = (~sessionObj, ~componentName) => { ] handlePostMessage(message) } + +let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => { + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) + let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + + React.useCallback((ev: Window.event) => { + if !isWallet { + let json = ev.data->JSON.parseExn + let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper + if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { + options.readOnly ? () : handleApplePayButtonClicked(~sessionObj, ~componentName) + } else if areRequiredFieldsEmpty { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterFieldsText, + ) + } else if !areRequiredFieldsValid { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterValidDetailsText, + ) + } + } + }, (areRequiredFieldsValid, areRequiredFieldsEmpty, isWallet, sessionObj, componentName)) +} diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 02bf1ae1d..35dee4224 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -580,20 +580,22 @@ let useRequiredFieldsBody = ( | FullName => getName(item, fullName) | _ => item.field_type->getFieldValueFromFieldType } - if ( - isSavedCardFlow && - (item.field_type === BillingName || item.field_type === FullName) && - item.display_name === "card_holder_name" && - item.required_field === "payment_method_data.card.card_holder_name" - ) { - if !isAllStoredCardsHaveName { - acc->Dict.set( - "payment_method_data.card_token.card_holder_name", - value->JSON.Encode.string, - ) + if value != "" { + if ( + isSavedCardFlow && + (item.field_type === BillingName || item.field_type === FullName) && + item.display_name === "card_holder_name" && + item.required_field === "payment_method_data.card.card_holder_name" + ) { + if !isAllStoredCardsHaveName { + acc->Dict.set( + "payment_method_data.card_token.card_holder_name", + value->JSON.Encode.string, + ) + } + } else { + acc->Dict.set(item.required_field, value->JSON.Encode.string) } - } else { - acc->Dict.set(item.required_field, value->JSON.Encode.string) } acc }) @@ -822,22 +824,6 @@ let usePaymentMethodTypeFromList = ( }, (paymentMethodListValue, paymentMethod, paymentMethodType)) } -let useAreAllRequiredFieldsPrefilled = ( - ~paymentMethodListValue, - ~paymentMethod, - ~paymentMethodType, -) => { - let paymentMethodTypes = usePaymentMethodTypeFromList( - ~paymentMethodListValue, - ~paymentMethod, - ~paymentMethodType, - ) - - paymentMethodTypes.required_fields->Array.reduce(true, (acc, requiredField) => { - acc && requiredField.value != "" - }) -} - let removeRequiredFieldsDuplicates = ( requiredFields: array, ) => { diff --git a/src/Utilities/GooglePayHelpers.res b/src/Utilities/GooglePayHelpers.res index f9a756caf..b8bb45ab2 100644 --- a/src/Utilities/GooglePayHelpers.res +++ b/src/Utilities/GooglePayHelpers.res @@ -81,7 +81,13 @@ let processPayment = ( ) } -let useHandleGooglePayResponse = (~connectors, ~intent, ~isSavedMethodsFlow=false) => { +let useHandleGooglePayResponse = ( + ~connectors, + ~intent, + ~isSavedMethodsFlow=false, + ~isWallet=true, + ~requiredFieldsBody=Dict.make(), +) => { let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) let isManualRetryEnabled = Recoil.useRecoilValueFromAtom(RecoilAtoms.isManualRetryEnabled) @@ -118,8 +124,19 @@ let useHandleGooglePayResponse = (~connectors, ~intent, ~isSavedMethodsFlow=fals ~stateJson, ~isSavedMethodsFlow, ) + + let googlePayBody = if isWallet { + body + } else { + body + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict + } + processPayment( - ~body, + ~body=googlePayBody, ~isThirdPartyFlow=false, ~intent, ~options: PaymentType.options, @@ -129,14 +146,14 @@ let useHandleGooglePayResponse = (~connectors, ~intent, ~isSavedMethodsFlow=fals } if dict->Dict.get("gpayError")->Option.isSome { handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) - if isSavedMethodsFlow { + if isSavedMethodsFlow || !isWallet { postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong") } } } Window.addEventListener("message", handle) Some(() => {Window.removeEventListener("message", handle)}) - }, (paymentMethodTypes, stateJson, isManualRetryEnabled)) + }, (paymentMethodTypes, stateJson, isManualRetryEnabled, requiredFieldsBody, isWallet)) } let handleGooglePayClicked = (~sessionObj, ~componentName, ~iframeId, ~readOnly) => { @@ -153,3 +170,38 @@ let handleGooglePayClicked = (~sessionObj, ~componentName, ~iframeId, ~readOnly) ]) } } + +let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => { + let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) + let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) + let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) + let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + + React.useCallback((ev: Window.event) => { + if !isWallet { + let json = ev.data->JSON.parseExn + let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper + if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { + handleGooglePayClicked(~sessionObj, ~componentName, ~iframeId, ~readOnly=options.readOnly) + } else if areRequiredFieldsEmpty { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterFieldsText, + ) + } else if !areRequiredFieldsValid { + postFailedSubmitResponse( + ~errortype="validation_error", + ~message=localeString.enterValidDetailsText, + ) + } + } + }, ( + areRequiredFieldsValid, + areRequiredFieldsEmpty, + isWallet, + sessionObj, + componentName, + iframeId, + )) +} diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 92096fef2..735ad4d57 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -7,6 +7,10 @@ let paymentListLookupNew = ( ~isShowKlarnaOneClick, ~isKlarnaSDKFlow, ~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList, + ~areAllApplePayRequiredFieldsPrefilled, + ~areAllGooglePayRequiredFieldsPrefilled, + ~isApplePayReady, + ~isGooglePayReady, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] @@ -29,6 +33,22 @@ let paymentListLookupNew = ( ] let otherPaymentList = [] + if ( + !paymentMethodListValue.collect_billing_details_from_wallets && + !areAllApplePayRequiredFieldsPrefilled && + isApplePayReady + ) { + walletToBeDisplayedInTabs->Array.push("apple_pay") + } + + if ( + !paymentMethodListValue.collect_billing_details_from_wallets && + !areAllGooglePayRequiredFieldsPrefilled && + isGooglePayReady + ) { + walletToBeDisplayedInTabs->Array.push("google_pay") + } + pmList->Array.forEach(item => { if walletToBeDisplayedInTabs->Array.includes(item.paymentMethodName) { otherPaymentList->Array.push(item.paymentMethodName)->ignore @@ -303,6 +323,23 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { let isKlarnaSDKFlow = getIsKlarnaSDKFlow(sessions) + let paymentMethodListValue = Recoil.useRecoilValueFromAtom(paymentMethodListValue) + + let areAllApplePayRequiredFieldsPrefilled = useAreAllRequiredFieldsPrefilled( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + ) + + let areAllGooglePayRequiredFieldsPrefilled = useAreAllRequiredFieldsPrefilled( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", + ) + + let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) + let isGooglePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) + React.useMemo(() => { switch methodslist { | Loaded(paymentlist) => @@ -316,6 +353,10 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { ~isShowKlarnaOneClick=optionAtomValue.wallets.klarna === Auto, ~isKlarnaSDKFlow, ~paymentMethodListValue=plist, + ~areAllApplePayRequiredFieldsPrefilled, + ~areAllGooglePayRequiredFieldsPrefilled, + ~isApplePayReady, + ~isGooglePayReady, ) let klarnaPaymentMethodExperience = PaymentMethodsRecord.getPaymentExperienceTypeFromPML( @@ -353,6 +394,10 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { optionAtomValue.wallets.klarna, paymentType, isKlarnaSDKFlow, + areAllApplePayRequiredFieldsPrefilled, + areAllGooglePayRequiredFieldsPrefilled, + isApplePayReady, + isGooglePayReady, )) }