From 143dabd7cb639cbdac08eea59028d8c0d23a34d9 Mon Sep 17 00:00:00 2001 From: Arush Date: Fri, 12 Jan 2024 14:04:31 +0530 Subject: [PATCH 1/2] feat: HS-181: Added support of Dynamic Fields For Bancontact --- src/CardUtils.res | 82 +++ src/Components/DynamicFields.res | 983 +++++++++++++------------- src/Payments/CardPayment.res | 146 ++-- src/Payments/PaymentMethodsRecord.res | 91 ++- src/RenderPaymentMethods.res | 1 - src/SingleLineCardPayment.res | 1 - src/Utilities/DynamicFieldsUtils.res | 397 +++++++++++ src/Utilities/PaymentBody.res | 27 +- src/Utilities/PaymentUtils.res | 105 ++- 9 files changed, 1186 insertions(+), 647 deletions(-) create mode 100644 src/Utilities/DynamicFieldsUtils.res diff --git a/src/CardUtils.res b/src/CardUtils.res index 701da909b..172240627 100644 --- a/src/CardUtils.res +++ b/src/CardUtils.res @@ -581,3 +581,85 @@ let postalRegex = (postalCodes: array, ~country=?, ( let countryPostal = Utils.getCountryPostal(country, postalCodes) countryPostal.regex == "" ? "" : countryPostal.regex } + +let getCardDetailsFromCardProps = cardProps => { + let defaultCardProps = ( + None, + _ => (), + "", + _ => (), + _ => (), + React.useRef(Js.Nullable.null), + <> , + "", + _ => (), + 0, + ) + + switch cardProps { + | Some(cardProps) => cardProps + | None => defaultCardProps + } +} + +let getExpiryDetailsFromExpiryProps = expiryProps => { + let defaultExpiryProps = ( + None, + _ => (), + "", + _ => (), + _ => (), + React.useRef(Js.Nullable.null), + _ => (), + "", + _ => (), + ) + + switch expiryProps { + | Some(expiryProps) => expiryProps + | None => defaultExpiryProps + } +} + +let getCvcDetailsFromCvcProps = cvcProps => { + let defaultCvcProps = ( + None, + _ => (), + "", + _ => (), + _ => (), + _ => (), + React.useRef(Js.Nullable.null), + _ => (), + "", + _ => (), + ) + + switch cvcProps { + | Some(cvcProps) => cvcProps + | None => defaultCvcProps + } +} + +let setRightIconForCvc = (~cardEmpty, ~cardInvalid, ~color, ~cardComplete) => { + if cardEmpty { + + } else if cardInvalid { +
+ +
+ } else if cardComplete { + + } else { + + } + } + +let getCardDetailsHook = (~cvcNumber, ~isCvcValidValue, ~isCVCValid) => { + React.useMemo3(() => { + let isCardDetailsEmpty = Js.String2.length(cvcNumber) == 0 + let isCardDetailsValid = isCvcValidValue == "valid" + let isCardDetailsInvalid = isCvcValidValue == "invalid" + (isCardDetailsEmpty, isCardDetailsValid, isCardDetailsInvalid) + }, (cvcNumber, isCvcValidValue, isCVCValid)) +} diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index e98fe4fa2..9b5777bf1 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -8,6 +8,10 @@ let make = ( ~setRequiredFieldsBody, ~isSavedCardFlow=false, ~savedCards=[]: array, + ~cardProps=None, + ~expiryProps=None, + ~cvcProps=None, + ~isBancontact=false, ) => { React.useEffect1(() => { setRequiredFieldsBody(_ => Js.Dict.empty()) @@ -56,6 +60,7 @@ let make = ( requiredFields, ~isSavedCardFlow, ~isAllStoredCardsHaveName, + ~isBancontact, (), ) ->Utils.removeDuplicate @@ -114,6 +119,54 @@ let make = ( setCountry(. val) } + let ( + isCardValid, + setIsCardValid, + cardNumber, + changeCardNumber, + handleCardBlur, + cardRef, + icon, + cardError, + _, + maxCardLength, + ) = + cardProps->CardUtils.getCardDetailsFromCardProps + + let ( + isExpiryValid, + setIsExpiryValid, + cardExpiry, + changeCardExpiry, + handleExpiryBlur, + expiryRef, + _, + expiryError, + _, + ) = + expiryProps->CardUtils.getExpiryDetailsFromExpiryProps + + let ( + isCVCValid, + setIsCVCValid, + cvcNumber, + _, + changeCVCNumber, + handleCVCBlur, + cvcRef, + _, + cvcError, + _, + ) = + cvcProps->CardUtils.getCvcDetailsFromCvcProps + + let isCvcValidValue = CardUtils.getBoolOptionVal(isCVCValid) + let (cardEmpty, cardComplete, cardInvalid) = CardUtils.getCardDetailsHook( + ~cvcNumber, + ~isCVCValid, + ~isCvcValidValue, + ) + React.useEffect0(() => { let bank = bankNames->Belt.Array.get(0)->Belt.Option.getWithDefault("") setSelectedBank(_ => bank) @@ -183,285 +236,95 @@ let make = ( } } - React.useEffect7(() => { - let areRequiredFieldsValid = fieldsArr->Js.Array2.reduce((acc, paymentMethodFields) => { - acc && - switch paymentMethodFields { - | Email => email.isValid - | FullName => Some(fullName.value !== "") - | Country => Some(country !== "" || countryNames->Belt.Array.length === 0) - | AddressCountry(countryArr) => Some(country !== "" || countryArr->Belt.Array.length === 0) - | BillingName => Some(billingName.value !== "") - | AddressLine1 => Some(line1.value !== "") - | Bank => Some(selectedBank !== "" || bankNames->Belt.Array.length === 0) - | PhoneNumber => Some(phone.value !== "") - | StateAndCity => Some(state.value !== "" && city.value !== "") - | CountryAndPincode(countryArr) => - Some( - (country !== "" || countryArr->Belt.Array.length === 0) && - postalCode.isValid->Belt.Option.getWithDefault(false), - ) - | AddressCity => Some(city.value !== "") - | AddressPincode => postalCode.isValid - | AddressState => Some(state.value !== "") - | BlikCode => Some(blikCode.value !== "") - | Currency(currencyArr) => Some(currency !== "" || currencyArr->Belt.Array.length === 0) - | AddressLine2 - | SpecialField(_) - | InfoElement - | None => - Some(true) - }->Belt.Option.getWithDefault(false) - }, true) - setAreRequiredFieldsValid(._ => areRequiredFieldsValid) - - let areRequiredFieldsEmpty = fieldsArr->Js.Array2.reduce((acc, paymentMethodFields) => { - acc || - switch paymentMethodFields { - | Email => email.value === "" - | FullName => fullName.value === "" - | Country => country === "" && countryNames->Belt.Array.length > 0 - | AddressCountry(countryArr) => country === "" && countryArr->Belt.Array.length > 0 - | BillingName => billingName.value === "" - | AddressLine1 => line1.value === "" - | Bank => selectedBank === "" && bankNames->Belt.Array.length > 0 - | StateAndCity => city.value === "" || state.value === "" - | CountryAndPincode(countryArr) => - (country === "" && countryArr->Belt.Array.length > 0) || postalCode.value === "" - | PhoneNumber => phone.value === "" - | AddressCity => city.value === "" - | AddressPincode => postalCode.value === "" - | AddressState => state.value === "" - | BlikCode => blikCode.value === "" - | Currency(currencyArr) => currency === "" && currencyArr->Belt.Array.length > 0 - | AddressLine2 - | SpecialField(_) - | InfoElement - | None => false - } - }, false) - setAreRequiredFieldsEmpty(._ => areRequiredFieldsEmpty) - None - }, ( - fieldsArr, - currency, - fullName.value, - country, - billingName.value, - line1.value, - ( - email, - line2.value, - selectedBank, - phone.value, - city.value, - postalCode.value, - state.value, - blikCode.value, - ), - )) + DynamicFieldsUtils.requiredFieldsEmptyAndValidHook( + ~fieldsArr, + ~country, + ~countryNames, + ~selectedBank, + ~bankNames, + ~currency, + ~email, + ~fullName, + ~billingName, + ~line1, + ~line2, + ~phone, + ~state, + ~city, + ~postalCode, + ~blikCode, + ~isCardValid, + ~isExpiryValid, + ~isCVCValid, + ~cardNumber, + ~cardExpiry, + ~cvcNumber, + ~setAreRequiredFieldsValid, + ~setAreRequiredFieldsEmpty, + ) let requiredFieldsType = requiredFields ->Utils.getDictFromJson ->Js.Dict.values ->Js.Array2.map(item => - item->Utils.getDictFromJson->PaymentMethodsRecord.getRequiredFieldsFromJson + item->Utils.getDictFromJson->PaymentMethodsRecord.getRequiredFieldsFromJson(isBancontact) ) - React.useEffect0(() => { - let getNameValue = (item: PaymentMethodsRecord.required_fields) => { - requiredFieldsType - ->Js.Array2.filter(requiredFields => requiredFields.field_type === item.field_type) - ->Js.Array2.reduce((acc, item) => { - let requiredFieldsArr = item.required_field->Js.String2.split(".") - switch requiredFieldsArr - ->Belt.Array.get(requiredFieldsArr->Belt.Array.length - 1) - ->Belt.Option.getWithDefault("") { - | "first_name" => item.value->Js.String2.concat(acc) - | "last_name" => acc->Js.String2.concatMany([" ", item.value]) - | _ => acc - } - }, "") - ->Js.String2.trim - } - - let setFields = ( - setMethod: (. RecoilAtomTypes.field => RecoilAtomTypes.field) => unit, - field: RecoilAtomTypes.field, - item: PaymentMethodsRecord.required_fields, - isNameField, - ) => { - if isNameField && field.value === "" { - setMethod(.prev => { - ...prev, - value: getNameValue(item), - }) - } else if field.value === "" { - setMethod(.prev => { - ...prev, - value: item.value, - }) - } - } - - requiredFieldsType->Js.Array2.forEach(requiredField => { - let value = requiredField.value - switch requiredField.field_type { - | Email => { - let emailValue = email.value - setFields(setEmail, email, requiredField, false) - if emailValue === "" { - let newEmail: RecoilAtomTypes.field = { - value, - isValid: None, - errorString: "", - } - Utils.checkEmailValid(newEmail, setEmail) - } - } - | FullName => setFields(setFullName, fullName, requiredField, true) - | AddressLine1 => setFields(setLine1, line1, requiredField, false) - | AddressLine2 => setFields(setLine2, line2, requiredField, false) - | StateAndCity => { - setFields(setState, state, requiredField, false) - setFields(setCity, city, requiredField, false) - } - | CountryAndPincode(_) => { - setFields(setPostalCode, postalCode, requiredField, false) - if value !== "" && country === "" { - let countryCode = - Country.getCountry(paymentMethodType) - ->Js.Array2.filter(item => item.isoAlpha2 === value) - ->Belt.Array.get(0) - ->Belt.Option.getWithDefault(Country.defaultTimeZone) - setCountry(_ => countryCode.countryName) - } - } - | AddressState => setFields(setState, state, requiredField, false) - | AddressCity => setFields(setCity, city, requiredField, false) - | AddressPincode => setFields(setPostalCode, postalCode, requiredField, false) - | PhoneNumber => setFields(setPhone, phone, requiredField, false) - | BlikCode => setFields(setBlikCode, blikCode, requiredField, false) - | BillingName => setFields(setBillingName, billingName, requiredField, true) - | Country - | AddressCountry(_) => - if value !== "" { - let defaultCountry = - Country.getCountry(paymentMethodType) - ->Js.Array2.filter(item => item.isoAlpha2 === value) - ->Belt.Array.get(0) - ->Belt.Option.getWithDefault(Country.defaultTimeZone) - setCountry(_ => defaultCountry.countryName) - } - | Currency(_) => - if value !== "" && currency === "" { - setCurrency(_ => value) - } - | Bank => - if value !== "" && selectedBank === "" { - setSelectedBank(_ => value) - } - | StateAndCity - | CountryAndPincode(_) - | SpecialField(_) - | InfoElement - | None => () - } - }) - None - }) - - React.useEffect1(() => { - let getName = (item: PaymentMethodsRecord.required_fields, field: RecoilAtomTypes.field) => { - let fieldNameArr = field.value->Js.String2.split(" ") - let requiredFieldsArr = item.required_field->Js.String2.split(".") - switch requiredFieldsArr - ->Belt.Array.get(requiredFieldsArr->Belt.Array.length - 1) - ->Belt.Option.getWithDefault("") { - | "first_name" => fieldNameArr->Belt.Array.get(0)->Belt.Option.getWithDefault(field.value) - | "last_name" => fieldNameArr->Belt.Array.sliceToEnd(1)->Js.Array2.reduce((acc, item) => { - acc ++ item - }, "") - | _ => field.value - } - } + DynamicFieldsUtils.setInitialRequiredFieldsHook( + ~requiredFieldsType, + ~email, + ~setEmail, + ~fullName, + ~setFullName, + ~line1, + ~setLine1, + ~line2, + ~setLine2, + ~state, + ~setState, + ~city, + ~setCity, + ~postalCode, + ~setPostalCode, + ~country, + ~setCountry, + ~paymentMethodType, + ~phone, + ~setPhone, + ~blikCode, + ~setBlikCode, + ~billingName, + ~setBillingName, + ~currency, + ~setCurrency, + ~selectedBank, + ~setSelectedBank, + ) - let requiredFieldsBody = - requiredFieldsType - ->Js.Array2.filter(item => item.field_type !== None) - ->Js.Array2.reduce((acc, item) => { - let value = switch item.field_type { - | Email => email.value - | FullName => getName(item, fullName) - | AddressLine1 => line1.value - | AddressLine2 => line2.value - | AddressCity => city.value - | AddressPincode => postalCode.value - | AddressState => state.value - | BlikCode => blikCode.value - | PhoneNumber => phone.value - | Currency(_) => currency - | Country => country - | Bank => selectedBank - | BillingName => getName(item, billingName) - | AddressCountry(_) => { - let countryCode = - Country.getCountry(paymentMethodType) - ->Js.Array2.filter(item => item.countryName === country) - ->Belt.Array.get(0) - ->Belt.Option.getWithDefault(Country.defaultTimeZone) - countryCode.isoAlpha2 - } - | StateAndCity - | CountryAndPincode(_) - | SpecialField(_) - | InfoElement - | _ => "" - } - switch item.field_type { - | StateAndCity => - acc->Js.Dict.set("billing.address.city", city.value->Js.Json.string) - acc->Js.Dict.set("billing.address.state", state.value->Js.Json.string) - | CountryAndPincode(_) => - acc->Js.Dict.set("billing.address.country", city.value->Js.Json.string) - acc->Js.Dict.set("billing.address.zip", postalCode.value->Js.Json.string) - | _ => () - } - 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->Js.Dict.set( - "payment_method_data.card_token.card_holder_name", - value->Js.Json.string, - ) - } - } else { - acc->Js.Dict.set(item.required_field, value->Js.Json.string) - } - acc - }, Js.Dict.empty()) - - setRequiredFieldsBody(_ => requiredFieldsBody) - None - }, [ - fullName.value, - email.value, - line1.value, - line2.value, - city.value, - postalCode.value, - state.value, - blikCode.value, - phone.value, - currency, - billingName.value, - country, - ]) + DynamicFieldsUtils.requiredFieldsBodyHook( + ~requiredFieldsType, + ~email, + ~fullName, + ~billingName, + ~line1, + ~line2, + ~phone, + ~state, + ~city, + ~postalCode, + ~blikCode, + ~currency, + ~country, + ~selectedBank, + ~paymentMethodType, + ~cardNumber, + ~cardExpiry, + ~cvcNumber, + ~isSavedCardFlow, + ~isAllStoredCardsHaveName, + ~setRequiredFieldsBody, + ) let bottomElement = @@ -480,218 +343,394 @@ let make = ( } } + let dynamicFieldsToRenderOutsideBilling = + fieldsArr->Js.Array2.filter(field => + PaymentMethodsRecord.dynamicFieldsToRenderOutsideBilling->Js.Array2.includes(field) + ) + + let dynamicFieldsToRenderInsideBilling = + fieldsArr->Js.Array2.filter(field => + !(PaymentMethodsRecord.dynamicFieldsToRenderOutsideBilling->Js.Array2.includes(field)) + ) + + let isOnlyInfoElementPresent = + dynamicFieldsToRenderInsideBilling->Js.Array2.length === 1 && + dynamicFieldsToRenderInsideBilling->Belt.Array.get(0) === Some(InfoElement) + + let isRenderDynamicFieldsInsideBilling = + dynamicFieldsToRenderInsideBilling->Js.Array2.length > 1 || !isOnlyInfoElementPresent + { fieldsArr->Js.Array2.length > 0 - ?
- {React.string("Billing Details")} -
- {fieldsArr - ->Js.Array2.mapi((item, index) => { -
Js.Int.toString} className="flex flex-col w-full place-content-between"> - {switch item { - | FullName => - getCustomFieldName} /> - | BillingName => - getCustomFieldName} /> - | Email => - | PhoneNumber => - | StateAndCity => -
- { - setCity(.prev => { - ...prev, - value: ReactEvent.Form.target(ev)["value"], - }) - }} - paymentType - type_="text" - name="city" - inputRef=cityRef - placeholder=localeString.cityLabel - /> - {switch stateJson { - | Some(options) => - Utils.getStateNames({ - value: country, - isValid: None, - errorString: "", - })} - /> - | None => React.null - }} -
- | CountryAndPincode(countryArr) => -
- - -
- | AddressLine1 => - { - setLine1(.prev => { - ...prev, - value: ReactEvent.Form.target(ev)["value"], - }) - }} - paymentType - type_="text" - name="line1" - inputRef=line1Ref - placeholder=localeString.line1Placeholder - /> - | AddressLine2 => - { - setLine2(.prev => { - ...prev, - value: ReactEvent.Form.target(ev)["value"], - }) - }} - paymentType - type_="text" - name="line2" - inputRef=line2Ref - placeholder=localeString.line2Placeholder - /> - | AddressCity => - { - setCity(.prev => { - ...prev, - value: ReactEvent.Form.target(ev)["value"], - }) - }} - paymentType - type_="text" - name="city" - inputRef=cityRef - placeholder=localeString.cityLabel - /> - | AddressState => - switch stateJson { - | Some(options) => - Utils.getStateNames({ - value: country, - isValid: None, - errorString: "", - })} - /> - | None => React.null - } - | AddressPincode => - + {dynamicFieldsToRenderOutsideBilling + ->Js.Array2.mapi((item, index) => { +
Js.Int.toString}`} + className="flex flex-col w-full place-content-between" + style={ReactDOMStyle.make( + ~marginTop=index !== 0 || paymentMethod === "card" + ? themeObj.spacingGridColumn + : "", + ~gridColumnGap=themeObj.spacingGridRow, + (), + )}> + {switch item { + | CardNumber => + + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear => + + | CardCvc => + + | CardExpiryAndCvc => +
+ - | BlikCode => - | Currency(currencyArr) => - - | Country => - - | AddressCountry(countryArr) => - - | Bank => - - | SpecialField(element) => element - | InfoElement => - <> - - {if fieldsArr->Js.Array2.length > 1 { - bottomElement - } else { - +
+ | Email + | FullName + | InfoElement + | Country + | Bank + | None + | BillingName + | PhoneNumber + | AddressLine1 + | AddressLine2 + | AddressCity + | StateAndCity + | AddressPincode + | AddressState + | BlikCode + | SpecialField(_) + | CountryAndPincode(_) + | AddressCountry(_) + | Currency(_) => React.null + }} +
+ }) + ->React.array} + +
+ {React.string("Billing Details")} +
+ {dynamicFieldsToRenderInsideBilling + ->Js.Array2.mapi((item, index) => { +
Js.Int.toString}`} + className="flex flex-col w-full place-content-between"> + {switch item { + | FullName => + getCustomFieldName} + /> + | BillingName => + getCustomFieldName} + /> + | Email => + | PhoneNumber => + | StateAndCity => +
+ { + setCity(.prev => { + ...prev, + value: ReactEvent.Form.target(ev)["value"], + }) + }} + paymentType + type_="text" + name="city" + inputRef=cityRef + placeholder=localeString.cityLabel + /> + {switch stateJson { + | Some(options) => + Utils.getStateNames({ + value: country, + isValid: None, + errorString: "", + })} + /> + | None => React.null + }} +
+ | CountryAndPincode(countryArr) => +
+ + +
+ | AddressLine1 => + { + setLine1(.prev => { + ...prev, + value: ReactEvent.Form.target(ev)["value"], + }) + }} + paymentType + type_="text" + name="line1" + inputRef=line1Ref + placeholder=localeString.line1Placeholder + /> + | AddressLine2 => + { + setLine2(.prev => { + ...prev, + value: ReactEvent.Form.target(ev)["value"], + }) + }} + paymentType + type_="text" + name="line2" + inputRef=line2Ref + placeholder=localeString.line2Placeholder + /> + | AddressCity => + { + setCity(.prev => { + ...prev, + value: ReactEvent.Form.target(ev)["value"], + }) + }} + paymentType + type_="text" + name="city" + inputRef=cityRef + placeholder=localeString.cityLabel + /> + | AddressState => + switch stateJson { + | Some(options) => + Utils.getStateNames({ + value: country, + isValid: None, + errorString: "", + })} + /> + | None => React.null + } + | AddressPincode => + + | BlikCode => + | Currency(currencyArr) => + + | Country => + + | AddressCountry(countryArr) => + + | Bank => + + | SpecialField(element) => element + | InfoElement => + <> + + {if fieldsArr->Js.Array2.length > 1 { + bottomElement + } else { + + }} + + | CardNumber + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear + | CardCvc + | CardExpiryAndCvc + | None => React.null }} - - | None => React.null - }} +
+ }) + ->React.array}
- }) - ->React.array} -
-
+
+ + + {<> + + {if fieldsArr->Js.Array2.length > 1 { + bottomElement + } else { + + }} + } + + : React.null } } diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 2ea6304a0..05d69151e 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -65,7 +65,6 @@ let make = ( let (showFields, setShowFields) = Recoil.useRecoilState(RecoilAtoms.showCardFeildsAtom) let (paymentToken, setPaymentToken) = Recoil.useRecoilState(RecoilAtoms.paymentTokenAtom) let (token, _) = paymentToken - let cardHolderName = Recoil.useRecoilValueFromAtom(RecoilAtoms.userFullName) let setComplete = Recoil.useSetRecoilState(RecoilAtoms.fieldsComplete) let ( loadSavedCards: PaymentType.savedCardsLoadState, @@ -142,26 +141,11 @@ let make = ( }, (empty, complete)) let isCvcValidValue = CardUtils.getBoolOptionVal(isCVCValid) - let (cardEmpty, cardComplete, cardInvalid) = React.useMemo3(() => { - let isCardDetailsEmpty = Js.String2.length(cvcNumber) == 0 - let isCardDetailsValid = isCvcValidValue == "valid" - let isCardDetailsInvalid = isCvcValidValue == "invalid" - (isCardDetailsEmpty, isCardDetailsValid, isCardDetailsInvalid) - }, (cvcNumber, isCvcValidValue, isCVCValid)) - - let setRightIconForCvc = () => { - if cardEmpty { - - } else if cardInvalid { -
- -
- } else if cardComplete { - - } else { - - } - } + let (cardEmpty, cardComplete, cardInvalid) = CardUtils.getCardDetailsHook( + ~cvcNumber, + ~isCVCValid, + ~isCvcValidValue, + ) let submitCallback = React.useCallback((ev: Window.event) => { let json = ev.data->Js.Json.parseExn @@ -186,21 +170,16 @@ let make = ( ~cvcNumber, ~cardBrand=cardNetwork, ) - let banContactBody = PaymentBody.bancontactBody( - ~cardNumber, - ~month, - ~year, - ~cardHolderName=cardHolderName.value, - ) + let banContactBody = PaymentBody.bancontactBody() let cardBody = isSaveCardsChecked ? deafultCardBody->Js.Array2.concat(onSessionBody) : deafultCardBody if confirm.doSubmit { let validFormat = + (isBancontact || + (isCVCValid->Belt.Option.getWithDefault(false) && isCardValid->Belt.Option.getWithDefault(false) && - isExpiryValid->Belt.Option.getWithDefault(false) && - (isBancontact || isCVCValid->Belt.Option.getWithDefault(false)) && - areRequiredFieldsValid + isExpiryValid->Belt.Option.getWithDefault(false))) && areRequiredFieldsValid if validFormat && (showFields || isBancontact) { intent( ~bodyArr={ @@ -269,51 +248,47 @@ let make = ( className="flex flex-col" style={ReactDOMStyle.make(~gridGap=themeObj.spacingGridColumn, ())}>
- -
-
- -
- -
-
- + + +
+
+ +
- -
+
+ Js.Array.length !== 0}> diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index f8d84e414..1638c50d1 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -21,19 +21,31 @@ type paymentMethodsFields = | AddressCountry(array) | BlikCode | Currency(array) + | CardNumber + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear + | CardCvc + | CardExpiryAndCvc let getPaymentMethodsFieldsOrder = paymentMethodField => { switch paymentMethodField { - | AddressLine1 => 1 - | AddressLine2 => 2 - | AddressCity => 3 - | AddressState => 4 - | AddressCountry(_) => 5 - | AddressPincode => 6 - | StateAndCity => 4 - | CountryAndPincode(_) => 5 + | CardNumber => 0 + | CardExpiryMonth => 1 + | CardExpiryYear => 1 + | CardExpiryMonthAndYear => 1 + | CardCvc => 2 + | CardExpiryAndCvc => 2 + | AddressLine1 => 4 + | AddressLine2 => 5 + | AddressCity => 6 + | AddressState => 7 + | AddressCountry(_) => 8 + | AddressPincode => 9 + | StateAndCity => 7 + | CountryAndPincode(_) => 8 | InfoElement => 99 - | _ => 0 + | _ => 3 } } @@ -475,20 +487,24 @@ type required_fields = { value: string, } -let getPaymentMethodsFieldTypeFromString = str => { - switch str { - | "user_email_address" => Email - | "user_full_name" => FullName - | "user_country" => Country - | "user_bank" => Bank - | "user_phone_number" => PhoneNumber - | "user_address_line1" => AddressLine1 - | "user_address_line2" => AddressLine2 - | "user_address_city" => AddressCity - | "user_address_pincode" => AddressPincode - | "user_address_state" => AddressState - | "user_blik_code" => BlikCode - | "user_billing_name" => BillingName +let getPaymentMethodsFieldTypeFromString = (str, isBancontact) => { + switch (str, isBancontact) { + | ("user_email_address", _) => Email + | ("user_full_name", _) => FullName + | ("user_country", _) => Country + | ("user_bank", _) => Bank + | ("user_phone_number", _) => PhoneNumber + | ("user_address_line1", _) => AddressLine1 + | ("user_address_line2", _) => AddressLine2 + | ("user_address_city", _) => AddressCity + | ("user_address_pincode", _) => AddressPincode + | ("user_address_state", _) => AddressState + | ("user_blik_code", _) => BlikCode + | ("user_billing_name", _) => BillingName + | ("user_card_number", true) => CardNumber + | ("user_card_expiry_month", true) => CardExpiryMonth + | ("user_card_expiry_year", true) => CardExpiryYear + | ("user_card_cvc", true) => CardCvc | _ => None } } @@ -513,7 +529,7 @@ let getPaymentMethodsFieldTypeFromDict = dict => { } } -let getFieldType = dict => { +let getFieldType = (dict, isBancontact) => { let fieldClass = dict ->Js.Dict.get("field_type") @@ -526,17 +542,17 @@ let getFieldType = dict => { None | JSONNumber(_val) => None | JSONArray(_arr) => None - | JSONString(val) => val->getPaymentMethodsFieldTypeFromString + | JSONString(val) => val->getPaymentMethodsFieldTypeFromString(isBancontact) | JSONObject(dict) => dict->getPaymentMethodsFieldTypeFromDict } } -let getRequiredFieldsFromJson = dict => { +let getRequiredFieldsFromJson = (dict, isBancontact) => { { required_field: Utils.getString(dict, "required_field", ""), display_name: Utils.getString(dict, "display_name", ""), - field_type: dict->getFieldType, + field_type: dict->getFieldType(isBancontact), value: Utils.getString(dict, "value", ""), } } @@ -548,6 +564,7 @@ let dynamicFieldsEnabledPaymentMethods = [ "blik", "google_pay", "apple_pay", + "bancontact_card", ] let getIsBillingField = requiredFieldType => { @@ -562,9 +579,9 @@ let getIsBillingField = requiredFieldType => { } } -let getIsAnyBillingDetailEmpty = (requiredFieldsValues: array) => { +let getIsAnyBillingDetailEmpty = (requiredFieldsValues: array, isBancontact) => { requiredFieldsValues->Js.Array2.reduce((acc, item) => { - let requiredField = item->Utils.getDictFromJson->getRequiredFieldsFromJson + let requiredField = item->Utils.getDictFromJson->getRequiredFieldsFromJson(isBancontact) if getIsBillingField(requiredField.field_type) { requiredField.value === "" || acc } else { @@ -578,14 +595,14 @@ let getPaymentMethodFields = ( requiredFields, ~isSavedCardFlow=false, ~isAllStoredCardsHaveName=false, + ~isBancontact=false, (), ) => { let requiredFieldsValues = requiredFields->Utils.getDictFromJson->Js.Dict.values - let isAnyBillingDetailEmpty = requiredFieldsValues->getIsAnyBillingDetailEmpty + let isAnyBillingDetailEmpty = requiredFieldsValues->getIsAnyBillingDetailEmpty(isBancontact) let requiredFieldsArr = requiredFieldsValues->Js.Array2.map(item => { - let requiredField = item->Utils.getDictFromJson->getRequiredFieldsFromJson + let requiredField = item->Utils.getDictFromJson->getRequiredFieldsFromJson(isBancontact) let isShowBillingField = getIsBillingField(requiredField.field_type) && isAnyBillingDetailEmpty - if requiredField.value === "" || isShowBillingField { if ( isSavedCardFlow && @@ -905,6 +922,7 @@ let buildFromPaymentList = (plist: list) => { fields: getPaymentMethodFields( paymentMethodName, individualPaymentMethod.required_fields, + ~isBancontact=individualPaymentMethod.payment_method_type === "bancontact_card", (), ), paymentFlow: paymentExperience, @@ -936,3 +954,12 @@ let getPaymentMethodTypeFromList = (~list: list, ~paymentMethod, ~paymentMethodT item.payment_method_type == paymentMethodType }) } + +let dynamicFieldsToRenderOutsideBilling = [ + CardNumber, + CardExpiryMonth, + CardExpiryYear, + CardExpiryMonthAndYear, + CardCvc, + CardExpiryAndCvc, +] diff --git a/src/RenderPaymentMethods.res b/src/RenderPaymentMethods.res index 58abd8c56..cee839034 100644 --- a/src/RenderPaymentMethods.res +++ b/src/RenderPaymentMethods.res @@ -94,7 +94,6 @@ let make = ( type_="tel" maxLength=maxCardLength paymentType - pattern="[\d| ]{16,22}" inputRef=cardRef placeholder="1234 1234 1234 1234" id="card-number" diff --git a/src/SingleLineCardPayment.res b/src/SingleLineCardPayment.res index 1c83d4c19..434dad6f7 100644 --- a/src/SingleLineCardPayment.res +++ b/src/SingleLineCardPayment.res @@ -128,7 +128,6 @@ let make = ( type_="tel" maxLength=maxCardLength paymentType - pattern="[\d| ]{16,22}" inputRef=cardRef placeholder="1234 1234 1234 1234" isFocus diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res new file mode 100644 index 000000000..926e7f16b --- /dev/null +++ b/src/Utilities/DynamicFieldsUtils.res @@ -0,0 +1,397 @@ +let requiredFieldsEmptyAndValidHook = ( + ~fieldsArr: Js.Array2.t, + ~country, + ~countryNames, + ~selectedBank, + ~bankNames, + ~currency, + ~email: RecoilAtomTypes.field, + ~fullName: RecoilAtomTypes.field, + ~billingName: RecoilAtomTypes.field, + ~line1: RecoilAtomTypes.field, + ~line2: RecoilAtomTypes.field, + ~phone: RecoilAtomTypes.field, + ~state: RecoilAtomTypes.field, + ~city: RecoilAtomTypes.field, + ~postalCode: RecoilAtomTypes.field, + ~blikCode: RecoilAtomTypes.field, + ~isCardValid, + ~isExpiryValid, + ~isCVCValid, + ~cardNumber, + ~cardExpiry, + ~cvcNumber, + ~setAreRequiredFieldsValid, + ~setAreRequiredFieldsEmpty, +) => { + React.useEffect7(() => { + let areRequiredFieldsValid = fieldsArr->Js.Array2.reduce((acc, paymentMethodFields) => { + acc && + switch paymentMethodFields { + | Email => email.isValid + | FullName => Some(fullName.value !== "") + | Country => Some(country !== "" || countryNames->Belt.Array.length === 0) + | AddressCountry(countryArr) => Some(country !== "" || countryArr->Belt.Array.length === 0) + | BillingName => Some(billingName.value !== "") + | AddressLine1 => Some(line1.value !== "") + | AddressLine2 => Some(line2.value !== "") + | Bank => Some(selectedBank !== "" || bankNames->Belt.Array.length === 0) + | PhoneNumber => Some(phone.value !== "") + | StateAndCity => Some(state.value !== "" && city.value !== "") + | CountryAndPincode(countryArr) => + Some( + (country !== "" || countryArr->Belt.Array.length === 0) && + postalCode.isValid->Belt.Option.getWithDefault(false), + ) + | AddressCity => Some(city.value !== "") + | AddressPincode => postalCode.isValid + | AddressState => Some(state.value !== "") + | BlikCode => Some(blikCode.value !== "") + | Currency(currencyArr) => Some(currency !== "" || currencyArr->Belt.Array.length === 0) + | CardNumber => isCardValid + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear => isExpiryValid + | CardCvc => isCVCValid + | CardExpiryAndCvc => + Some( + isExpiryValid->Belt.Option.getWithDefault(false) && + isCVCValid->Belt.Option.getWithDefault(false), + ) + | _ => Some(true) + }->Belt.Option.getWithDefault(false) + }, true) + setAreRequiredFieldsValid(._ => areRequiredFieldsValid) + + let areRequiredFieldsEmpty = fieldsArr->Js.Array2.reduce((acc, paymentMethodFields) => { + acc || + switch paymentMethodFields { + | Email => email.value === "" + | FullName => fullName.value === "" + | Country => country === "" && countryNames->Belt.Array.length > 0 + | AddressCountry(countryArr) => country === "" && countryArr->Belt.Array.length > 0 + | BillingName => billingName.value === "" + | AddressLine1 => line1.value === "" + | AddressLine2 => line2.value === "" + | Bank => selectedBank === "" && bankNames->Belt.Array.length > 0 + | StateAndCity => city.value === "" || state.value === "" + | CountryAndPincode(countryArr) => + (country === "" && countryArr->Belt.Array.length > 0) || postalCode.value === "" + | PhoneNumber => phone.value === "" + | AddressCity => city.value === "" + | AddressPincode => postalCode.value === "" + | AddressState => state.value === "" + | BlikCode => blikCode.value === "" + | Currency(currencyArr) => currency === "" && currencyArr->Belt.Array.length > 0 + | CardNumber => cardNumber === "" + | CardExpiryMonth => + let (month, _) = CardUtils.getExpiryDates(cardExpiry) + month === "" + | CardExpiryYear => + let (_, year) = CardUtils.getExpiryDates(cardExpiry) + year === "" + | CardExpiryMonthAndYear => + let (month, year) = CardUtils.getExpiryDates(cardExpiry) + month === "" || year === "" + | CardCvc => cvcNumber === "" + | CardExpiryAndCvc => + let (month, year) = CardUtils.getExpiryDates(cardExpiry) + month === "" || year === "" || cvcNumber === "" + | _ => false + } + }, false) + setAreRequiredFieldsEmpty(._ => areRequiredFieldsEmpty) + None + }, ( + fieldsArr, + currency, + fullName.value, + country, + billingName.value, + line1.value, + ( + email, + line2.value, + selectedBank, + phone.value, + city.value, + postalCode.value, + state.value, + blikCode.value, + isCardValid, + isExpiryValid, + isCVCValid, + cardNumber, + cardExpiry, + cvcNumber, + ), + )) +} + +let setInitialRequiredFieldsHook = ( + ~requiredFieldsType: Js.Array2.t, + ~email: RecoilAtomTypes.field, + ~setEmail, + ~fullName, + ~setFullName, + ~line1, + ~setLine1, + ~line2, + ~setLine2, + ~state, + ~setState, + ~city, + ~setCity, + ~postalCode, + ~setPostalCode, + ~country, + ~setCountry, + ~paymentMethodType, + ~phone, + ~setPhone, + ~blikCode, + ~setBlikCode, + ~billingName, + ~setBillingName, + ~currency, + ~setCurrency, + ~selectedBank, + ~setSelectedBank +) => { + React.useEffect0(() => { + let getNameValue = (item: PaymentMethodsRecord.required_fields) => { + requiredFieldsType + ->Js.Array2.filter(requiredFields => requiredFields.field_type === item.field_type) + ->Js.Array2.reduce((acc, item) => { + let requiredFieldsArr = item.required_field->Js.String2.split(".") + switch requiredFieldsArr + ->Belt.Array.get(requiredFieldsArr->Belt.Array.length - 1) + ->Belt.Option.getWithDefault("") { + | "first_name" => item.value->Js.String2.concat(acc) + | "last_name" => acc->Js.String2.concatMany([" ", item.value]) + | _ => acc + } + }, "") + ->Js.String2.trim + } + + let setFields = ( + setMethod: (. RecoilAtomTypes.field => RecoilAtomTypes.field) => unit, + field: RecoilAtomTypes.field, + item: PaymentMethodsRecord.required_fields, + isNameField, + ) => { + if isNameField && field.value === "" { + setMethod(.prev => { + ...prev, + value: getNameValue(item), + }) + } else if field.value === "" { + setMethod(.prev => { + ...prev, + value: item.value, + }) + } + } + + requiredFieldsType->Js.Array2.forEach(requiredField => { + let value = requiredField.value + switch requiredField.field_type { + | Email => { + let emailValue = email.value + setFields(setEmail, email, requiredField, false) + if emailValue === "" { + let newEmail: RecoilAtomTypes.field = { + value, + isValid: None, + errorString: "", + } + Utils.checkEmailValid(newEmail, setEmail) + } + } + | FullName => setFields(setFullName, fullName, requiredField, true) + | AddressLine1 => setFields(setLine1, line1, requiredField, false) + | AddressLine2 => setFields(setLine2, line2, requiredField, false) + | StateAndCity => { + setFields(setState, state, requiredField, false) + setFields(setCity, city, requiredField, false) + } + | CountryAndPincode(_) => { + setFields(setPostalCode, postalCode, requiredField, false) + if value !== "" && country === "" { + let countryCode = + Country.getCountry(paymentMethodType) + ->Js.Array2.filter(item => item.isoAlpha2 === value) + ->Belt.Array.get(0) + ->Belt.Option.getWithDefault(Country.defaultTimeZone) + setCountry(_ => countryCode.countryName) + } + } + | AddressState => setFields(setState, state, requiredField, false) + | AddressCity => setFields(setCity, city, requiredField, false) + | AddressPincode => setFields(setPostalCode, postalCode, requiredField, false) + | PhoneNumber => setFields(setPhone, phone, requiredField, false) + | BlikCode => setFields(setBlikCode, blikCode, requiredField, false) + | BillingName => setFields(setBillingName, billingName, requiredField, true) + | Country + | AddressCountry(_) => + if value !== "" { + let defaultCountry = + Country.getCountry(paymentMethodType) + ->Js.Array2.filter(item => item.isoAlpha2 === value) + ->Belt.Array.get(0) + ->Belt.Option.getWithDefault(Country.defaultTimeZone) + setCountry(_ => defaultCountry.countryName) + } + | Currency(_) => + if value !== "" && currency === "" { + setCurrency(_ => value) + } + | Bank => + if value !== "" && selectedBank === "" { + setSelectedBank(_ => value) + } + | StateAndCity + | CountryAndPincode(_) + | SpecialField(_) + | InfoElement + | CardNumber + | CardExpiryMonth + | CardExpiryYear + | CardExpiryMonthAndYear + | CardCvc + | CardExpiryAndCvc + | None => () + } + }) + None + }) +} + +let requiredFieldsBodyHook = ( + ~requiredFieldsType: Js.Array2.t, + ~email: RecoilAtomTypes.field, + ~fullName: RecoilAtomTypes.field, + ~billingName: RecoilAtomTypes.field, + ~line1: RecoilAtomTypes.field, + ~line2: RecoilAtomTypes.field, + ~phone: RecoilAtomTypes.field, + ~state: RecoilAtomTypes.field, + ~city: RecoilAtomTypes.field, + ~postalCode: RecoilAtomTypes.field, + ~blikCode: RecoilAtomTypes.field, + ~currency, + ~country, + ~selectedBank, + ~paymentMethodType, + ~cardNumber, + ~cardExpiry, + ~cvcNumber, + ~isSavedCardFlow, + ~isAllStoredCardsHaveName, + ~setRequiredFieldsBody, +) => { + React.useEffect1(() => { + let getName = (item: PaymentMethodsRecord.required_fields, field: RecoilAtomTypes.field) => { + let fieldNameArr = field.value->Js.String2.split(" ") + let requiredFieldsArr = item.required_field->Js.String2.split(".") + switch requiredFieldsArr + ->Belt.Array.get(requiredFieldsArr->Belt.Array.length - 1) + ->Belt.Option.getWithDefault("") { + | "first_name" => fieldNameArr->Belt.Array.get(0)->Belt.Option.getWithDefault(field.value) + | "last_name" => fieldNameArr->Belt.Array.sliceToEnd(1)->Js.Array2.reduce((acc, item) => { + acc ++ item + }, "") + | _ => field.value + } + } + + let requiredFieldsBody = + requiredFieldsType + ->Js.Array2.filter(item => item.field_type !== None) + ->Js.Array2.reduce((acc, item) => { + let value = switch item.field_type { + | Email => email.value + | FullName => getName(item, fullName) + | AddressLine1 => line1.value + | AddressLine2 => line2.value + | AddressCity => city.value + | AddressPincode => postalCode.value + | AddressState => state.value + | BlikCode => blikCode.value + | PhoneNumber => phone.value + | Currency(_) => currency + | Country => country + | Bank => selectedBank + | BillingName => getName(item, billingName) + | AddressCountry(_) => { + let countryCode = + Country.getCountry(paymentMethodType) + ->Js.Array2.filter(item => item.countryName === country) + ->Belt.Array.get(0) + ->Belt.Option.getWithDefault(Country.defaultTimeZone) + countryCode.isoAlpha2 + } + | CardNumber => cardNumber->CardUtils.clearSpaces + | CardExpiryMonth => + let (month, _) = CardUtils.getExpiryDates(cardExpiry) + month + | CardExpiryYear => + let (_, year) = CardUtils.getExpiryDates(cardExpiry) + year + | CardCvc => cvcNumber + | StateAndCity + | CountryAndPincode(_) + | SpecialField(_) + | InfoElement + | CardExpiryMonthAndYear + | CardExpiryAndCvc + | None => "" + } + switch item.field_type { + | StateAndCity => + acc->Js.Dict.set("billing.address.city", city.value->Js.Json.string) + acc->Js.Dict.set("billing.address.state", state.value->Js.Json.string) + | CountryAndPincode(_) => + acc->Js.Dict.set("billing.address.country", city.value->Js.Json.string) + acc->Js.Dict.set("billing.address.zip", postalCode.value->Js.Json.string) + | _ => () + } + 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->Js.Dict.set( + "payment_method_data.card_token.card_holder_name", + value->Js.Json.string, + ) + } + } else { + acc->Js.Dict.set(item.required_field, value->Js.Json.string) + } + acc + }, Js.Dict.empty()) + + setRequiredFieldsBody(_ => requiredFieldsBody) + None + }, [ + fullName.value, + email.value, + line1.value, + line2.value, + city.value, + postalCode.value, + state.value, + blikCode.value, + phone.value, + currency, + billingName.value, + country, + cardNumber, + cardExpiry, + cvcNumber, + ]) +} diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index 46dd4bad3..d1baab3d0 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -25,34 +25,9 @@ let cardPaymentBody = (~cardNumber, ~month, ~year, ~cardHolderName, ~cvcNumber, ), ] -let bancontactBody = (~cardNumber, ~month, ~year, ~cardHolderName) => [ +let bancontactBody = () => [ ("payment_method", "bank_redirect"->Js.Json.string), ("payment_method_type", "bancontact_card"->Js.Json.string), - ( - "payment_method_data", - [ - ( - "bank_redirect", - [ - ( - "bancontact_card", - [ - ("card_number", cardNumber->CardUtils.clearSpaces->Js.Json.string), - ("card_exp_month", month->Js.Json.string), - ("card_exp_year", year->Js.Json.string), - ("card_holder_name", cardHolderName->Js.Json.string), - ] - ->Js.Dict.fromArray - ->Js.Json.object_, - ), - ] - ->Js.Dict.fromArray - ->Js.Json.object_, - ), - ] - ->Js.Dict.fromArray - ->Js.Json.object_, - ), ] let savedCardBody = (~paymentToken, ~customerId, ~cvcNumber) => [ diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index ad36c4358..24170291c 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -231,10 +231,26 @@ let getDisplayNameAndIcon = ( } } -let updateDynamicFields = (arr: Js.Array2.t, ()) => { +let combineStateAndCity = arr => { open PaymentMethodsRecord let hasStateAndCity = arr->Js.Array2.includes(AddressState) && arr->Js.Array2.includes(AddressCity) + if hasStateAndCity { + arr->Js.Array2.push(StateAndCity)->ignore + arr->Js.Array2.filter(item => + switch item { + | AddressCity + | AddressState => false + | _ => true + } + ) + } else { + arr + } +} + +let combineCountryAndPostal = arr => { + open PaymentMethodsRecord let hasCountryAndPostal = arr ->Js.Array2.filter(item => @@ -255,43 +271,60 @@ let updateDynamicFields = (arr: Js.Array2.t { - arr->Js.Array2.push(StateAndCity)->ignore - arr->Js.Array2.push(CountryAndPincode(options))->ignore - arr->Js.Array2.filter(item => - switch item { - | AddressCity - | AddressPincode - | AddressState - | AddressCountry(_) => false - | _ => true - } - ) + if hasCountryAndPostal { + arr->Js.Array2.push(CountryAndPincode(options))->ignore + arr->Js.Array2.filter(item => + switch item { + | AddressPincode + | AddressCountry(_) => false + | _ => true } - | (true, false) => { - arr->Js.Array2.push(StateAndCity)->ignore - arr->Js.Array2.filter(item => - switch item { - | AddressCity - | AddressState => false - | _ => true - } - ) + ) + } else { + arr + } +} + +let combineCardExpiryMonthAndYear = arr => { + open PaymentMethodsRecord + let hasCardExpiryMonthAndYear = + arr->Js.Array2.includes(CardExpiryMonth) && arr->Js.Array2.includes(CardExpiryYear) + if hasCardExpiryMonthAndYear { + arr->Js.Array2.push(CardExpiryMonthAndYear)->ignore + arr->Js.Array2.filter(item => + switch item { + | CardExpiryMonth + | CardExpiryYear => false + | _ => true } - | (false, true) => { - arr->Js.Array2.push(CountryAndPincode(options))->ignore - arr->Js.Array2.filter(item => - switch item { - | AddressPincode - | AddressCountry(_) => false - | _ => true - } - ) + ) + } else { + arr + } +} + +let combineCardExpiryAndCvc = arr => { + open PaymentMethodsRecord + let hasCardExpiryAndCvc = + arr->Js.Array2.includes(CardExpiryMonthAndYear) && arr->Js.Array2.includes(CardCvc) + if hasCardExpiryAndCvc { + arr->Js.Array2.push(CardExpiryAndCvc)->ignore + arr->Js.Array2.filter(item => + switch item { + | CardExpiryMonthAndYear + | CardCvc => false + | _ => true } - | (_, _) => arr - } + ) + } else { + arr } - newArr +} + +let updateDynamicFields = (arr: Js.Array2.t, ()) => { + arr + ->combineStateAndCity + ->combineCountryAndPostal + ->combineCardExpiryMonthAndYear + ->combineCardExpiryAndCvc } From b590bc5d2eda253419abf8eea3bd7b7e853b57d7 Mon Sep 17 00:00:00 2001 From: Arush Date: Tue, 16 Jan 2024 14:41:10 +0530 Subject: [PATCH 2/2] feat: HS-181: Fixed card number icon not changing and resolving comments --- src/CardUtils.res | 24 ++--- src/Components/DynamicFields.res | 83 ++-------------- src/Components/SavedMethods.res | 21 ++-- src/Payment.res | 12 ++- src/Payments/CardPayment.res | 4 +- src/Utilities/DynamicFieldsUtils.res | 141 +++++++++++++++------------ src/Utilities/RecoilAtoms.res | 2 +- 7 files changed, 126 insertions(+), 161 deletions(-) diff --git a/src/CardUtils.res b/src/CardUtils.res index 172240627..0ae19883f 100644 --- a/src/CardUtils.res +++ b/src/CardUtils.res @@ -642,20 +642,20 @@ let getCvcDetailsFromCvcProps = cvcProps => { } let setRightIconForCvc = (~cardEmpty, ~cardInvalid, ~color, ~cardComplete) => { - if cardEmpty { - - } else if cardInvalid { -
- -
- } else if cardComplete { - - } else { - - } + if cardEmpty { + + } else if cardInvalid { +
+ +
+ } else if cardComplete { + + } else { + } +} -let getCardDetailsHook = (~cvcNumber, ~isCvcValidValue, ~isCVCValid) => { +let useCardDetails = (~cvcNumber, ~isCvcValidValue, ~isCVCValid) => { React.useMemo3(() => { let isCardDetailsEmpty = Js.String2.length(cvcNumber) == 0 let isCardDetailsValid = isCvcValidValue == "valid" diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 9b5777bf1..9937b5cc4 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -76,7 +76,6 @@ let make = ( let setAreRequiredFieldsValid = Recoil.useSetRecoilState(areRequiredFieldsValid) let setAreRequiredFieldsEmpty = Recoil.useSetRecoilState(areRequiredFieldsEmpty) - let (email, setEmail) = Recoil.useLoggedRecoilState(userEmailAddress, "email", logger) let (line1, setLine1) = Recoil.useLoggedRecoilState(userAddressline1, "line1", logger) let (line2, setLine2) = Recoil.useLoggedRecoilState(userAddressline2, "line2", logger) let (city, setCity) = Recoil.useLoggedRecoilState(userAddressCity, "city", logger) @@ -87,15 +86,7 @@ let make = ( logger, ) let (postalCodes, setPostalCodes) = React.useState(_ => [PostalCodeType.defaultPostalCode]) - let (fullName, setFullName) = Recoil.useLoggedRecoilState(userFullName, "fullName", logger) - let (blikCode, setBlikCode) = Recoil.useLoggedRecoilState(userBlikCode, "blikCode", logger) - let (phone, setPhone) = Recoil.useLoggedRecoilState(userPhoneNumber, "phone", logger) let (currency, setCurrency) = Recoil.useLoggedRecoilState(userCurrency, "currency", logger) - let (billingName, setBillingName) = Recoil.useLoggedRecoilState( - userBillingName, - "billingName", - logger, - ) let line1Ref = React.useRef(Js.Nullable.null) let line2Ref = React.useRef(Js.Nullable.null) let cityRef = React.useRef(Js.Nullable.null) @@ -161,7 +152,7 @@ let make = ( cvcProps->CardUtils.getCvcDetailsFromCvcProps let isCvcValidValue = CardUtils.getBoolOptionVal(isCVCValid) - let (cardEmpty, cardComplete, cardInvalid) = CardUtils.getCardDetailsHook( + let (cardEmpty, cardComplete, cardInvalid) = CardUtils.useCardDetails( ~cvcNumber, ~isCVCValid, ~isCvcValidValue, @@ -236,31 +227,16 @@ let make = ( } } - DynamicFieldsUtils.requiredFieldsEmptyAndValidHook( + DynamicFieldsUtils.useRequiredFieldsEmptyAndValid( ~fieldsArr, - ~country, ~countryNames, - ~selectedBank, ~bankNames, - ~currency, - ~email, - ~fullName, - ~billingName, - ~line1, - ~line2, - ~phone, - ~state, - ~city, - ~postalCode, - ~blikCode, ~isCardValid, ~isExpiryValid, ~isCVCValid, ~cardNumber, ~cardExpiry, ~cvcNumber, - ~setAreRequiredFieldsValid, - ~setAreRequiredFieldsEmpty, ) let requiredFieldsType = @@ -271,52 +247,10 @@ let make = ( item->Utils.getDictFromJson->PaymentMethodsRecord.getRequiredFieldsFromJson(isBancontact) ) - DynamicFieldsUtils.setInitialRequiredFieldsHook( - ~requiredFieldsType, - ~email, - ~setEmail, - ~fullName, - ~setFullName, - ~line1, - ~setLine1, - ~line2, - ~setLine2, - ~state, - ~setState, - ~city, - ~setCity, - ~postalCode, - ~setPostalCode, - ~country, - ~setCountry, - ~paymentMethodType, - ~phone, - ~setPhone, - ~blikCode, - ~setBlikCode, - ~billingName, - ~setBillingName, - ~currency, - ~setCurrency, - ~selectedBank, - ~setSelectedBank, - ) + DynamicFieldsUtils.useSetInitialRequiredFields(~requiredFieldsType, ~paymentMethodType) - DynamicFieldsUtils.requiredFieldsBodyHook( + DynamicFieldsUtils.useRequiredFieldsBody( ~requiredFieldsType, - ~email, - ~fullName, - ~billingName, - ~line1, - ~line2, - ~phone, - ~state, - ~city, - ~postalCode, - ~blikCode, - ~currency, - ~country, - ~selectedBank, ~paymentMethodType, ~cardNumber, ~cardExpiry, @@ -358,7 +292,8 @@ let make = ( dynamicFieldsToRenderInsideBilling->Belt.Array.get(0) === Some(InfoElement) let isRenderDynamicFieldsInsideBilling = - dynamicFieldsToRenderInsideBilling->Js.Array2.length > 1 || !isOnlyInfoElementPresent + dynamicFieldsToRenderInsideBilling->Js.Array2.length > 0 && + (dynamicFieldsToRenderInsideBilling->Js.Array2.length > 1 || !isOnlyInfoElementPresent) { fieldsArr->Js.Array2.length > 0 @@ -499,7 +434,7 @@ let make = ( ->React.array}
| PhoneNumber => | StateAndCity => -
+
| CountryAndPincode(countryArr) => -
+
{ let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) - let (showFeilds, setShowFeilds) = Recoil.useRecoilState(RecoilAtoms.showCardFeildsAtom) + let (showFields, setShowFields) = Recoil.useRecoilState(RecoilAtoms.showCardFieldsAtom) let (token, _) = paymentToken let savedCardlength = savedMethods->Js.Array2.length let bottomElement = { @@ -46,7 +46,7 @@ let make = ( className="flex flex-col overflow-auto h-auto no-scrollbar animate-slowShow" style={ReactDOMStyle.make(~padding="5px", ())}> {if ( - savedCardlength === 0 && (loadSavedCards === PaymentType.LoadingSavedCards || !showFeilds) + savedCardlength === 0 && (loadSavedCards === PaymentType.LoadingSavedCards || !showFields) ) {
} else { - + }} Js.Array.length !== 0}> - +
{ - setShowFeilds(._ => true) + setShowFields(._ => true) }}> - {React.string(localeString.addNewCard)} + + {React.string(localeString.addNewCard)}
diff --git a/src/Payment.res b/src/Payment.res index d1ab5200a..74baac979 100644 --- a/src/Payment.res +++ b/src/Payment.res @@ -13,7 +13,8 @@ let make = (~paymentMode, ~integrateError, ~logger) => { let {localeString} = Recoil.useRecoilValueFromAtom(configAtom) let keys = Recoil.useRecoilValueFromAtom(keys) let cardScheme = Recoil.useRecoilValueFromAtom(RecoilAtoms.cardBrand) - let showFeilds = Recoil.useRecoilValueFromAtom(RecoilAtoms.showCardFeildsAtom) + let showFields = Recoil.useRecoilValueFromAtom(RecoilAtoms.showCardFieldsAtom) + let selectedOption = Recoil.useRecoilValueFromAtom(selectedOptionAtom) let paymentToken = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentTokenAtom) let (token, _) = paymentToken @@ -47,8 +48,11 @@ let make = (~paymentMode, ~integrateError, ~logger) => { let (cardBrand, maxCardLength) = React.useMemo3(() => { let brand = getCardBrand(cardNumber) let maxLength = getMaxLength(cardNumber) - (brand == "" && !showFeilds) || !showFeilds ? (cardScheme, maxLength) : (brand, maxLength) - }, (cardNumber, cardScheme, showFeilds)) + let isNotBancontact = selectedOption !== "bancontact_card" && brand == "" + ((brand == "" && !showFields) || !showFields) && isNotBancontact + ? (cardScheme, maxLength) + : (brand, maxLength) + }, (cardNumber, cardScheme, showFields)) let clientTimeZone = dateTimeFormat(.).resolvedOptions(.).timeZone let clientCountry = Utils.getClientCountry(clientTimeZone) @@ -209,7 +213,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { setCardError(_ => "") setExpiryError(_ => "") None - }, (token, showFeilds)) + }, (token, showFields)) let submitValue = (_ev, confirmParam) => { let validFormat = switch paymentMode->getPaymentMode { diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 05d69151e..6b501498d 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -62,7 +62,7 @@ let make = ( ) let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Card) let (savedMethods, setSavedMethods) = React.useState(_ => []) - let (showFields, setShowFields) = Recoil.useRecoilState(RecoilAtoms.showCardFeildsAtom) + let (showFields, setShowFields) = Recoil.useRecoilState(RecoilAtoms.showCardFieldsAtom) let (paymentToken, setPaymentToken) = Recoil.useRecoilState(RecoilAtoms.paymentTokenAtom) let (token, _) = paymentToken let setComplete = Recoil.useSetRecoilState(RecoilAtoms.fieldsComplete) @@ -141,7 +141,7 @@ let make = ( }, (empty, complete)) let isCvcValidValue = CardUtils.getBoolOptionVal(isCVCValid) - let (cardEmpty, cardComplete, cardInvalid) = CardUtils.getCardDetailsHook( + let (cardEmpty, cardComplete, cardInvalid) = CardUtils.useCardDetails( ~cvcNumber, ~isCVCValid, ~isCvcValidValue, diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 926e7f16b..3e53dbb2a 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -1,29 +1,30 @@ -let requiredFieldsEmptyAndValidHook = ( +let useRequiredFieldsEmptyAndValid = ( ~fieldsArr: Js.Array2.t, - ~country, ~countryNames, - ~selectedBank, ~bankNames, - ~currency, - ~email: RecoilAtomTypes.field, - ~fullName: RecoilAtomTypes.field, - ~billingName: RecoilAtomTypes.field, - ~line1: RecoilAtomTypes.field, - ~line2: RecoilAtomTypes.field, - ~phone: RecoilAtomTypes.field, - ~state: RecoilAtomTypes.field, - ~city: RecoilAtomTypes.field, - ~postalCode: RecoilAtomTypes.field, - ~blikCode: RecoilAtomTypes.field, ~isCardValid, ~isExpiryValid, ~isCVCValid, ~cardNumber, ~cardExpiry, ~cvcNumber, - ~setAreRequiredFieldsValid, - ~setAreRequiredFieldsEmpty, ) => { + let email = Recoil.useRecoilValueFromAtom(RecoilAtoms.userEmailAddress) + let fullName = Recoil.useRecoilValueFromAtom(RecoilAtoms.userFullName) + let billingName = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBillingName) + let line1 = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressline1) + let line2 = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressline2) + let phone = Recoil.useRecoilValueFromAtom(RecoilAtoms.userPhoneNumber) + let state = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressState) + let city = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressCity) + let postalCode = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressPincode) + let blikCode = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBlikCode) + let country = Recoil.useRecoilValueFromAtom(RecoilAtoms.userCountry) + let selectedBank = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBank) + let currency = Recoil.useRecoilValueFromAtom(RecoilAtoms.userCurrency) + let setAreRequiredFieldsValid = Recoil.useSetRecoilState(RecoilAtoms.areRequiredFieldsValid) + let setAreRequiredFieldsEmpty = Recoil.useSetRecoilState(RecoilAtoms.areRequiredFieldsEmpty) + React.useEffect7(() => { let areRequiredFieldsValid = fieldsArr->Js.Array2.reduce((acc, paymentMethodFields) => { acc && @@ -128,36 +129,53 @@ let requiredFieldsEmptyAndValidHook = ( )) } -let setInitialRequiredFieldsHook = ( +let useSetInitialRequiredFields = ( ~requiredFieldsType: Js.Array2.t, - ~email: RecoilAtomTypes.field, - ~setEmail, - ~fullName, - ~setFullName, - ~line1, - ~setLine1, - ~line2, - ~setLine2, - ~state, - ~setState, - ~city, - ~setCity, - ~postalCode, - ~setPostalCode, - ~country, - ~setCountry, ~paymentMethodType, - ~phone, - ~setPhone, - ~blikCode, - ~setBlikCode, - ~billingName, - ~setBillingName, - ~currency, - ~setCurrency, - ~selectedBank, - ~setSelectedBank ) => { + let logger = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) + let (email, setEmail) = Recoil.useLoggedRecoilState(RecoilAtoms.userEmailAddress, "email", logger) + let (fullName, setFullName) = Recoil.useLoggedRecoilState( + RecoilAtoms.userFullName, + "fullName", + logger, + ) + let (billingName, setBillingName) = Recoil.useLoggedRecoilState( + RecoilAtoms.userBillingName, + "billingName", + logger, + ) + let (line1, setLine1) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressline1, "line1", logger) + let (line2, setLine2) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressline2, "line2", logger) + let (phone, setPhone) = Recoil.useLoggedRecoilState(RecoilAtoms.userPhoneNumber, "phone", logger) + let (state, setState) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressState, "state", logger) + let (city, setCity) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressCity, "city", logger) + let (postalCode, setPostalCode) = Recoil.useLoggedRecoilState( + RecoilAtoms.userAddressPincode, + "postal_code", + logger, + ) + let (blikCode, setBlikCode) = Recoil.useLoggedRecoilState( + RecoilAtoms.userBlikCode, + "blikCode", + logger, + ) + let (country, setCountry) = Recoil.useLoggedRecoilState( + RecoilAtoms.userCountry, + "country", + logger, + ) + let (selectedBank, setSelectedBank) = Recoil.useLoggedRecoilState( + RecoilAtoms.userBank, + "selectedBank", + logger, + ) + let (currency, setCurrency) = Recoil.useLoggedRecoilState( + RecoilAtoms.userCurrency, + "currency", + logger, + ) + React.useEffect0(() => { let getNameValue = (item: PaymentMethodsRecord.required_fields) => { requiredFieldsType @@ -224,7 +242,7 @@ let setInitialRequiredFieldsHook = ( ->Js.Array2.filter(item => item.isoAlpha2 === value) ->Belt.Array.get(0) ->Belt.Option.getWithDefault(Country.defaultTimeZone) - setCountry(_ => countryCode.countryName) + setCountry(. _ => countryCode.countryName) } } | AddressState => setFields(setState, state, requiredField, false) @@ -241,15 +259,15 @@ let setInitialRequiredFieldsHook = ( ->Js.Array2.filter(item => item.isoAlpha2 === value) ->Belt.Array.get(0) ->Belt.Option.getWithDefault(Country.defaultTimeZone) - setCountry(_ => defaultCountry.countryName) + setCountry(. _ => defaultCountry.countryName) } | Currency(_) => if value !== "" && currency === "" { - setCurrency(_ => value) + setCurrency(. _ => value) } | Bank => if value !== "" && selectedBank === "" { - setSelectedBank(_ => value) + setSelectedBank(. _ => value) } | StateAndCity | CountryAndPincode(_) @@ -268,21 +286,8 @@ let setInitialRequiredFieldsHook = ( }) } -let requiredFieldsBodyHook = ( +let useRequiredFieldsBody = ( ~requiredFieldsType: Js.Array2.t, - ~email: RecoilAtomTypes.field, - ~fullName: RecoilAtomTypes.field, - ~billingName: RecoilAtomTypes.field, - ~line1: RecoilAtomTypes.field, - ~line2: RecoilAtomTypes.field, - ~phone: RecoilAtomTypes.field, - ~state: RecoilAtomTypes.field, - ~city: RecoilAtomTypes.field, - ~postalCode: RecoilAtomTypes.field, - ~blikCode: RecoilAtomTypes.field, - ~currency, - ~country, - ~selectedBank, ~paymentMethodType, ~cardNumber, ~cardExpiry, @@ -291,6 +296,20 @@ let requiredFieldsBodyHook = ( ~isAllStoredCardsHaveName, ~setRequiredFieldsBody, ) => { + let email = Recoil.useRecoilValueFromAtom(RecoilAtoms.userEmailAddress) + let fullName = Recoil.useRecoilValueFromAtom(RecoilAtoms.userFullName) + let billingName = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBillingName) + let line1 = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressline1) + let line2 = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressline2) + let phone = Recoil.useRecoilValueFromAtom(RecoilAtoms.userPhoneNumber) + let state = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressState) + let city = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressCity) + let postalCode = Recoil.useRecoilValueFromAtom(RecoilAtoms.userAddressPincode) + let blikCode = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBlikCode) + let country = Recoil.useRecoilValueFromAtom(RecoilAtoms.userCountry) + let selectedBank = Recoil.useRecoilValueFromAtom(RecoilAtoms.userBank) + let currency = Recoil.useRecoilValueFromAtom(RecoilAtoms.userCurrency) + React.useEffect1(() => { let getName = (item: PaymentMethodsRecord.required_fields, field: RecoilAtomTypes.field) => { let fieldNameArr = field.value->Js.String2.split(" ") diff --git a/src/Utilities/RecoilAtoms.res b/src/Utilities/RecoilAtoms.res index 7018702e3..90ccfe50d 100644 --- a/src/Utilities/RecoilAtoms.res +++ b/src/Utilities/RecoilAtoms.res @@ -13,7 +13,7 @@ let isConfirmBlocked = Recoil.atom(. "isConfirmBlocked", false) let switchToCustomPod = Recoil.atom(. "switchToCustomPod", false) let selectedOptionAtom = Recoil.atom(. "selectedOption", "") let paymentTokenAtom = Recoil.atom(. "paymentToken", ("", "")) -let showCardFeildsAtom = Recoil.atom(. "showCardFeilds", false) +let showCardFieldsAtom = Recoil.atom(. "showCardFields", false) let phoneJson = Recoil.atom(. "phoneJson", Loading) let cardBrand = Recoil.atom(. "cardBrand", "")