From 4e42eeec6c5042b01b16af855eaa18a2b46d26da Mon Sep 17 00:00:00 2001 From: Pradeep Kumar Date: Mon, 27 Jan 2025 23:52:56 +0530 Subject: [PATCH] feat: added BECS Bank Debit --- src/components/common/DynamicFields.res | 42 ++++-- src/hooks/PMListModifier.res | 17 +++ src/hooks/S3ApiHook.res | 6 + src/icons/Icon.res | 2 + src/pages/payment/Redirect.res | 129 ++++++++++++++++-- src/types/LocaleDataType.res | 4 + src/types/PaymentMethodListType.res | 42 +++++- src/types/RequiredFieldsTypes.res | 12 ++ src/types/Types.res | 6 + .../reusableCodeFromWeb/Validation.res | 4 + 10 files changed, 243 insertions(+), 21 deletions(-) diff --git a/src/components/common/DynamicFields.res b/src/components/common/DynamicFields.res index e7763b97..f99f3f14 100644 --- a/src/components/common/DynamicFields.res +++ b/src/components/common/DynamicFields.res @@ -45,6 +45,8 @@ module RenderField = { ~required_fields_type: RequiredFieldsTypes.required_fields_type, ~setFinalJsonDict, ~finalJsonDict, + ~customValidationFunc, + ~customOnChangeFunc, ~isSaveCardsFlow, ~statesAndCountry: CountryStateDataContext.data, ~keyToTrigerButtonClickError, @@ -101,28 +103,36 @@ module RenderField = { ) switch requiredFieldPath { | StringField(stringFieldPath) => - let tempValid = RequiredFieldsTypes.checkIsValid( - ~text, - ~field_type=required_fields_type.field_type, - ~localeObject, - ) - + let validationErrMsg = switch customValidationFunc { + | Some(validation) => + validation( + ~text, + ~field_type=required_fields_type.field_type, + ~display_name=Some(required_fields_type.display_name), + ) + | None => + RequiredFieldsTypes.checkIsValid( + ~text, + ~field_type=required_fields_type.field_type, + ~localeObject, + ) + } let isCountryField = switch required_fields_type.field_type { | AddressCountry(_) => true | _ => false } - setErrorMesage(_ => tempValid) + setErrorMesage(_ => validationErrMsg) setFinalJsonDict(prev => { let newData = Dict.assign(Dict.make(), prev) if isCountryField { let stateKey = getKey(stringFieldPath, "state") switch newData->Dict.get(stateKey) { - | Some(_) => newData->Dict.set(stateKey, (JSON.Encode.null, tempValid)) + | Some(_) => newData->Dict.set(stateKey, (JSON.Encode.null, validationErrMsg)) | None => () } } - newData->Dict.set(stringFieldPath, (text->JSON.Encode.string, tempValid)) + newData->Dict.set(stringFieldPath, (text->JSON.Encode.string, validationErrMsg)) newData }) | FullNameField(firstNameFieldPath, lastNameFieldPath) => @@ -181,7 +191,11 @@ module RenderField = { setVal(val) } let onChange = text => { - setVal(_ => text) + switch customOnChangeFunc { + | Some(setter) => + setVal(prev => setter(~text, ~field_type=required_fields_type.field_type, ~prev)) + | None => setVal(_ => text) + } } React.useEffect1(() => { keyToTrigerButtonClickError != 0 @@ -304,6 +318,8 @@ module Fields = { ~setFinalJsonDict, ~isSaveCardsFlow, ~statesAndCountry: CountryStateDataContext.data, + ~customValidationFunc, + ~customOnChangeFunc, ~keyToTrigerButtonClickError, ) => { fields @@ -315,6 +331,8 @@ module Fields = { key={index->Int.toString} isSaveCardsFlow statesAndCountry + customValidationFunc + customOnChangeFunc finalJsonDict setFinalJsonDict keyToTrigerButtonClickError @@ -337,6 +355,8 @@ let make = ( ~keyToTrigerButtonClickError, ~shouldRenderShippingFields=false, //To render shipping fields ~displayPreValueFields=false, + ~customValidationFunc=None, + ~customOnChangeFunc=None, ~fieldsOrder: array=[Other, Billing, Shipping], ) => { // let {component} = ThemebasedStyle.useThemeBasedStyle() @@ -521,6 +541,8 @@ let make = ( setFinalJsonDict isSaveCardsFlow statesAndCountry + customValidationFunc + customOnChangeFunc keyToTrigerButtonClickError /> diff --git a/src/hooks/PMListModifier.res b/src/hooks/PMListModifier.res index 0e73cf64..2400e8fa 100644 --- a/src/hooks/PMListModifier.res +++ b/src/hooks/PMListModifier.res @@ -250,6 +250,23 @@ let useListModifier = () => { isScreenFocus redirectProp=CRYPTO(cryptoVal) fields setConfirmButtonDataRef />, }) + | BANK_DEBIT(bankDebitVal) => + let fields = + redirectionList + ->Array.find(l => l.name == bankDebitVal.payment_method_type) + ->Option.getOr(Types.defaultRedirectType) + Some({ + name: fields.text, + componentHoc: (~isScreenFocus, ~setConfirmButtonDataRef) => + , + }) } { | Some(tab) => let isInvalidScreen = diff --git a/src/hooks/S3ApiHook.res b/src/hooks/S3ApiHook.res index e135a9d9..4af9f4e0 100644 --- a/src/hooks/S3ApiHook.res +++ b/src/hooks/S3ApiHook.res @@ -242,6 +242,12 @@ let getLocaleStrings: Js.Json.t => localeStrings = data => { "deletePaymentMethod", defaultLocale.deletePaymentMethod->Option.getOr("delete"), ), + enterValidDigitsText: Utils.getString( + res, + "enterValidDigitsText", + defaultLocale.enterValidDigitsText, + ), + digitsText: Utils.getString(res, "digitsText", defaultLocale.digitsText), } | None => defaultLocale } diff --git a/src/icons/Icon.res b/src/icons/Icon.res index 18dd6ff2..5471ffb6 100644 --- a/src/icons/Icon.res +++ b/src/icons/Icon.res @@ -13,6 +13,7 @@ let checkboxnotclicked = `` let google_pay = ` ` let applePayList = `` +let becsDebit = `` type uri = { uri: string, local: bool, @@ -59,6 +60,7 @@ let make = ( | "defaulttick" => defaultTick | "google pay" => google_pay | "apple pay" => applePayList + | "becs debit" => becsDebit | _ => "" } localName == "" diff --git a/src/pages/payment/Redirect.res b/src/pages/payment/Redirect.res index eb10246f..8540190a 100644 --- a/src/pages/payment/Redirect.res +++ b/src/pages/payment/Redirect.res @@ -1,6 +1,7 @@ open ReactNative open PaymentMethodListType open CustomPicker +open RequiredFieldsTypes type klarnaSessionCheck = { isKlarna: bool, @@ -13,7 +14,7 @@ let make = ( ~fields: Types.redirectTypeJson, ~isScreenFocus, ~isDynamicFields: bool=false, - ~dynamicFields: RequiredFieldsTypes.required_fields=[], + ~dynamicFields: required_fields=[], ~setConfirmButtonDataRef: React.element => unit, ~sessionObject: SessionsType.sessions=SessionsType.defaultToken, ) => { @@ -28,8 +29,19 @@ let make = ( } } + let bankDebitType: PaymentMethodListType.payment_method_types_bank_debit = switch redirectProp { + | BANK_DEBIT(bankDebitVal) => bankDebitVal + | _ => { + payment_method: "", + payment_method_type: "", + payment_method_type_var: NONE, + payment_experience: [], + required_field: [], + } + } let (nativeProp, _) = React.useContext(NativePropContext.nativePropContext) let (allApiData, _) = React.useContext(AllApiDataContext.allApiDataContext) + let (launchKlarna, setLaunchKlarna) = React.useState(_ => None) let (email, setEmail) = React.useState(_ => None) let (isEmailValid, setIsEmailValid) = React.useState(_ => None) @@ -74,20 +86,20 @@ let make = ( | BANK_REDIRECT(prop) => prop.payment_method_type | CRYPTO(prop) => prop.payment_method_type | OPEN_BANKING(prop) => prop.payment_method_type + | BANK_DEBIT(prop) => prop.payment_method_type } let paymentExperience = switch redirectProp { - | CARD(_) => None + | CARD(_) + | BANK_REDIRECT(_) => + None | WALLET(prop) => prop.payment_experience ->Array.get(0) ->Option.map(paymentExperience => paymentExperience.payment_experience_type_decode) - | PAY_LATER(prop) => prop.payment_experience ->Array.get(0) ->Option.map(paymentExperience => paymentExperience.payment_experience_type_decode) - - | BANK_REDIRECT(_) => None | OPEN_BANKING(prop) => prop.payment_experience ->Array.get(0) @@ -96,6 +108,10 @@ let make = ( prop.payment_experience ->Array.get(0) ->Option.map(paymentExperience => paymentExperience.payment_experience_type_decode) + | BANK_DEBIT(prop) => + prop.payment_experience + ->Array.get(0) + ->Option.map(paymentExperience => paymentExperience.payment_experience_type_decode) } let paymentMethodType = switch redirectProp { | BANK_REDIRECT(prop) => prop.payment_method_type @@ -246,10 +262,10 @@ let make = ( ), payment_type: ?allApiData.additionalPMLData.paymentType, // mandate_data: ?( - // allApiData.mandateType != NORMAL + // allApiData.additionalPMLData.mandateType != NORMAL // ? Some({ // customer_acceptance: { - // acceptance_type: "online", + // acceptance_type: "offline", // accepted_at: Date.now()->Date.fromTime->Date.toISOString, // online: { // ip_address: ?nativeProp.hyperParams.ip, @@ -384,8 +400,7 @@ let make = ( let middleData = Dict.make() middleData->Dict.set(prop.payment_method, innerData->JSON.Encode.object) payment_method_data->Dict.set("payment_method_data", middleData->JSON.Encode.object) - let dynamic_pmd = - payment_method_data->RequiredFieldsTypes.mergeTwoFlattenedJsonDicts(dynamicFieldsJsonDict) + let dynamic_pmd = payment_method_data->mergeTwoFlattenedJsonDicts(dynamicFieldsJsonDict) processRequest( ~payment_method_data=dynamic_pmd ->Utils.getJsonObjectFromDict("payment_method_data") @@ -860,6 +875,29 @@ let make = ( ) } + let processRequestBankDebit = (prop: payment_method_types_bank_debit) => { + let dynamicFieldsArray = dynamicFieldsJson->Dict.toArray + let dynamicFieldsJsonDict = dynamicFieldsArray->Array.reduce(Dict.make(), ( + acc, + (key, (val, _)), + ) => { + acc->Dict.set(key, val) + acc + }) + + let payment_method_data = dynamicFieldsJsonDict->JSON.Encode.object->unflattenObject + processRequest( + ~payment_method_data=payment_method_data + ->Utils.getJsonObjectFromDict("payment_method_data") + ->JSON.stringifyAny + ->Option.getOr("{}") + ->JSON.parseExn, + ~payment_method=prop.payment_method, + ~payment_method_type=prop.payment_method_type, + (), + ) + } + //need refactoring let handlePressEmail = text => { setIsEmailValid(_ => text->Validation.isValidEmail) @@ -891,7 +929,14 @@ let make = ( : ((fields.fields->Array.includes("email") ? isEmailValid->Option.getOr(false) : true) && ( fields.fields->Array.includes("name") ? isNameValid->Option.getOr(false) : true )) || (fields.name == "klarna" && isKlarna) - , (isEmailValid, isNameValid, allApiData.sessions, isDynamicFields, isAllDynamicFieldValid)) + , ( + isEmailValid, + isNameValid, + allApiData.sessions, + isDynamicFields, + isAllDynamicFieldValid, + dynamicFieldsJson, + )) let handlePress = _ => { if isAllValuesValid { @@ -906,6 +951,7 @@ let make = ( | CRYPTO(prop) => processRequestCrypto(prop) | WALLET(prop) => processRequestWallet(prop) | OPEN_BANKING(prop) => processRequestOpenBanking(prop) + | BANK_DEBIT(prop) => processRequestBankDebit(prop) | _ => () } } else { @@ -941,6 +987,61 @@ let make = ( country, selectedBank, )) + + let numberOfDigitsValidation = (text, digits, display_name) => { + if text->Validation.containsOnlyDigits && text->Validation.clearSpaces->String.length > 0 { + if text->String.length == digits { + None + } else { + Some( + localeObject.enterValidDigitsText ++ + digits->Int.toString ++ + localeObject.digitsText ++ + display_name->toCamelCase, + ) + } + } else { + Some(localeObject.enterValidDetailsText) + } + } + + let customValidationFunc = React.useCallback((~text, ~field_type, ~display_name) => { + switch field_type { + | AccountNumber => + switch bankDebitType.payment_method_type_var { + | BECS => numberOfDigitsValidation(text, 9, display_name->Option.getOr("")) + | _ => None + } + | BSBNumber => numberOfDigitsValidation(text, 6, display_name->Option.getOr("")) + | _ => checkIsValid(~text, ~field_type, ~localeObject) // need for other all fields + } + }, [paymentMethod]) + + let customOnChangeFunc = React.useCallback((~text, ~field_type, ~prev) => { + switch field_type { + | AccountNumber => + switch bankDebitType.payment_method_type_var { + | BECS => + let val = text->Option.getOr("")->Validation.clearSpaces + if val->String.length <= 9 { + Some(val) + } else { + prev + } + | _ => None + } + + | BSBNumber => + let val = text->Option.getOr("")->Validation.clearSpaces + if val->String.length <= 6 { + Some(val) + } else { + prev + } + | _ => text + } + }, [paymentMethod]) + <> @@ -966,6 +1067,14 @@ let make = ( setDynamicFieldsJson keyToTrigerButtonClickError savedCardsData=None + customOnChangeFunc={switch bankDebitType.payment_method_type_var { + | BECS => Some(customOnChangeFunc) + | _ => None + }} + customValidationFunc={switch bankDebitType.payment_method_type_var { + | BECS => Some(customValidationFunc) + | _ => None + }} /> } else { fields.fields diff --git a/src/types/LocaleDataType.res b/src/types/LocaleDataType.res index 7b1bf587..0d8e5811 100644 --- a/src/types/LocaleDataType.res +++ b/src/types/LocaleDataType.res @@ -71,6 +71,8 @@ type localeStrings = { addPaymentMethodLabel: string, walletDisclaimer: string, deletePaymentMethod?: string, + enterValidDigitsText: string, + digitsText: string, } let defaultLocale = { locale: "en", @@ -145,4 +147,6 @@ let defaultLocale = { addPaymentMethodLabel: "Add new payment method", walletDisclaimer: "Wallet details will be saved upon selection", deletePaymentMethod: "Delete", + enterValidDigitsText: "Please enter valid ", + digitsText: " digits ", } diff --git a/src/types/PaymentMethodListType.res b/src/types/PaymentMethodListType.res index 6a8131bb..ee7dfa0b 100644 --- a/src/types/PaymentMethodListType.res +++ b/src/types/PaymentMethodListType.res @@ -62,6 +62,15 @@ type payment_method_types_open_banking = { required_field: RequiredFieldsTypes.required_fields, } +type bank_debit_types = BECS | NONE +type payment_method_types_bank_debit = { + payment_method: string, + payment_method_type: string, + payment_experience: array, + payment_method_type_var: bank_debit_types, + required_field: RequiredFieldsTypes.required_fields, +} + type payment_method = | CARD(payment_method_types_card) | WALLET(payment_method_types_wallet) @@ -69,6 +78,7 @@ type payment_method = | BANK_REDIRECT(payment_method_types_bank_redirect) | CRYPTO(payment_method_types_pay_later) | OPEN_BANKING(payment_method_types_open_banking) + | BANK_DEBIT(payment_method_types_bank_debit) type online = { ip_address?: string, @@ -90,7 +100,10 @@ type customer_acceptance = { accepted_at: string, online: online, } -type mandate_data = {customer_acceptance: customer_acceptance} + +type mandate_data = { + customer_acceptance: customer_acceptance, +} type redirectType = { client_secret: string, return_url?: string, @@ -255,6 +268,32 @@ let flattenPaymentListArray = (plist, item) => { required_field: dict2->RequiredFieldsTypes.getRequiredFieldsFromDict, })->Js.Array.push(plist) }) + | "bank_debit" => + payment_method_types_array->Array.map(item2 => { + let dict2 = item2->getDictFromJson + BANK_DEBIT({ + payment_method: "bank_debit", + payment_method_type: dict2->getString("payment_method_type", ""), + payment_method_type_var: switch dict2->getString("payment_method_type", "") { + | "becs" => BECS + | _ => NONE + }, + payment_experience: dict2 + ->getArray("payment_experience") + ->Array.map(item3 => { + let dict3 = item3->getDictFromJson + { + payment_experience_type: dict3->getString("payment_experience_type", ""), + payment_experience_type_decode: switch dict3->getString("payment_experience_type", "") { + | "redirect_to_url" => REDIRECT_TO_URL + | _ => NONE + }, + eligible_connectors: dict3->getArray("eligible_connectors"), + } + }), + required_field: dict2->RequiredFieldsTypes.getRequiredFieldsFromDict, + })->Js.Array.push(plist) + }) | _ => [] }->ignore @@ -269,6 +308,7 @@ let getPaymentMethodType = pm => { | BANK_REDIRECT(payment_method_type) => payment_method_type.payment_method_type | CRYPTO(payment_method_type) => payment_method_type.payment_method_type | OPEN_BANKING(payment_method_type) => payment_method_type.payment_method_type + | BANK_DEBIT(payment_method_type) => payment_method_type.payment_method_type } } diff --git a/src/types/RequiredFieldsTypes.res b/src/types/RequiredFieldsTypes.res index 6819a53c..30d18b9e 100644 --- a/src/types/RequiredFieldsTypes.res +++ b/src/types/RequiredFieldsTypes.res @@ -24,6 +24,9 @@ type paymentMethodsFields = | AddressCountry(addressCountry) | BlikCode | Currency(array) + | AccountNumber + | RoutingNumber + | BSBNumber type requiredField = | StringField(string) @@ -60,6 +63,8 @@ let getPaymentMethodsFieldTypeFromString = str => { | "user_blik_code" => BlikCode | "user_billing_name" => BillingName | "user_shipping_name" => ShippingName + | "user_bank_account_number" => AccountNumber + | "user_bsb_number" => BSBNumber | var => UnKnownField(var) } } @@ -114,7 +119,10 @@ let getFieldType = dict => { let getPaymentMethodsFieldsOrder = paymentMethodField => { switch paymentMethodField { | FullName | ShippingName | BillingName => 1 + | AccountNumber => -1 + | RoutingNumber => 1 | Email => 2 + | BSBNumber => 3 | AddressLine1 => 4 | AddressLine2 => 5 | AddressCity => 6 @@ -123,6 +131,7 @@ let getPaymentMethodsFieldsOrder = paymentMethodField => { | StateAndCity => 9 | CountryAndPincode(_) => 10 | AddressPincode => 11 + | InfoElement => 99 | _ => 0 } @@ -319,6 +328,9 @@ let useGetPlaceholder = ( // | ShippingAddressPincode => localeObject.postalCodeLabel // | ShippingAddressState => localeObject.stateLabel | SpecialField(_) + | AccountNumber + | RoutingNumber + | BSBNumber | UnKnownField(_) | PhoneNumber | StateAndCity diff --git a/src/types/Types.res b/src/types/Types.res index 18d3348d..d2d96dd7 100644 --- a/src/types/Types.res +++ b/src/types/Types.res @@ -97,6 +97,12 @@ let defaultConfig = { header: "", fields: [], }, + { + name: "becs", + text: "BECS Debit", + header: "", + fields: [], + }, // { // name: "google_pay", // text: "Google Pay", diff --git a/src/utility/reusableCodeFromWeb/Validation.res b/src/utility/reusableCodeFromWeb/Validation.res index 8a815332..8e856e9e 100644 --- a/src/utility/reusableCodeFromWeb/Validation.res +++ b/src/utility/reusableCodeFromWeb/Validation.res @@ -272,6 +272,10 @@ let getExpiryValidity = cardExpiry => { valid } +let containsOnlyDigits = text => { + %re("/^[0-9]*$/")->Js.Re.test_(text) +} + // let max = (a, b) => { // a > b ? a : b // }