From aa90402f96ac8f7229550b53016923286a1c1217 Mon Sep 17 00:00:00 2001 From: Arush Date: Thu, 9 May 2024 01:41:53 +0530 Subject: [PATCH 1/6] feat: added support for collecting billing and shipping details via Apple Modal --- src/PaymentElement.res | 8 - src/Payments/ApplePay.res | 190 ++++++++----------- src/Payments/ApplePay.resi | 6 +- src/Payments/ApplePayLazy.res | 4 +- src/Payments/PaymentRequestButtonElement.res | 3 +- src/Types/ApplePayTypes.res | 50 ++++- src/Utilities/DynamicFieldsUtils.res | 59 ++++++ src/Utilities/PaymentUtils.res | 21 -- src/Utilities/Utils.res | 26 +++ src/orca-loader/Elements.res | 11 +- 10 files changed, 228 insertions(+), 150 deletions(-) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index e1e0d7479..21634fe86 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -226,8 +226,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod } let dict = sessions->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) @@ -319,12 +317,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod } | _ => React.null } - | ApplePay => - switch applePayToken { - | ApplePayTokenOptional(optToken) => - - | _ => React.null - } | Boleto => diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 83d7e8ef9..ee919556a 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -1,10 +1,7 @@ open Utils +open Promise @react.component -let make = ( - ~sessionObj: option, - ~paymentType: option, - ~walletOptions: array, -) => { +let make = (~sessionObj: option) => { let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) let {publishableKey, sdkHandleOneClickConfirmPayment} = Recoil.useRecoilValueFromAtom( RecoilAtoms.keys, @@ -18,19 +15,9 @@ let make = ( let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let (applePayClicked, setApplePayClicked) = React.useState(_ => false) let isApplePaySDKFlow = sessionObj->Option.isSome - let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) - let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) - let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) - let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) - let isWallet = walletOptions->Array.includes("apple_pay") let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) - - UtilityHooks.useHandlePostMessages( - ~complete=areRequiredFieldsValid, - ~empty=areRequiredFieldsEmpty, - ~paymentType="apple_pay", - ) + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) let applePayPaymentMethodType = React.useMemo(() => { switch PaymentMethodsRecord.getPaymentMethodTypeFromList( @@ -60,6 +47,17 @@ let make = ( let isGuestCustomer = UtilityHooks.useIsGuestCustomer() + React.useEffect0(() => { + AddressPaymentInput.importStates("./../States.json") + ->then(res => { + setStatesJson(_ => res.states) + resolve() + }) + ->ignore + + None + }) + let processPayment = (bodyArr, ~isThirdPartyFlow=false, ()) => { let requestBody = PaymentUtils.appendedCustomerAcceptance( ~isGuestCustomer, @@ -67,35 +65,16 @@ let make = ( ~body=bodyArr, ) - if isWallet { - intent( - ~bodyArr=requestBody, - ~confirmParam={ - return_url: options.wallets.walletReturnUrl, - publishableKey, - }, - ~handleUserError=true, - ~isThirdPartyFlow, - (), - ) - } else { - let requiredFieldsBodyArr = - requestBody - ->getJsonFromArrayOfJson - ->flattenObject(true) - ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) - ->getArrayOfTupleFromDict - intent( - ~bodyArr=requiredFieldsBodyArr, - ~confirmParam={ - return_url: options.wallets.walletReturnUrl, - publishableKey, - }, - ~handleUserError=true, - ~isThirdPartyFlow, - (), - ) - } + intent( + ~bodyArr=requestBody, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=true, + ~isThirdPartyFlow, + (), + ) } let syncPayment = () => { @@ -252,7 +231,6 @@ let make = ( (), ) setApplePayClicked(_ => true) - open Promise makeOneClickHandlerPromise(sdkHandleOneClickConfirmPayment) ->then(result => { let result = result->JSON.Decode.bool->Option.getOr(false) @@ -288,6 +266,12 @@ let make = ( ->ignore } + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + ) + React.useEffect(() => { let handleApplePayMessages = (ev: Window.event) => { let json = try { @@ -301,13 +285,41 @@ let make = ( if dict->Dict.get("applePayProcessPayment")->Option.isSome { let token = dict->Dict.get("applePayProcessPayment")->Option.getOr(Dict.make()->JSON.Encode.object) + + let billingContact = + dict + ->Dict.get("applePayBillingContact") + ->Option.flatMap(JSON.Decode.object) + ->Option.getOr(Dict.make()) + ->ApplePayTypes.billingContactItemToObjMapper + + let shippingContact = + dict + ->Dict.get("applePayShippingContact") + ->Option.flatMap(JSON.Decode.object) + ->Option.getOr(Dict.make()) + ->ApplePayTypes.shippingContactItemToObjMapper + + let requiredFieldsBody = DynamicFieldsUtils.getApplePayRequiredFieldsFromBillingAndShippingContact( + ~billingContact, + ~shippingContact, + ~paymentMethodTypes, + ~statesList=stateJson, + ) + let bodyDict = PaymentBody.applePayBody(~token, ~connectors) - processPayment(bodyDict, ()) + + let applePayBody = + bodyDict + ->Dict.fromArray + ->JSON.Encode.object + ->Utils.flattenObject(true) + ->Utils.mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->Utils.getArrayOfTupleFromDict + + processPayment(applePayBody, ()) } else if dict->Dict.get("showApplePayButton")->Option.isSome { setApplePayClicked(_ => false) - if !isWallet { - postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong") - } } else if dict->Dict.get("applePaySyncPayment")->Option.isSome { syncPayment() } @@ -322,13 +334,12 @@ let make = ( Window.removeEventListener("message", handleApplePayMessages) }, ) - }, (isInvokeSDKFlow, requiredFieldsBody, isWallet, processPayment)) + }, (isInvokeSDKFlow, processPayment, stateJson)) React.useEffect(() => { if ( (isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) && - isApplePayReady && - isWallet + isApplePayReady ) { setShowApplePay(_ => true) areOneClickWalletsRendered(prev => { @@ -338,61 +349,26 @@ let make = ( setIsShowOrPayUsing(_ => true) } None - }, (isApplePayReady, isInvokeSDKFlow, paymentExperience, isWallet)) - - let submitCallback = 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 - ? () - : handlePostMessage([("applePayButtonClicked", true->JSON.Encode.bool)]) - } else if areRequiredFieldsEmpty { - postFailedSubmitResponse( - ~errortype="validation_error", - ~message=localeString.enterFieldsText, - ) - } else if !areRequiredFieldsValid { - postFailedSubmitResponse( - ~errortype="validation_error", - ~message=localeString.enterValidDetailsText, - ) - } - } - }, (areRequiredFieldsValid, areRequiredFieldsEmpty)) - useSubmitPaymentData(submitCallback) - - if isWallet { -
- - - {if showApplePayLoader { -
-
-
- } else { - - }} - -
- } else { - val - | _ => NONE + }, (isApplePayReady, isInvokeSDKFlow, paymentExperience)) + +
+ + + {if showApplePayLoader { +
+
+
+ } else { + }} - paymentMethod="wallet" - paymentMethodType="apple_pay" - setRequiredFieldsBody - /> - } + +
} let default = make diff --git a/src/Payments/ApplePay.resi b/src/Payments/ApplePay.resi index 45efe9c77..05c6aaf1b 100644 --- a/src/Payments/ApplePay.resi +++ b/src/Payments/ApplePay.resi @@ -1,6 +1,2 @@ @react.component -let default: ( - ~sessionObj: option, - ~paymentType: option, - ~walletOptions: array, -) => React.element +let default: (~sessionObj: option) => React.element diff --git a/src/Payments/ApplePayLazy.res b/src/Payments/ApplePayLazy.res index 0198ffa87..57d9d5a69 100644 --- a/src/Payments/ApplePayLazy.res +++ b/src/Payments/ApplePayLazy.res @@ -1,9 +1,7 @@ open LazyUtils type props = { - sessionObj: option, - paymentType: CardThemeType.mode, - walletOptions: array, + sessionObj: option } let make: props => React.element = reactLazy(() => import_("./ApplePay.bs.js")) diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 868d141d3..0c2109899 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -100,8 +100,7 @@ let make = (~sessions, ~walletOptions) => { | ApplePayWallet => switch applePayToken { - | ApplePayTokenOptional(optToken) => - + | ApplePayTokenOptional(optToken) => | _ => React.null } diff --git a/src/Types/ApplePayTypes.res b/src/Types/ApplePayTypes.res index c4a5eb9c6..8865aaffc 100644 --- a/src/Types/ApplePayTypes.res +++ b/src/Types/ApplePayTypes.res @@ -1,5 +1,27 @@ type token = {paymentData: JSON.t} -type paymentResult = {token: JSON.t} +type billingContact = { + addressLines: array, + administrativeArea: string, + countryCode: string, + familyName: string, + givenName: string, + locality: string, + postalCode: string, +} + +type shippingContact = { + emailAddress: string, + phoneNumber: string, + addressLines: array, + administrativeArea: string, + countryCode: string, + familyName: string, + givenName: string, + locality: string, + postalCode: string, +} + +type paymentResult = {token: JSON.t, billingContact: JSON.t, shippingContact: JSON.t} type event = {validationURL: string, payment: paymentResult} type innerSession type session = { @@ -82,3 +104,29 @@ let jsonToPaymentRequestDataType: Dict.t => paymentRequestData = jsonDic ) } } + +let billingContactItemToObjMapper = dict => { + { + addressLines: dict->Utils.getStrArray("addressLines"), + administrativeArea: dict->Utils.getString("administrativeArea", ""), + countryCode: dict->Utils.getString("countryCode", ""), + familyName: dict->Utils.getString("familyName", ""), + givenName: dict->Utils.getString("givenName", ""), + locality: dict->Utils.getString("locality", ""), + postalCode: dict->Utils.getString("postalCode", ""), + } +} + +let shippingContactItemToObjMapper = dict => { + { + emailAddress: dict->Utils.getString("emailAddress", ""), + phoneNumber: dict->Utils.getString("phoneNumber", ""), + addressLines: dict->Utils.getStrArray("addressLines"), + administrativeArea: dict->Utils.getString("administrativeArea", ""), + countryCode: dict->Utils.getString("countryCode", ""), + familyName: dict->Utils.getString("familyName", ""), + givenName: dict->Utils.getString("givenName", ""), + locality: dict->Utils.getString("locality", ""), + postalCode: dict->Utils.getString("postalCode", ""), + } +} diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 56421f1d9..2ef60b093 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -738,3 +738,62 @@ let removeRequiredFieldsDuplicates = ( requiredFields } + +let getApplePayRequiredFieldsFromBillingAndShippingContact = ( + ~billingContact: ApplePayTypes.billingContact, + ~shippingContact: ApplePayTypes.shippingContact, + ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~statesList, +) => { + paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + let requiredFieldsArr = item.required_field->String.split(".") + + let getBillingOrShipping = (billingVal, shippingVal) => { + requiredFieldsArr->Array.includes("billing") ? billingVal : shippingVal + } + + let getName = { + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { + | "last_name" => billingContact.familyName->getBillingOrShipping(shippingContact.familyName) + | _ => billingContact.givenName->getBillingOrShipping(shippingContact.givenName) + } + } + + let getAddressLine = (addressLines, index) => { + addressLines->Array.get(index)->Option.getOr("") + } + + let fieldVal = switch item.field_type { + | FullName => getName + | Country => billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) + | BillingName => getName + | AddressLine1 => + let billingAddressLine1 = billingContact.addressLines->getAddressLine(0) + let shippingAddressLine1 = shippingContact.addressLines->getAddressLine(0) + billingAddressLine1->getBillingOrShipping(shippingAddressLine1) + | AddressLine2 => + let billingAddressLine1 = billingContact.addressLines->getAddressLine(1) + let shippingAddressLine1 = shippingContact.addressLines->getAddressLine(1) + billingAddressLine1->getBillingOrShipping(shippingAddressLine1) + | AddressCity => billingContact.locality->getBillingOrShipping(shippingContact.locality) + | AddressState => + let administrativeArea = + billingContact.administrativeArea->getBillingOrShipping(shippingContact.administrativeArea) + let countryCode = + billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) + Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + | AddressCountry(_) => + billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) + | AddressPincode => billingContact.postalCode->getBillingOrShipping(shippingContact.postalCode) + | Email => shippingContact.emailAddress + | PhoneNumber => shippingContact.phoneNumber + | _ => "" + } + + if fieldVal !== "" { + acc->Dict.set(item.required_field, fieldVal->JSON.Encode.string) + } + + acc + }) +} diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 7fde091e5..b63c66903 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -3,10 +3,8 @@ let paymentMethodListValue = Recoil.atom("paymentMethodListValue", PaymentMethod let paymentListLookupNew = ( list: PaymentMethodsRecord.paymentMethodList, ~order, - ~showApplePay, ~showGooglePay, ~areAllGooglePayRequiredFieldsPrefilled, - ~areAllApplePayRequiredFieldsPrefilled, ~isShowPaypal, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList @@ -28,7 +26,6 @@ let paymentListLookupNew = ( ] let otherPaymentList = [] let googlePayFields = pmList->Array.find(item => item.paymentMethodName === "google_pay") - let applePayFields = pmList->Array.find(item => item.paymentMethodName === "apple_pay") switch googlePayFields { | Some(val) => if val.fields->Array.length > 0 && showGooglePay && !areAllGooglePayRequiredFieldsPrefilled { @@ -36,13 +33,6 @@ let paymentListLookupNew = ( } | None => () } - switch applePayFields { - | Some(val) => - if val.fields->Array.length > 0 && showApplePay && !areAllApplePayRequiredFieldsPrefilled { - walletToBeDisplayedInTabs->Array.push("apple_pay")->ignore - } - | None => () - } pmList->Array.forEach(item => { if walletToBeDisplayedInTabs->Array.includes(item.paymentMethodName) { @@ -284,7 +274,6 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen RecoilAtoms.optionAtom, ) - let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) let isGooglePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) let optionAtomValue = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) @@ -296,12 +285,6 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen ~paymentMethodType="google_pay", ) - let areAllApplePayRequiredFieldsPrefilled = useAreAllRequiredFieldsPrefilled( - ~paymentMethodListValue, - ~paymentMethod="wallet", - ~paymentMethodType="apple_pay", - ) - React.useMemo(() => { switch methodslist { | Loaded(paymentlist) => @@ -311,10 +294,8 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen let (wallets, otherOptions) = plist->paymentListLookupNew( ~order=paymentOrder, - ~showApplePay=isApplePayReady, ~showGooglePay=isGooglePayReady, ~areAllGooglePayRequiredFieldsPrefilled, - ~areAllApplePayRequiredFieldsPrefilled, ~isShowPaypal=optionAtomValue.wallets.payPal === Auto, ) ( @@ -331,10 +312,8 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen }, ( methodslist, paymentMethodOrder, - isApplePayReady, isGooglePayReady, areAllGooglePayRequiredFieldsPrefilled, - areAllApplePayRequiredFieldsPrefilled, optionAtomValue.wallets.payPal, paymentType, )) diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 27d211e8b..173ac7359 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1246,3 +1246,29 @@ let isWalletElementPaymentType = (paymentType: CardThemeType.mode) => { let getUniqueArray = arr => arr->Array.map(item => (item, ""))->Dict.fromArray->Dict.keysToArray let getJsonFromArrayOfJson = arr => arr->Dict.fromArray->JSON.Encode.object + +let getStateNameFromStateCodeAndCountry = (list: JSON.t, stateCode: string, country: string) => { + let options = + list + ->getDictFromJson + ->getOptionalArrayFromDict(country) + ->Option.getOr([]) + + let val = options->Array.find(item => + item + ->getDictFromJson + ->Dict.get("code") + ->Option.flatMap(JSON.Decode.string) + ->Option.getOr("") === stateCode + ) + + switch val { + | Some(stateObj) => + stateObj + ->getDictFromJson + ->Dict.get("name") + ->Option.flatMap(JSON.Decode.string) + ->Option.getOr(stateCode) + | None => stateCode + } +} diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 699eb7df4..58ae31bd7 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -698,9 +698,14 @@ let make = ( applePayPresent->Belt.Option.isSome ) { //do operations here - let processPayment = (token: JSON.t) => { + let processPayment = (payment: ApplePayTypes.paymentResult) => { //let body = PaymentBody.applePayBody(~token) - let msg = [("applePayProcessPayment", token)]->Dict.fromArray + let msg = + [ + ("applePayProcessPayment", payment.token), + ("applePayBillingContact", payment.billingContact), + ("applePayShippingContact", payment.shippingContact), + ]->Dict.fromArray mountedIframeRef->Window.iframePostMessage(msg) } @@ -762,7 +767,7 @@ let make = ( {"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson, ) applePaySessionRef := Nullable.null - processPayment(event.payment.token) + processPayment(event.payment) let value = "Payment Data Filled: New Payment Method" logger.setLogInfo( ~value, From 3f43133a8bee9eff624893cf4b16e1dd5a1efd1a Mon Sep 17 00:00:00 2001 From: Arush Date: Thu, 9 May 2024 04:30:23 +0530 Subject: [PATCH 2/6] feat: added support for collecting billing and shipping details via GooglePay Modal --- src/PaymentElement.res | 31 +---- src/Payments/ApplePay.res | 2 +- src/Payments/GPay.res | 130 +++++++++---------- src/Payments/GPay.resi | 2 - src/Payments/GPayLazy.res | 2 - src/Payments/PaymentRequestButtonElement.res | 10 +- src/Types/GooglePayType.res | 31 +++++ src/Types/SessionsType.res | 9 ++ src/Utilities/DynamicFieldsUtils.res | 82 +++++++++++- src/Utilities/PaymentUtils.res | 31 +---- src/WalletElement.res | 10 +- src/orca-loader/Elements.res | 5 + 12 files changed, 188 insertions(+), 157 deletions(-) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index 21634fe86..808bd4f03 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -25,9 +25,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let (walletOptions, setWalletOptions) = React.useState(_ => []) let {sdkHandleConfirmPayment} = Recoil.useRecoilValueFromAtom(optionAtom) - let (paymentMethodListValue, setPaymentMethodListValue) = Recoil.useRecoilState( - PaymentUtils.paymentMethodListValue, - ) + let setPaymentMethodListValue = Recoil.useSetRecoilState(PaymentUtils.paymentMethodListValue) let (cardsContainerWidth, setCardsContainerWidth) = React.useState(_ => 0) let layoutClass = CardUtils.getLayoutClass(layout) let (selectedOption, setSelectedOption) = Recoil.useRecoilState(selectedOptionAtom) @@ -102,7 +100,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod }, [savedMethods]) let (walletList, paymentOptionsList, actualList) = PaymentUtils.useGetPaymentMethodList( - ~paymentMethodListValue, ~paymentOptions, ~paymentType, ) @@ -227,12 +224,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let dict = sessions->getDictFromJson let sessionObj = SessionsType.itemToObjMapper(dict, Others) let klarnaTokenObj = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Klarna) - let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) - let googlePayThirdPartySessionObj = SessionsType.itemToObjMapper(dict, GooglePayThirdPartyObject) - let googlePayThirdPartyToken = SessionsType.getPaymentSessionObj( - googlePayThirdPartySessionObj.sessionsToken, - Gpay, - ) let loader = () => { handlePostMessageEvents( @@ -297,26 +288,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod - | GooglePay => - switch gPayToken { - | OtherTokenOptional(optToken) => - switch googlePayThirdPartyToken { - | GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) => - - - - | _ => - - - - } - | _ => React.null - } | Boleto => diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index ee919556a..47e86699e 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -300,7 +300,7 @@ let make = (~sessionObj: option) => { ->Option.getOr(Dict.make()) ->ApplePayTypes.shippingContactItemToObjMapper - let requiredFieldsBody = DynamicFieldsUtils.getApplePayRequiredFieldsFromBillingAndShippingContact( + let requiredFieldsBody = DynamicFieldsUtils.getApplePayRequiredFields( ~billingContact, ~shippingContact, ~paymentMethodTypes, diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index 47fca8bc3..0bbec5705 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -2,26 +2,18 @@ open Utils open RecoilAtoms open GooglePayType +open Promise @react.component -let make = ( - ~sessionObj: option, - ~thirdPartySessionObj: option, - ~paymentType: option, - ~walletOptions: array, -) => { - let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) +let make = (~sessionObj: option, ~thirdPartySessionObj: option) => { let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom) let {iframeId} = Recoil.useRecoilValueFromAtom(keys) let {publishableKey, sdkHandleOneClickConfirmPayment} = Recoil.useRecoilValueFromAtom(keys) - let {localeString} = Recoil.useRecoilValueFromAtom(configAtom) let options = Recoil.useRecoilValueFromAtom(optionAtom) let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Gpay) let sync = PaymentHelpers.usePaymentSync(Some(loggerState), Gpay) let isGPayReady = Recoil.useRecoilValueFromAtom(isGooglePayReady) let setIsShowOrPayUsing = Recoil.useSetRecoilState(isShowOrPayUsing) - let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) - let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) let status = CommonHooks.useScript("https://pay.google.com/gp/p/js/pay.js") let isGooglePaySDKFlow = React.useMemo(() => { sessionObj->Option.isSome @@ -44,7 +36,6 @@ let make = ( | None => PaymentMethodsRecord.defaultPaymentMethodType } - let isWallet = walletOptions->Array.includes("google_pay") let paymentExperience = switch googlePayPaymentMethodType.payment_experience[0] { | Some(paymentExperience) => paymentExperience.payment_experience_type | None => PaymentMethodsRecord.RedirectToURL @@ -79,12 +70,25 @@ let make = ( ) } - UtilityHooks.useHandlePostMessages( - ~complete=areRequiredFieldsValid, - ~empty=areRequiredFieldsEmpty, - ~paymentType="google_pay", + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", ) + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) + + React.useEffect0(() => { + AddressPaymentInput.importStates("./../States.json") + ->then(res => { + setStatesJson(_ => res.states) + resolve() + }) + ->ignore + + None + }) + React.useEffect(() => { let handle = (ev: Window.event) => { let json = try { @@ -102,6 +106,33 @@ let make = ( ~body=PaymentBody.gpayBody(~payObj=obj, ~connectors), ) + let billingContact = + obj.paymentMethodData.info + ->getDictFromJson + ->Utils.getJsonObjectFromDict("billingAddress") + ->getDictFromJson + ->billingContactItemToObjMapper + + let shippingContact = + metadata + ->getDictFromJson + ->Utils.getJsonObjectFromDict("shippingAddress") + ->getDictFromJson + ->billingContactItemToObjMapper + + let email = + metadata + ->getDictFromJson + ->Utils.getString("email", "") + + let requiredFieldsBody = DynamicFieldsUtils.getGooglePayRequiredFields( + ~billingContact, + ~shippingContact, + ~paymentMethodTypes, + ~statesList=stateJson, + ~email, + ) + let body = { gPayBody ->getJsonFromArrayOfJson @@ -113,14 +144,11 @@ let make = ( } if dict->Dict.get("gpayError")->Option.isSome { Utils.handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) - if !isWallet { - postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong") - } } } Window.addEventListener("message", handle) Some(() => {Window.removeEventListener("message", handle)}) - }, [requiredFieldsBody]) + }, (paymentMethodTypes, stateJson)) let (_, buttonType, _) = options.wallets.style.type_ let (_, heightType, _) = options.wallets.style.height @@ -151,7 +179,6 @@ let make = ( ~paymentMethod="GOOGLE_PAY", (), ) - open Promise makeOneClickHandlerPromise(sdkHandleOneClickConfirmPayment)->then(result => { let result = result->JSON.Decode.bool->Option.getOr(false) if result { @@ -204,10 +231,9 @@ let make = ( React.useEffect(() => { if ( status == "ready" && - (isGPayReady || - isDelayedSessionToken || - paymentExperience == PaymentMethodsRecord.RedirectToURL) && - isWallet + (isGPayReady || + isDelayedSessionToken || + paymentExperience == PaymentMethodsRecord.RedirectToURL) ) { setIsShowOrPayUsing(_ => true) addGooglePayButton() @@ -240,9 +266,7 @@ let make = ( }) let isRenderGooglePayButton = - (isGPayReady || - paymentExperience == PaymentMethodsRecord.RedirectToURL || - isDelayedSessionToken) && isWallet + isGPayReady || paymentExperience == PaymentMethodsRecord.RedirectToURL || isDelayedSessionToken React.useEffect(() => { areOneClickWalletsRendered(prev => { @@ -252,51 +276,13 @@ let make = ( None }, [isRenderGooglePayButton]) - let submitCallback = (ev: Window.event) => { - if !isWallet { - let json = ev.data->JSON.parseExn - let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper - if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty { - handlePostMessage([ - ("fullscreen", true->JSON.Encode.bool), - ("param", "paymentloader"->JSON.Encode.string), - ("iframeId", iframeId->JSON.Encode.string), - ]) - options.readOnly ? () : handlePostMessage([("GpayClicked", true->JSON.Encode.bool)]) - } else if areRequiredFieldsEmpty { - postFailedSubmitResponse( - ~errortype="validation_error", - ~message=localeString.enterFieldsText, - ) - } else if !areRequiredFieldsValid { - postFailedSubmitResponse( - ~errortype="validation_error", - ~message=localeString.enterValidDetailsText, - ) - } - } - } - useSubmitPaymentData(submitCallback) - - { - isWallet - ? -
Belt.Int.toString}px`} - id="google-pay-button" - className={`w-full flex flex-row justify-center rounded-md`} - /> - - : val - | _ => NONE - }} - paymentMethod="wallet" - paymentMethodType="google_pay" - setRequiredFieldsBody - /> - } + +
Belt.Int.toString}px`} + id="google-pay-button" + className={`w-full flex flex-row justify-center rounded-md`} + /> + } let default = make diff --git a/src/Payments/GPay.resi b/src/Payments/GPay.resi index 3e95448d9..d03d071d4 100644 --- a/src/Payments/GPay.resi +++ b/src/Payments/GPay.resi @@ -2,6 +2,4 @@ let default: ( ~sessionObj: option, ~thirdPartySessionObj: option, - ~paymentType: option, - ~walletOptions: array, ) => React.element diff --git a/src/Payments/GPayLazy.res b/src/Payments/GPayLazy.res index 5d4fa241f..c291d2fe6 100644 --- a/src/Payments/GPayLazy.res +++ b/src/Payments/GPayLazy.res @@ -3,8 +3,6 @@ open LazyUtils type props = { sessionObj: option, thirdPartySessionObj: option, - paymentType: CardThemeType.mode, - walletOptions: array, } let make: props => React.element = reactLazy(() => import_("./GPay.bs.js")) diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 0c2109899..ff63350c6 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -74,15 +74,9 @@ let make = (~sessions, ~walletOptions) => { switch googlePayThirdPartyToken { | GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) => - | _ => - + | _ => } | _ => React.null }} diff --git a/src/Types/GooglePayType.res b/src/Types/GooglePayType.res index 23ff7a9ed..b35e60034 100644 --- a/src/Types/GooglePayType.res +++ b/src/Types/GooglePayType.res @@ -11,6 +11,9 @@ type paymentDataRequest = { mutable allowedPaymentMethods: array, mutable transactionInfo: JSON.t, mutable merchantInfo: JSON.t, + mutable shippingAddressRequired: bool, + mutable emailRequired: bool, + mutable shippingAddressParameters: JSON.t, } @val @scope("Object") external assign2: (JSON.t, JSON.t) => paymentDataRequest = "assign" type element = { @@ -64,6 +67,19 @@ type paymentMethodData = { \"type": string, } +type billingContact = { + address1: string, + address2: string, + address3: string, + administrativeArea: string, + countryCode: string, + locality: string, + name: string, + phoneNumber: string, + postalCode: string, + sortingCode: string, +} + type paymentData = {paymentMethodData: paymentMethodData} let defaultTokenizationData = { token: "", @@ -125,3 +141,18 @@ let jsonToPaymentRequestDataType: (paymentDataRequest, Dict.t) => paymen paymentRequest } + +let billingContactItemToObjMapper = dict => { + { + address1: dict->Utils.getString("address1", ""), + address2: dict->Utils.getString("address2", ""), + address3: dict->Utils.getString("address3", ""), + administrativeArea: dict->Utils.getString("administrativeArea", ""), + countryCode: dict->Utils.getString("countryCode", ""), + locality: dict->Utils.getString("locality", ""), + name: dict->Utils.getString("name", ""), + phoneNumber: dict->Utils.getString("phoneNumber", ""), + postalCode: dict->Utils.getString("postalCode", ""), + sortingCode: dict->Utils.getString("sortingCode", ""), + } +} diff --git a/src/Types/SessionsType.res b/src/Types/SessionsType.res index e47d51cc3..833b86f2d 100644 --- a/src/Types/SessionsType.res +++ b/src/Types/SessionsType.res @@ -10,6 +10,9 @@ type token = { allowed_payment_methods: array, transaction_info: JSON.t, merchant_info: JSON.t, + shippingAddressRequired: bool, + emailRequired: bool, + shippingAddressParameters: JSON.t, } type tokenType = @@ -33,6 +36,9 @@ let defaultToken = { allowed_payment_methods: [], transaction_info: Dict.make()->JSON.Encode.object, merchant_info: Dict.make()->JSON.Encode.object, + shippingAddressRequired: false, + emailRequired: false, + shippingAddressParameters: Dict.make()->JSON.Encode.object, } let getWallet = str => { switch str { @@ -59,6 +65,9 @@ let getSessionsToken = (dict, str) => { allowed_payment_methods: getArray(dict, "allowed_payment_methods"), transaction_info: getJsonObjectFromDict(dict, "transaction_info"), merchant_info: getJsonObjectFromDict(dict, "merchant_info"), + shippingAddressRequired: getBool(dict, "shipping_address_required", false), + emailRequired: getBool(dict, "email_required", false), + shippingAddressParameters: getJsonObjectFromDict(dict, "shipping_address_parameters"), } }) }) diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 2ef60b093..9a1f68bce 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -739,7 +739,7 @@ let removeRequiredFieldsDuplicates = ( requiredFields } -let getApplePayRequiredFieldsFromBillingAndShippingContact = ( +let getApplePayRequiredFields = ( ~billingContact: ApplePayTypes.billingContact, ~shippingContact: ApplePayTypes.shippingContact, ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, @@ -753,10 +753,14 @@ let getApplePayRequiredFieldsFromBillingAndShippingContact = ( } let getName = { + let firstName = billingContact.givenName->getBillingOrShipping(shippingContact.givenName) + let lastName = billingContact.familyName->getBillingOrShipping(shippingContact.familyName) + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { - | "last_name" => billingContact.familyName->getBillingOrShipping(shippingContact.familyName) - | _ => billingContact.givenName->getBillingOrShipping(shippingContact.givenName) - } + | "first_name" => firstName + | "last_name" => lastName + | _ => firstName->String.concatMany([" ", lastName]) + }->String.trim } let getAddressLine = (addressLines, index) => { @@ -765,7 +769,6 @@ let getApplePayRequiredFieldsFromBillingAndShippingContact = ( let fieldVal = switch item.field_type { | FullName => getName - | Country => billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) | BillingName => getName | AddressLine1 => let billingAddressLine1 = billingContact.addressLines->getAddressLine(0) @@ -782,6 +785,7 @@ let getApplePayRequiredFieldsFromBillingAndShippingContact = ( let countryCode = billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + | Country | AddressCountry(_) => billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) | AddressPincode => billingContact.postalCode->getBillingOrShipping(shippingContact.postalCode) @@ -797,3 +801,71 @@ let getApplePayRequiredFieldsFromBillingAndShippingContact = ( acc }) } + +let getGooglePayRequiredFields = ( + ~billingContact: GooglePayType.billingContact, + ~shippingContact: GooglePayType.billingContact, + ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~statesList, + ~email, +) => { + paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + let requiredFieldsArr = item.required_field->String.split(".") + + let getBillingOrShipping = (billingVal, shippingVal) => { + requiredFieldsArr->Array.includes("billing") ? billingVal : shippingVal + } + + let getName = { + let name = billingContact.name->getBillingOrShipping(shippingContact.name) + + let nameArr = name->String.split(" ") + let nameArrLength = nameArr->Array.length + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { + | "first_name" => { + let end = nameArrLength === 1 ? nameArrLength : nameArrLength - 1 + nameArr + ->Array.slice(~start=0, ~end) + ->Array.reduce("", (acc, item) => { + acc ++ " " ++ item + }) + } + | "last_name" => + if nameArrLength === 1 { + "" + } else { + nameArr->Array.get(nameArrLength - 1)->Option.getOr(name) + } + | _ => name + }->String.trim + } + + let fieldVal = switch item.field_type { + | FullName => getName + | BillingName => getName + | AddressLine1 => billingContact.address1->getBillingOrShipping(shippingContact.address1) + | AddressLine2 => billingContact.address2->getBillingOrShipping(shippingContact.address2) + | AddressCity => billingContact.locality->getBillingOrShipping(shippingContact.locality) + | AddressState => + let administrativeArea = + billingContact.administrativeArea->getBillingOrShipping(shippingContact.administrativeArea) + let countryCode = + billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) + Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + | Country + | AddressCountry(_) => + billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) + | AddressPincode => billingContact.postalCode->getBillingOrShipping(shippingContact.postalCode) + | Email => email + | PhoneNumber => + shippingContact.phoneNumber->String.replaceAll(" ", "")->String.replaceAll("-", "") + | _ => "" + } + + if fieldVal !== "" { + acc->Dict.set(item.required_field, fieldVal->JSON.Encode.string) + } + + acc + }) +} diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index b63c66903..7f49fa152 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -3,8 +3,6 @@ let paymentMethodListValue = Recoil.atom("paymentMethodListValue", PaymentMethod let paymentListLookupNew = ( list: PaymentMethodsRecord.paymentMethodList, ~order, - ~showGooglePay, - ~areAllGooglePayRequiredFieldsPrefilled, ~isShowPaypal, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList @@ -25,14 +23,6 @@ let paymentListLookupNew = ( "samsung_pay", ] let otherPaymentList = [] - let googlePayFields = pmList->Array.find(item => item.paymentMethodName === "google_pay") - switch googlePayFields { - | Some(val) => - if val.fields->Array.length > 0 && showGooglePay && !areAllGooglePayRequiredFieldsPrefilled { - walletToBeDisplayedInTabs->Array.push("google_pay")->ignore - } - | None => () - } pmList->Array.forEach(item => { if walletToBeDisplayedInTabs->Array.includes(item.paymentMethodName) { @@ -266,25 +256,17 @@ let useAreAllRequiredFieldsPrefilled = ( }) } -let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymentType) => { +let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { open Utils let methodslist = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentMethodList) let {showCardFormByDefault, paymentMethodOrder} = Recoil.useRecoilValueFromAtom( RecoilAtoms.optionAtom, ) - - let isGooglePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) let optionAtomValue = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let paymentOrder = paymentMethodOrder->getOptionalArr->removeDuplicate - let areAllGooglePayRequiredFieldsPrefilled = useAreAllRequiredFieldsPrefilled( - ~paymentMethodListValue, - ~paymentMethod="wallet", - ~paymentMethodType="google_pay", - ) - React.useMemo(() => { switch methodslist { | Loaded(paymentlist) => @@ -294,8 +276,6 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen let (wallets, otherOptions) = plist->paymentListLookupNew( ~order=paymentOrder, - ~showGooglePay=isGooglePayReady, - ~areAllGooglePayRequiredFieldsPrefilled, ~isShowPaypal=optionAtomValue.wallets.payPal === Auto, ) ( @@ -309,12 +289,5 @@ let useGetPaymentMethodList = (~paymentMethodListValue, ~paymentOptions, ~paymen : ([], [], []) | _ => ([], [], []) } - }, ( - methodslist, - paymentMethodOrder, - isGooglePayReady, - areAllGooglePayRequiredFieldsPrefilled, - optionAtomValue.wallets.payPal, - paymentType, - )) + }, (methodslist, paymentMethodOrder, optionAtomValue.wallets.payPal, paymentType)) } diff --git a/src/WalletElement.res b/src/WalletElement.res index 741491e49..6cc3fa1a7 100644 --- a/src/WalletElement.res +++ b/src/WalletElement.res @@ -5,15 +5,9 @@ let make = (~paymentType) => { let (sessions, setSessions) = React.useState(_ => Dict.make()->JSON.Encode.object) let (walletOptions, setWalletOptions) = React.useState(_ => []) - let (paymentMethodListValue, setPaymentMethodListValue) = Recoil.useRecoilState( - PaymentUtils.paymentMethodListValue, - ) + let setPaymentMethodListValue = Recoil.useSetRecoilState(PaymentUtils.paymentMethodListValue) - let (walletList, _, _) = PaymentUtils.useGetPaymentMethodList( - ~paymentMethodListValue, - ~paymentOptions=[], - ~paymentType, - ) + let (walletList, _, _) = PaymentUtils.useGetPaymentMethodList(~paymentOptions=[], ~paymentType) React.useEffect(() => { switch methodslist { diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 58ae31bd7..0a416ab2d 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -840,6 +840,11 @@ let make = ( paymentDataRequest.transactionInfo = gpayobj.transaction_info->transformKeys(CamelCase) paymentDataRequest.merchantInfo = gpayobj.merchant_info->transformKeys(CamelCase) + paymentDataRequest.shippingAddressRequired = gpayobj.shippingAddressRequired + paymentDataRequest.emailRequired = gpayobj.emailRequired + paymentDataRequest.shippingAddressParameters = + gpayobj.shippingAddressParameters->transformKeys(CamelCase) + try { let gPayClient = GooglePayType.google( { From f8f23441881116b7ba4cf494856cf3b4234f3648 Mon Sep 17 00:00:00 2001 From: Arush Date: Thu, 9 May 2024 06:15:09 +0530 Subject: [PATCH 3/6] feat: added support for collecting shipping details via PayPal Braintree SDK --- src/Payments/PayPal.res | 2 +- src/Payments/PaypalSDK.res | 72 +++++++++++++++++------ src/Types/PaypalSDKTypes.res | 52 +++++++++++++++- src/Types/SessionsType.res | 3 + src/Utilities/DynamicFieldsUtils.res | 88 ++++++++++++++++++++++------ 5 files changed, 177 insertions(+), 40 deletions(-) diff --git a/src/Payments/PayPal.res b/src/Payments/PayPal.res index e1713d423..d82417def 100644 --- a/src/Payments/PayPal.res +++ b/src/Payments/PayPal.res @@ -3,7 +3,7 @@ open RecoilAtoms module Loader = { @react.component let make = () => { -
+
} diff --git a/src/Payments/PaypalSDK.res b/src/Payments/PaypalSDK.res index 45fc32d5a..5395ff3cb 100644 --- a/src/Payments/PaypalSDK.res +++ b/src/Payments/PaypalSDK.res @@ -1,3 +1,6 @@ +open PaypalSDKTypes +open Promise + @react.component let make = (~sessionObj: SessionsType.token) => { let {iframeId, publishableKey, sdkHandleOneClickConfirmPayment} = Recoil.useRecoilValueFromAtom( @@ -8,16 +11,18 @@ let make = (~sessionObj: SessionsType.token) => { let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) let token = sessionObj.token + let orderDetails = sessionObj.orderDetails->getOrderDetails let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Paypal) let checkoutScript = Window.document(Window.window)->Window.getElementById("braintree-checkout")->Nullable.toOption let clientScript = Window.document(Window.window)->Window.getElementById("braintree-client")->Nullable.toOption + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) + let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let (_, _, buttonType) = options.wallets.style.type_ let (_, _, heightType) = options.wallets.style.height - open PaypalSDKTypes let buttonStyle = { layout: "vertical", color: options.wallets.style.theme == Outline @@ -37,6 +42,24 @@ let make = (~sessionObj: SessionsType.token) => { } let handleCloseLoader = () => Utils.handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) let isGuestCustomer = UtilityHooks.useIsGuestCustomer() + + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="paypal", + ) + + React.useEffect0(() => { + AddressPaymentInput.importStates("./../States.json") + ->then(res => { + setStatesJson(_ => res.states) + resolve() + }) + ->ignore + + None + }) + let loadPaypalSdk = () => { loggerState.setLogInfo( ~value="Paypal SDK Button Clicked", @@ -44,7 +67,6 @@ let make = (~sessionObj: SessionsType.token) => { ~paymentMethod="PAYPAL", (), ) - open Promise Utils.makeOneClickHandlerPromise(sdkHandleOneClickConfirmPayment) ->then(result => { let result = result->JSON.Decode.bool->Option.getOr(false) @@ -75,12 +97,7 @@ let make = (~sessionObj: SessionsType.token) => { ("param", "paymentloader"->JSON.Encode.string), ("iframeId", iframeId->JSON.Encode.string), ]) - options.readOnly - ? () - : paypalCheckoutInstance.createPayment({ - ...defaultOrderDetails, - flow: "vault", - }) + options.readOnly ? () : paypalCheckoutInstance.createPayment(orderDetails) }, onApprove: (data, _actions) => { options.readOnly @@ -96,10 +113,25 @@ let make = (~sessionObj: SessionsType.token) => { ~token=payload.nonce, ~connectors, ) + + let requiredFieldsBody = DynamicFieldsUtils.getPaypalRequiredFields( + ~details=payload.details, + ~paymentMethodTypes, + ~statesList=stateJson, + ) + + let paypalBody = + body + ->Dict.fromArray + ->JSON.Encode.object + ->Utils.flattenObject(true) + ->Utils.mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->Utils.getArrayOfTupleFromDict + let modifiedPaymentBody = PaymentUtils.appendedCustomerAcceptance( ~isGuestCustomer, ~paymentType=paymentMethodListValue.payment_type, - ~body, + ~body=paypalBody, ) intent( @@ -137,19 +169,21 @@ let make = (~sessionObj: SessionsType.token) => { }) ->ignore } - React.useEffect0(() => { - if true { - try { - switch (checkoutScript, clientScript) { - | (Some(_), Some(_)) => loadPaypalSdk() - | (_, _) => Utils.logInfo(Console.log("Error loading Paypal")) - } - } catch { - | _err => Utils.logInfo(Console.log("Error loading Paypal")) + React.useEffect(() => { + try { + switch ( + checkoutScript, + clientScript, + stateJson->Identity.jsonToNullableJson->Js.Nullable.isNullable, + ) { + | (Some(_), Some(_), false) => loadPaypalSdk() + | (_, _, _) => Utils.logInfo(Console.log("Error loading Paypal")) } + } catch { + | _err => Utils.logInfo(Console.log("Error loading Paypal")) } None - }) + }, [stateJson])
} diff --git a/src/Types/PaypalSDKTypes.res b/src/Types/PaypalSDKTypes.res index 4f7089133..98d845bc3 100644 --- a/src/Types/PaypalSDKTypes.res +++ b/src/Types/PaypalSDKTypes.res @@ -4,7 +4,6 @@ type paypalCheckoutErr = {message: string} type data type actions type err -type payload = {nonce: string} type vault = {vault: bool} type shipping = { recipientName: option, @@ -16,6 +15,12 @@ type shipping = { state: option, phone: option, } +type details = { + email: string, + shippingAddress: shipping, + phone: option, +} +type payload = {nonce: string, details: details} type orderDetails = { flow: string, billingAgreementDescription: option, @@ -87,3 +92,48 @@ type paypal = {"Buttons": buttons => some, "FUNDING": funding} @val external braintree: braintree = "braintree" @val external paypal: paypal = "paypal" + +let getShippingDetails = shippingAddressOverrideObj => { + let shippingAddressOverride = shippingAddressOverrideObj->Utils.getDictFromJson + + let recipientName = shippingAddressOverride->Utils.getOptionString("recipient_name") + let line1 = shippingAddressOverride->Utils.getOptionString("line1") + let line2 = shippingAddressOverride->Utils.getOptionString("line2") + let city = shippingAddressOverride->Utils.getOptionString("city") + let countryCode = shippingAddressOverride->Utils.getOptionString("country_code") + let postalCode = shippingAddressOverride->Utils.getOptionString("postal_code") + let state = shippingAddressOverride->Utils.getOptionString("state") + let phone = shippingAddressOverride->Utils.getOptionString("phone") + + if ( + [recipientName, line1, line2, city, countryCode, postalCode, state, phone]->Array.includes(None) + ) { + None + } else { + Some({ + recipientName, + line1, + line2, + city, + countryCode, + postalCode, + state, + phone, + }) + } +} + +let getOrderDetails = orderDetails => { + let orderDetailsDict = orderDetails->Utils.getDictFromJson + + let shippingAddressOverride = + orderDetailsDict->Utils.getJsonObjectFromDict("shipping_address_override")->getShippingDetails + + { + flow: orderDetailsDict->Utils.getString("flow", "vault"), + billingAgreementDescription: None, + enableShippingAddress: orderDetailsDict->Utils.getOptionBool("enable_shipping_address"), + shippingAddressEditable: None, + shippingAddressOverride, + } +} diff --git a/src/Types/SessionsType.res b/src/Types/SessionsType.res index 833b86f2d..395a0b699 100644 --- a/src/Types/SessionsType.res +++ b/src/Types/SessionsType.res @@ -13,6 +13,7 @@ type token = { shippingAddressRequired: bool, emailRequired: bool, shippingAddressParameters: JSON.t, + orderDetails: JSON.t, } type tokenType = @@ -39,6 +40,7 @@ let defaultToken = { shippingAddressRequired: false, emailRequired: false, shippingAddressParameters: Dict.make()->JSON.Encode.object, + orderDetails: Dict.make()->JSON.Encode.object, } let getWallet = str => { switch str { @@ -68,6 +70,7 @@ let getSessionsToken = (dict, str) => { shippingAddressRequired: getBool(dict, "shipping_address_required", false), emailRequired: getBool(dict, "email_required", false), shippingAddressParameters: getJsonObjectFromDict(dict, "shipping_address_parameters"), + orderDetails: getJsonObjectFromDict(dict, "order_details"), } }) }) diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 9a1f68bce..b452fe0ee 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -802,6 +802,28 @@ let getApplePayRequiredFields = ( }) } +let getNameFromString = (name, requiredFieldsArr) => { + let nameArr = name->String.split(" ") + let nameArrLength = nameArr->Array.length + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { + | "first_name" => { + let end = nameArrLength === 1 ? nameArrLength : nameArrLength - 1 + nameArr + ->Array.slice(~start=0, ~end) + ->Array.reduce("", (acc, item) => { + acc ++ " " ++ item + }) + } + | "last_name" => + if nameArrLength === 1 { + "" + } else { + nameArr->Array.get(nameArrLength - 1)->Option.getOr(name) + } + | _ => name + }->String.trim +} + let getGooglePayRequiredFields = ( ~billingContact: GooglePayType.billingContact, ~shippingContact: GooglePayType.billingContact, @@ -819,25 +841,7 @@ let getGooglePayRequiredFields = ( let getName = { let name = billingContact.name->getBillingOrShipping(shippingContact.name) - let nameArr = name->String.split(" ") - let nameArrLength = nameArr->Array.length - switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { - | "first_name" => { - let end = nameArrLength === 1 ? nameArrLength : nameArrLength - 1 - nameArr - ->Array.slice(~start=0, ~end) - ->Array.reduce("", (acc, item) => { - acc ++ " " ++ item - }) - } - | "last_name" => - if nameArrLength === 1 { - "" - } else { - nameArr->Array.get(nameArrLength - 1)->Option.getOr(name) - } - | _ => name - }->String.trim + name->getNameFromString(requiredFieldsArr) } let fieldVal = switch item.field_type { @@ -869,3 +873,49 @@ let getGooglePayRequiredFields = ( acc }) } + +let getPaypalRequiredFields = ( + ~details: PaypalSDKTypes.details, + ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~statesList, +) => { + paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + let requiredFieldsArr = item.required_field->String.split(".") + + let getName = { + let name = details.shippingAddress.recipientName->Option.getOr("") + + name->getNameFromString(requiredFieldsArr) + } + + let fieldVal = switch item.field_type { + | FullName => getName + | BillingName => getName + | AddressLine1 => details.shippingAddress.line1->Option.getOr("") + | AddressLine2 => details.shippingAddress.line2->Option.getOr("") + | AddressCity => details.shippingAddress.city->Option.getOr("") + | AddressState => + let administrativeArea = details.shippingAddress.state->Option.getOr("") + let countryCode = details.shippingAddress.countryCode->Option.getOr("") + Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + | Country + | AddressCountry(_) => + details.shippingAddress.countryCode->Option.getOr("") + | AddressPincode => details.shippingAddress.postalCode->Option.getOr("") + | Email => details.email + | PhoneNumber => + switch (details.phone->Option.getOr(""), details.shippingAddress.phone->Option.getOr("")) { + | (phone, "") => phone + | ("", phone) => phone + | _ => "" + } + | _ => "" + } + + if fieldVal !== "" { + acc->Dict.set(item.required_field, fieldVal->JSON.Encode.string) + } + + acc + }) +} From f587c8909846722b4dc64a7122c2d31b493ec0d3 Mon Sep 17 00:00:00 2001 From: Arush Date: Fri, 10 May 2024 00:25:16 +0530 Subject: [PATCH 4/6] fix: added support for collecting shipping details only via PaymentRequestButtonsElement --- src/PaymentElement.res | 2 +- src/PaymentElementRenderer.res | 4 +- src/Payments/PaymentRequestButtonElement.res | 4 +- src/Payments/PaypalSDK.res | 4 +- src/Payments/PaypalSDK.resi | 2 +- src/Payments/PaypalSDKLazy.res | 2 +- src/RenderPaymentMethods.res | 2 +- src/Types/PaypalSDKTypes.res | 15 ++++++-- src/Utilities/Utils.res | 8 +++- src/WalletElement.res | 2 +- src/orca-loader/Elements.res | 39 +++++++++++++++++--- 11 files changed, 61 insertions(+), 23 deletions(-) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index 808bd4f03..b5eebb301 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -344,7 +344,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod showFields}>
- + Array.length > 0 && diff --git a/src/PaymentElementRenderer.res b/src/PaymentElementRenderer.res index 8541fa69d..1bc1a9f82 100644 --- a/src/PaymentElementRenderer.res +++ b/src/PaymentElementRenderer.res @@ -15,12 +15,12 @@ let make = ( switch (sessions, paymentMethodList) { | (_, Loading) => - {paymentType->Utils.isWalletElementPaymentType + {paymentType->Utils.getIsWalletElementPaymentType ? : } | _ => - paymentType->Utils.isWalletElementPaymentType + paymentType->Utils.getIsWalletElementPaymentType ? : } diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index ff63350c6..0c44ede94 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -36,7 +36,7 @@ module WalletsSaveDetailsText = { } @react.component -let make = (~sessions, ~walletOptions) => { +let make = (~sessions, ~walletOptions, ~paymentType) => { open SessionsType let dict = sessions->Utils.getDictFromJson let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) @@ -86,7 +86,7 @@ let make = (~sessions, ~walletOptions) => { {switch paypalToken { | OtherTokenOptional(optToken) => switch optToken { - | Some(token) => + | Some(token) => | None => } | _ => diff --git a/src/Payments/PaypalSDK.res b/src/Payments/PaypalSDK.res index 5395ff3cb..71477276e 100644 --- a/src/Payments/PaypalSDK.res +++ b/src/Payments/PaypalSDK.res @@ -2,7 +2,7 @@ open PaypalSDKTypes open Promise @react.component -let make = (~sessionObj: SessionsType.token) => { +let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) => { let {iframeId, publishableKey, sdkHandleOneClickConfirmPayment} = Recoil.useRecoilValueFromAtom( RecoilAtoms.keys, ) @@ -11,7 +11,7 @@ let make = (~sessionObj: SessionsType.token) => { let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) let token = sessionObj.token - let orderDetails = sessionObj.orderDetails->getOrderDetails + let orderDetails = sessionObj.orderDetails->getOrderDetails(paymentType) let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Paypal) let checkoutScript = Window.document(Window.window)->Window.getElementById("braintree-checkout")->Nullable.toOption diff --git a/src/Payments/PaypalSDK.resi b/src/Payments/PaypalSDK.resi index 975369c2c..cb375a27e 100644 --- a/src/Payments/PaypalSDK.resi +++ b/src/Payments/PaypalSDK.resi @@ -1,2 +1,2 @@ @react.component -let default: (~sessionObj: SessionsType.token) => React.element +let default: (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) => React.element diff --git a/src/Payments/PaypalSDKLazy.res b/src/Payments/PaypalSDKLazy.res index c3be96f97..9d978b196 100644 --- a/src/Payments/PaypalSDKLazy.res +++ b/src/Payments/PaypalSDKLazy.res @@ -1,5 +1,5 @@ open LazyUtils -type props = {sessionObj: SessionsType.token} +type props = {sessionObj: SessionsType.token, paymentType: CardThemeType.mode} let make: props => React.element = reactLazy(() => import_("./PaypalSDK.bs.js")) diff --git a/src/RenderPaymentMethods.res b/src/RenderPaymentMethods.res index 500affcf1..b6f788e49 100644 --- a/src/RenderPaymentMethods.res +++ b/src/RenderPaymentMethods.res @@ -85,7 +85,7 @@ let make = ( | Payment => - {paymentType->Utils.isWalletElementPaymentType + {paymentType->Utils.getIsWalletElementPaymentType ? : } }> diff --git a/src/Types/PaypalSDKTypes.res b/src/Types/PaypalSDKTypes.res index 98d845bc3..dd44f1ec5 100644 --- a/src/Types/PaypalSDKTypes.res +++ b/src/Types/PaypalSDKTypes.res @@ -123,16 +123,23 @@ let getShippingDetails = shippingAddressOverrideObj => { } } -let getOrderDetails = orderDetails => { +let getOrderDetails = (orderDetails, paymentType) => { let orderDetailsDict = orderDetails->Utils.getDictFromJson - let shippingAddressOverride = - orderDetailsDict->Utils.getJsonObjectFromDict("shipping_address_override")->getShippingDetails + let isWalletElementPaymentType = paymentType->Utils.getIsWalletElementPaymentType + + let shippingAddressOverride = isWalletElementPaymentType + ? orderDetailsDict->Utils.getJsonObjectFromDict("shipping_address_override")->getShippingDetails + : None + + let enableShippingAddress = isWalletElementPaymentType + ? orderDetailsDict->Utils.getOptionBool("enable_shipping_address") + : None { flow: orderDetailsDict->Utils.getString("flow", "vault"), billingAgreementDescription: None, - enableShippingAddress: orderDetailsDict->Utils.getOptionBool("enable_shipping_address"), + enableShippingAddress, shippingAddressEditable: None, shippingAddressOverride, } diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 173ac7359..9f6c6260d 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1228,7 +1228,11 @@ let expressCheckoutComponents = ["googlePay", "payPal", "applePay", "paymentRequ let componentsForPaymentElementCreate = ["payment"]->Array.concat(expressCheckoutComponents) -let isComponentTypeForPaymentElementCreate = componentType => { +let getIsExpressCheckoutComponent = componentType => { + expressCheckoutComponents->Array.includes(componentType) +} + +let getIsComponentTypeForPaymentElementCreate = componentType => { componentsForPaymentElementCreate->Array.includes(componentType) } @@ -1239,7 +1243,7 @@ let walletElementPaymentType: array = [ PaymentRequestButtonsElement, ] -let isWalletElementPaymentType = (paymentType: CardThemeType.mode) => { +let getIsWalletElementPaymentType = (paymentType: CardThemeType.mode) => { walletElementPaymentType->Array.includes(paymentType) } diff --git a/src/WalletElement.res b/src/WalletElement.res index 6cc3fa1a7..3e22e8e3e 100644 --- a/src/WalletElement.res +++ b/src/WalletElement.res @@ -30,7 +30,7 @@ let make = (~paymentType) => { Array.length > 0}>
- +
diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 0a416ab2d..befecebda 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -270,7 +270,7 @@ let make = ( [ ( "paymentElementCreate", - componentType->isComponentTypeForPaymentElementCreate->JSON.Encode.bool, + componentType->getIsComponentTypeForPaymentElementCreate->JSON.Encode.bool, ), ("otherElements", otherElements->JSON.Encode.bool), ("options", newOptions), @@ -694,7 +694,7 @@ let make = ( ->then(res => { let (json, applePayPresent, googlePayPresent) = res if ( - componentType->isComponentTypeForPaymentElementCreate && + componentType->getIsComponentTypeForPaymentElementCreate && applePayPresent->Belt.Option.isSome ) { //do operations here @@ -738,6 +738,30 @@ let make = ( ->Belt.Option.getWithDefault(Dict.make()->JSON.Encode.object) ->transformKeys(CamelCase) + let requiredShippingContactFields = + paymentRequest + ->Utils.getDictFromJson + ->Utils.getStrArray("requiredShippingContactFields") + + if ( + componentType->getIsExpressCheckoutComponent->not && + requiredShippingContactFields->Array.length !== 0 + ) { + let requiredShippingContactFields = + requiredShippingContactFields->Array.filter(item => + item !== "postalAddress" + ) + + paymentRequest + ->Utils.getDictFromJson + ->Dict.set( + "requiredShippingContactFields", + requiredShippingContactFields + ->Utils.getArrofJsonString + ->JSON.Encode.array, + ) + } + let ssn = applePaySession(3, paymentRequest) switch applePaySessionRef.contents->Nullable.toOption { | Some(session) => @@ -801,7 +825,7 @@ let make = ( addSmartEventListener("message", handleApplePayMessages, "onApplePayMessages") } if ( - componentType->isComponentTypeForPaymentElementCreate && + componentType->getIsComponentTypeForPaymentElementCreate && googlePayPresent->Belt.Option.isSome && wallets.googlePay === Auto ) { @@ -840,10 +864,13 @@ let make = ( paymentDataRequest.transactionInfo = gpayobj.transaction_info->transformKeys(CamelCase) paymentDataRequest.merchantInfo = gpayobj.merchant_info->transformKeys(CamelCase) - paymentDataRequest.shippingAddressRequired = gpayobj.shippingAddressRequired paymentDataRequest.emailRequired = gpayobj.emailRequired - paymentDataRequest.shippingAddressParameters = - gpayobj.shippingAddressParameters->transformKeys(CamelCase) + + if componentType->getIsExpressCheckoutComponent { + paymentDataRequest.shippingAddressRequired = gpayobj.shippingAddressRequired + paymentDataRequest.shippingAddressParameters = + gpayobj.shippingAddressParameters->transformKeys(CamelCase) + } try { let gPayClient = GooglePayType.google( From 3aa20e15affcafdb075a698c2be93846001b7385 Mon Sep 17 00:00:00 2001 From: Arush Date: Tue, 14 May 2024 15:01:30 +0530 Subject: [PATCH 5/6] fix: added support for ShippingAddress enums for Dynamic Fields --- src/Components/DynamicFields.res | 16 ++- src/Payments/PaymentMethodsRecord.res | 45 ++++++-- src/Utilities/DynamicFieldsUtils.res | 145 ++++++++++++++------------ 3 files changed, 130 insertions(+), 76 deletions(-) diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 30189d19c..638a2097e 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -484,7 +484,14 @@ let make = ( | BlikCode | SpecialField(_) | CountryAndPincode(_) - | AddressCountry(_) => React.null + | AddressCountry(_) + | ShippingName // Shipping Details are currently supported by only one click widgets + | ShippingAddressLine1 + | ShippingAddressLine2 + | ShippingAddressCity + | ShippingAddressPincode + | ShippingAddressState + | ShippingAddressCountry(_) => React.null }}
}) @@ -757,6 +764,13 @@ let make = ( | CardExpiryAndCvc | Currency(_) | FullName + | ShippingName // Shipping Details are currently supported by only one click widgets + | ShippingAddressLine1 + | ShippingAddressLine2 + | ShippingAddressCity + | ShippingAddressPincode + | ShippingAddressState + | ShippingAddressCountry(_) | None => React.null }}
diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 4e0b7815c..b63bdc2da 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -27,6 +27,13 @@ type paymentMethodsFields = | CardExpiryMonthAndYear | CardCvc | CardExpiryAndCvc + | ShippingName + | ShippingAddressLine1 + | ShippingAddressLine2 + | ShippingAddressCity + | ShippingAddressPincode + | ShippingAddressState + | ShippingAddressCountry(array) let getPaymentMethodsFieldsOrder = paymentMethodField => { switch paymentMethodField { @@ -526,21 +533,31 @@ let getPaymentMethodsFieldTypeFromString = (str, isBancontact) => { | ("user_card_expiry_month", true) => CardExpiryMonth | ("user_card_expiry_year", true) => CardExpiryYear | ("user_card_cvc", true) => CardCvc + | ("user_shipping_name", _) => ShippingName + | ("user_shipping_address_line1", _) => ShippingAddressLine1 + | ("user_shipping_address_line2", _) => ShippingAddressLine2 + | ("user_shipping_address_city", _) => ShippingAddressCity + | ("user_shipping_address_pincode", _) => ShippingAddressPincode + | ("user_shipping_address_state", _) => ShippingAddressState | _ => None } } -let getOptionsFromPaymentMethodFieldType = (dict, key) => { +let getOptionsFromPaymentMethodFieldType = (dict, key, ~isAddressCountry=true) => { let options = dict->Utils.getArrayValFromJsonDict(key, "options") switch options->Array.get(0)->Option.getOr("") { | "" => None - | "ALL" => AddressCountry(Country.country->Array.map(item => item.countryName)) - | _ => - AddressCountry( - Country.country - ->Array.filter(item => options->Array.includes(item.isoAlpha2)) - ->Array.map(item => item.countryName), - ) + | "ALL" => { + let countryArr = Country.country->Array.map(item => item.countryName) + isAddressCountry ? AddressCountry(countryArr) : ShippingAddressCountry(countryArr) + } + | _ => { + let countryArr = + Country.country + ->Array.filter(item => options->Array.includes(item.isoAlpha2)) + ->Array.map(item => item.countryName) + isAddressCountry ? AddressCountry(countryArr) : ShippingAddressCountry(countryArr) + } } } @@ -554,6 +571,11 @@ let getPaymentMethodsFieldTypeFromDict = dict => { } | "user_country" => dict->getOptionsFromPaymentMethodFieldType("user_country") | "user_address_country" => dict->getOptionsFromPaymentMethodFieldType("user_address_country") + | "user_shipping_address_country" => + dict->getOptionsFromPaymentMethodFieldType( + "user_shipping_address_country", + ~isAddressCountry=false, + ) | _ => None } } @@ -1057,5 +1079,12 @@ let paymentMethodFieldToStrMapper = (field: paymentMethodsFields) => { | CardExpiryMonthAndYear => "CardExpiryMonthAndYear" | CardCvc => "CardCvc" | CardExpiryAndCvc => "CardExpiryAndCvc" + | ShippingName => "ShippingName" + | ShippingAddressLine1 => "ShippingAddressLine1" + | ShippingAddressLine2 => "ShippingAddressLine2" + | ShippingAddressCity => "ShippingAddressCity" + | ShippingAddressPincode => "ShippingAddressPincode" + | ShippingAddressState => "ShippingAddressState" + | ShippingAddressCountry(_) => "ShippingAddressCountry" } } diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index b452fe0ee..8477c2cdb 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -361,6 +361,13 @@ let useSetInitialRequiredFields = ( | CardExpiryMonthAndYear | CardCvc | CardExpiryAndCvc + | ShippingName // Shipping Details are currently supported by only one click widgets + | ShippingAddressLine1 + | ShippingAddressLine2 + | ShippingAddressCity + | ShippingAddressPincode + | ShippingAddressState + | ShippingAddressCountry(_) | None => () } }) @@ -435,6 +442,13 @@ let useRequiredFieldsBody = ( | CardExpiryMonthAndYear | CardExpiryAndCvc | FullName + | ShippingName // Shipping Details are currently supported by only one click widgets + | ShippingAddressLine1 + | ShippingAddressLine2 + | ShippingAddressCity + | ShippingAddressPincode + | ShippingAddressState + | ShippingAddressCountry(_) | None => "" } } @@ -748,14 +762,7 @@ let getApplePayRequiredFields = ( paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { let requiredFieldsArr = item.required_field->String.split(".") - let getBillingOrShipping = (billingVal, shippingVal) => { - requiredFieldsArr->Array.includes("billing") ? billingVal : shippingVal - } - - let getName = { - let firstName = billingContact.givenName->getBillingOrShipping(shippingContact.givenName) - let lastName = billingContact.familyName->getBillingOrShipping(shippingContact.familyName) - + let getName = (firstName, lastName) => { switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { | "first_name" => firstName | "last_name" => lastName @@ -768,29 +775,36 @@ let getApplePayRequiredFields = ( } let fieldVal = switch item.field_type { - | FullName => getName - | BillingName => getName - | AddressLine1 => - let billingAddressLine1 = billingContact.addressLines->getAddressLine(0) - let shippingAddressLine1 = shippingContact.addressLines->getAddressLine(0) - billingAddressLine1->getBillingOrShipping(shippingAddressLine1) - | AddressLine2 => - let billingAddressLine1 = billingContact.addressLines->getAddressLine(1) - let shippingAddressLine1 = shippingContact.addressLines->getAddressLine(1) - billingAddressLine1->getBillingOrShipping(shippingAddressLine1) - | AddressCity => billingContact.locality->getBillingOrShipping(shippingContact.locality) + | FullName + | BillingName => + getName(billingContact.givenName, billingContact.familyName) + | AddressLine1 => billingContact.addressLines->getAddressLine(0) + | AddressLine2 => billingContact.addressLines->getAddressLine(1) + | AddressCity => billingContact.locality | AddressState => - let administrativeArea = - billingContact.administrativeArea->getBillingOrShipping(shippingContact.administrativeArea) - let countryCode = - billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) - Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + Utils.getStateNameFromStateCodeAndCountry( + statesList, + billingContact.administrativeArea, + billingContact.countryCode, + ) | Country | AddressCountry(_) => - billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) - | AddressPincode => billingContact.postalCode->getBillingOrShipping(shippingContact.postalCode) + billingContact.countryCode + | AddressPincode => billingContact.postalCode | Email => shippingContact.emailAddress | PhoneNumber => shippingContact.phoneNumber + | ShippingName => getName(shippingContact.givenName, shippingContact.familyName) + | ShippingAddressLine1 => shippingContact.addressLines->getAddressLine(0) + | ShippingAddressLine2 => shippingContact.addressLines->getAddressLine(1) + | ShippingAddressCity => shippingContact.locality + | ShippingAddressState => + Utils.getStateNameFromStateCodeAndCountry( + statesList, + shippingContact.administrativeArea, + shippingContact.countryCode, + ) + | ShippingAddressCountry(_) => shippingContact.countryCode + | ShippingAddressPincode => shippingContact.postalCode | _ => "" } @@ -834,35 +848,37 @@ let getGooglePayRequiredFields = ( paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { let requiredFieldsArr = item.required_field->String.split(".") - let getBillingOrShipping = (billingVal, shippingVal) => { - requiredFieldsArr->Array.includes("billing") ? billingVal : shippingVal - } - - let getName = { - let name = billingContact.name->getBillingOrShipping(shippingContact.name) - - name->getNameFromString(requiredFieldsArr) - } - let fieldVal = switch item.field_type { - | FullName => getName - | BillingName => getName - | AddressLine1 => billingContact.address1->getBillingOrShipping(shippingContact.address1) - | AddressLine2 => billingContact.address2->getBillingOrShipping(shippingContact.address2) - | AddressCity => billingContact.locality->getBillingOrShipping(shippingContact.locality) + | FullName => billingContact.name->getNameFromString(requiredFieldsArr) + | BillingName => billingContact.name->getNameFromString(requiredFieldsArr) + | AddressLine1 => billingContact.address1 + | AddressLine2 => billingContact.address2 + | AddressCity => billingContact.locality | AddressState => - let administrativeArea = - billingContact.administrativeArea->getBillingOrShipping(shippingContact.administrativeArea) - let countryCode = - billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) - Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + Utils.getStateNameFromStateCodeAndCountry( + statesList, + billingContact.administrativeArea, + billingContact.countryCode, + ) | Country | AddressCountry(_) => - billingContact.countryCode->getBillingOrShipping(shippingContact.countryCode) - | AddressPincode => billingContact.postalCode->getBillingOrShipping(shippingContact.postalCode) + billingContact.countryCode + | AddressPincode => billingContact.postalCode | Email => email | PhoneNumber => shippingContact.phoneNumber->String.replaceAll(" ", "")->String.replaceAll("-", "") + | ShippingName => shippingContact.name->getNameFromString(requiredFieldsArr) + | ShippingAddressLine1 => shippingContact.address1 + | ShippingAddressLine2 => shippingContact.address2 + | ShippingAddressCity => shippingContact.locality + | ShippingAddressState => + Utils.getStateNameFromStateCodeAndCountry( + statesList, + shippingContact.administrativeArea, + shippingContact.countryCode, + ) + | ShippingAddressCountry(_) => shippingContact.countryCode + | ShippingAddressPincode => shippingContact.postalCode | _ => "" } @@ -882,26 +898,21 @@ let getPaypalRequiredFields = ( paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { let requiredFieldsArr = item.required_field->String.split(".") - let getName = { - let name = details.shippingAddress.recipientName->Option.getOr("") - - name->getNameFromString(requiredFieldsArr) - } - let fieldVal = switch item.field_type { - | FullName => getName - | BillingName => getName - | AddressLine1 => details.shippingAddress.line1->Option.getOr("") - | AddressLine2 => details.shippingAddress.line2->Option.getOr("") - | AddressCity => details.shippingAddress.city->Option.getOr("") - | AddressState => - let administrativeArea = details.shippingAddress.state->Option.getOr("") - let countryCode = details.shippingAddress.countryCode->Option.getOr("") - Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) - | Country - | AddressCountry(_) => - details.shippingAddress.countryCode->Option.getOr("") - | AddressPincode => details.shippingAddress.postalCode->Option.getOr("") + | ShippingName => { + let name = details.shippingAddress.recipientName->Option.getOr("") + name->getNameFromString(requiredFieldsArr) + } + | ShippingAddressLine1 => details.shippingAddress.line1->Option.getOr("") + | ShippingAddressLine2 => details.shippingAddress.line2->Option.getOr("") + | ShippingAddressCity => details.shippingAddress.city->Option.getOr("") + | ShippingAddressState => { + let administrativeArea = details.shippingAddress.state->Option.getOr("") + let countryCode = details.shippingAddress.countryCode->Option.getOr("") + Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + } + | ShippingAddressCountry(_) => details.shippingAddress.countryCode->Option.getOr("") + | ShippingAddressPincode => details.shippingAddress.postalCode->Option.getOr("") | Email => details.email | PhoneNumber => switch (details.phone->Option.getOr(""), details.shippingAddress.phone->Option.getOr("")) { From 08edabf20a1ba80330794017e672e2c8605eade4 Mon Sep 17 00:00:00 2001 From: Arush Date: Thu, 16 May 2024 17:00:58 +0530 Subject: [PATCH 6/6] fix: resolved comments --- src/Payments/ApplePay.res | 36 ++++++++------------------- src/Payments/GPay.res | 25 ++++++------------- src/Payments/PaymentMethodsRecord.res | 10 +++++--- src/Payments/PaypalSDK.res | 14 ++--------- src/Types/GooglePayType.res | 20 +++++++-------- src/Utilities/PaymentUtils.res | 14 +++++++++++ src/Utilities/Utils.res | 8 ++---- 7 files changed, 53 insertions(+), 74 deletions(-) diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 47e86699e..3547fe569 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -47,16 +47,7 @@ let make = (~sessionObj: option) => { let isGuestCustomer = UtilityHooks.useIsGuestCustomer() - React.useEffect0(() => { - AddressPaymentInput.importStates("./../States.json") - ->then(res => { - setStatesJson(_ => res.states) - resolve() - }) - ->ignore - - None - }) + PaymentUtils.useStatesJson(setStatesJson) let processPayment = (bodyArr, ~isThirdPartyFlow=false, ()) => { let requestBody = PaymentUtils.appendedCustomerAcceptance( @@ -252,7 +243,7 @@ let make = (~sessionObj: option) => { processPayment(bodyDict, ~isThirdPartyFlow=true, ()) } else { let message = [("applePayButtonClicked", true->JSON.Encode.bool)] - Utils.handlePostMessage(message) + handlePostMessage(message) } } else { let bodyDict = PaymentBody.applePayRedirectBody(~connectors) @@ -281,23 +272,19 @@ let make = (~sessionObj: option) => { } try { - let dict = json->Utils.getDictFromJson + let dict = json->getDictFromJson if dict->Dict.get("applePayProcessPayment")->Option.isSome { let token = dict->Dict.get("applePayProcessPayment")->Option.getOr(Dict.make()->JSON.Encode.object) let billingContact = dict - ->Dict.get("applePayBillingContact") - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) + ->getDictfromDict("applePayBillingContact") ->ApplePayTypes.billingContactItemToObjMapper let shippingContact = dict - ->Dict.get("applePayShippingContact") - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) + ->getDictfromDict("applePayShippingContact") ->ApplePayTypes.shippingContactItemToObjMapper let requiredFieldsBody = DynamicFieldsUtils.getApplePayRequiredFields( @@ -311,11 +298,10 @@ let make = (~sessionObj: option) => { let applePayBody = bodyDict - ->Dict.fromArray - ->JSON.Encode.object - ->Utils.flattenObject(true) - ->Utils.mergeTwoFlattenedJsonDicts(requiredFieldsBody) - ->Utils.getArrayOfTupleFromDict + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict processPayment(applePayBody, ()) } else if dict->Dict.get("showApplePayButton")->Option.isSome { @@ -324,13 +310,13 @@ let make = (~sessionObj: option) => { syncPayment() } } catch { - | _ => Utils.logInfo(Console.log("Error in parsing Apple Pay Data")) + | _ => logInfo(Console.log("Error in parsing Apple Pay Data")) } } Window.addEventListener("message", handleApplePayMessages) Some( () => { - Utils.handlePostMessage([("applePaySessionAbort", true->JSON.Encode.bool)]) + handlePostMessage([("applePaySessionAbort", true->JSON.Encode.bool)]) Window.removeEventListener("message", handleApplePayMessages) }, ) diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index 0bbec5705..f7f80a5bc 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -78,16 +78,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) - React.useEffect0(() => { - AddressPaymentInput.importStates("./../States.json") - ->then(res => { - setStatesJson(_ => res.states) - resolve() - }) - ->ignore - - None - }) + PaymentUtils.useStatesJson(setStatesJson) React.useEffect(() => { let handle = (ev: Window.event) => { @@ -96,7 +87,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti } catch { | _ => Dict.make()->JSON.Encode.object } - let dict = json->Utils.getDictFromJson + let dict = json->getDictFromJson if dict->Dict.get("gpayResponse")->Option.isSome { let metadata = dict->getJsonObjectFromDict("gpayResponse") let obj = metadata->getDictFromJson->itemToObjMapper @@ -109,21 +100,21 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti let billingContact = obj.paymentMethodData.info ->getDictFromJson - ->Utils.getJsonObjectFromDict("billingAddress") + ->getJsonObjectFromDict("billingAddress") ->getDictFromJson ->billingContactItemToObjMapper let shippingContact = metadata ->getDictFromJson - ->Utils.getJsonObjectFromDict("shippingAddress") + ->getJsonObjectFromDict("shippingAddress") ->getDictFromJson ->billingContactItemToObjMapper let email = metadata ->getDictFromJson - ->Utils.getString("email", "") + ->getString("email", "") let requiredFieldsBody = DynamicFieldsUtils.getGooglePayRequiredFields( ~billingContact, @@ -143,7 +134,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti processPayment(body, ()) } if dict->Dict.get("gpayError")->Option.isSome { - Utils.handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) + handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) } } Window.addEventListener("message", handle) @@ -248,13 +239,13 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti } catch { | _ => Dict.make()->JSON.Encode.object } - let dict = json->Utils.getDictFromJson + let dict = json->getDictFromJson try { if dict->Dict.get("googlePaySyncPayment")->Option.isSome { syncPayment() } } catch { - | _ => Utils.logInfo(Console.log("Error in syncing GooglePay Payment")) + | _ => logInfo(Console.log("Error in syncing GooglePay Payment")) } } Window.addEventListener("message", handleGooglePayMessages) diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index b63bdc2da..8afd893b1 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -552,10 +552,12 @@ let getOptionsFromPaymentMethodFieldType = (dict, key, ~isAddressCountry=true) = isAddressCountry ? AddressCountry(countryArr) : ShippingAddressCountry(countryArr) } | _ => { - let countryArr = - Country.country - ->Array.filter(item => options->Array.includes(item.isoAlpha2)) - ->Array.map(item => item.countryName) + let countryArr = Country.country->Array.reduce([], (acc, country) => { + if options->Array.includes(country.isoAlpha2) { + acc->Array.push(country.countryName) + } + acc + }) isAddressCountry ? AddressCountry(countryArr) : ShippingAddressCountry(countryArr) } } diff --git a/src/Payments/PaypalSDK.res b/src/Payments/PaypalSDK.res index 71477276e..1b72dd12e 100644 --- a/src/Payments/PaypalSDK.res +++ b/src/Payments/PaypalSDK.res @@ -49,16 +49,7 @@ let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) = ~paymentMethodType="paypal", ) - React.useEffect0(() => { - AddressPaymentInput.importStates("./../States.json") - ->then(res => { - setStatesJson(_ => res.states) - resolve() - }) - ->ignore - - None - }) + PaymentUtils.useStatesJson(setStatesJson) let loadPaypalSdk = () => { loggerState.setLogInfo( @@ -122,8 +113,7 @@ let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) = let paypalBody = body - ->Dict.fromArray - ->JSON.Encode.object + ->Utils.getJsonFromArrayOfJson ->Utils.flattenObject(true) ->Utils.mergeTwoFlattenedJsonDicts(requiredFieldsBody) ->Utils.getArrayOfTupleFromDict diff --git a/src/Types/GooglePayType.res b/src/Types/GooglePayType.res index b35e60034..5669b17d9 100644 --- a/src/Types/GooglePayType.res +++ b/src/Types/GooglePayType.res @@ -144,15 +144,15 @@ let jsonToPaymentRequestDataType: (paymentDataRequest, Dict.t) => paymen let billingContactItemToObjMapper = dict => { { - address1: dict->Utils.getString("address1", ""), - address2: dict->Utils.getString("address2", ""), - address3: dict->Utils.getString("address3", ""), - administrativeArea: dict->Utils.getString("administrativeArea", ""), - countryCode: dict->Utils.getString("countryCode", ""), - locality: dict->Utils.getString("locality", ""), - name: dict->Utils.getString("name", ""), - phoneNumber: dict->Utils.getString("phoneNumber", ""), - postalCode: dict->Utils.getString("postalCode", ""), - sortingCode: dict->Utils.getString("sortingCode", ""), + address1: dict->getString("address1", ""), + address2: dict->getString("address2", ""), + address3: dict->getString("address3", ""), + administrativeArea: dict->getString("administrativeArea", ""), + countryCode: dict->getString("countryCode", ""), + locality: dict->getString("locality", ""), + name: dict->getString("name", ""), + phoneNumber: dict->getString("phoneNumber", ""), + postalCode: dict->getString("postalCode", ""), + sortingCode: dict->getString("sortingCode", ""), } } diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 7f49fa152..fbf4a692f 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -291,3 +291,17 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { } }, (methodslist, paymentMethodOrder, optionAtomValue.wallets.payPal, paymentType)) } + +let useStatesJson = setStatesJson => { + open Promise + React.useEffect0(() => { + AddressPaymentInput.importStates("./../States.json") + ->then(res => { + setStatesJson(_ => res.states) + resolve() + }) + ->ignore + + None + }) +} diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 9f6c6260d..efcb2667e 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1261,18 +1261,14 @@ let getStateNameFromStateCodeAndCountry = (list: JSON.t, stateCode: string, coun let val = options->Array.find(item => item ->getDictFromJson - ->Dict.get("code") - ->Option.flatMap(JSON.Decode.string) - ->Option.getOr("") === stateCode + ->getString("code", "") === stateCode ) switch val { | Some(stateObj) => stateObj ->getDictFromJson - ->Dict.get("name") - ->Option.flatMap(JSON.Decode.string) - ->Option.getOr(stateCode) + ->getString("name", stateCode) | None => stateCode } }