From 31403f89ac4d9ebfdac04034fe845a07c8ecfef2 Mon Sep 17 00:00:00 2001 From: ArushKapoorJuspay <121166031+ArushKapoorJuspay@users.noreply.github.com> Date: Tue, 7 May 2024 15:45:27 +0530 Subject: [PATCH] Fix: revert card details (#354) --- src/Components/DynamicFields.res | 29 +--- src/Payment.res | 11 +- src/Payments/CardPayment.res | 215 ++++++++++++++++++++++++-- src/Payments/PaymentMethodsRecord.res | 60 +++---- src/Utilities/DynamicFieldsUtils.res | 92 +---------- src/Utilities/PaymentBody.res | 33 +--- 6 files changed, 251 insertions(+), 189 deletions(-) diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 52135e3ab..f1344d92b 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -42,7 +42,6 @@ let make = ( paymentMethodTypes.required_fields ->Array.concat(creditRequiredFields) ->DynamicFieldsUtils.removeRequiredFieldsDuplicates - ->DynamicFieldsUtils.filterCardDetailsIfSavedCardsFlow(isSavedCardFlow) } else if ( PaymentMethodsRecord.dynamicFieldsEnabledPaymentMethods->Array.includes(paymentMethodType) ) { @@ -50,7 +49,7 @@ let make = ( } else { [] } - }, (paymentMethod, paymentMethodTypes.required_fields, paymentMethodType, isSavedCardFlow)) + }, (paymentMethod, paymentMethodTypes.required_fields, paymentMethodType)) let requiredFields = React.useMemo(() => { requiredFieldsWithBillingDetails->DynamicFieldsUtils.removeBillingDetailsIfUseBillingAddress( @@ -71,13 +70,7 @@ let make = ( ~isAllStoredCardsHaveName, (), ) - ->DynamicFieldsUtils.updateDynamicFields( - billingAddress, - ~paymentMethodListValue, - ~paymentMethod, - ~isSavedCardFlow, - (), - ) + ->DynamicFieldsUtils.updateDynamicFields(billingAddress, ()) ->Belt.SortArray.stableSortBy(PaymentMethodsRecord.sortPaymentMethodFields) //<...>// }, (requiredFields, isAllStoredCardsHaveName, isSavedCardFlow)) @@ -167,7 +160,7 @@ let make = ( cardRef, icon, cardError, - setCardError, + _, maxCardLength, ) = switch cardProps { | Some(cardProps) => cardProps @@ -183,7 +176,7 @@ let make = ( expiryRef, _, expiryError, - setExpiryError, + _, ) = switch expiryProps { | Some(expiryProps) => expiryProps | None => defaultExpiryProps @@ -199,7 +192,7 @@ let make = ( cvcRef, _, cvcError, - setCvcError, + _, ) = switch cvcProps { | Some(cvcProps) => cvcProps | None => defaultCvcProps @@ -294,17 +287,9 @@ let make = ( ~isSavedCardFlow, ~isAllStoredCardsHaveName, ~setRequiredFieldsBody, - ~paymentMethodListValue, ) - let submitCallback = DynamicFieldsUtils.useSubmitCallback( - ~cardNumber, - ~setCardError, - ~cardExpiry, - ~setExpiryError, - ~cvcNumber, - ~setCvcError, - ) + let submitCallback = DynamicFieldsUtils.useSubmitCallback() useSubmitPaymentData(submitCallback) let bottomElement = @@ -352,7 +337,7 @@ let make = ( key={`outside-billing-${index->Int.toString}`} className="flex flex-col w-full place-content-between" style={ReactDOMStyle.make( - ~marginTop=index !== 0 ? themeObj.spacingGridColumn : "", + ~marginTop=index !== 0 || paymentMethod === "card" ? themeObj.spacingGridColumn : "", ~gridColumnGap=themeObj.spacingGridRow, (), )}> diff --git a/src/Payment.res b/src/Payment.res index 94b14daee..8d15eec44 100644 --- a/src/Payment.res +++ b/src/Payment.res @@ -228,6 +228,13 @@ let make = (~paymentMode, ~integrateError, ~logger) => { checkCardExpiry(getCardElementValue(iframeId, "card-expiry")) | _ => true } + let cardNetwork = { + if cardBrand != "" { + [("card_network", cardNumber->CardUtils.getCardBrand->JSON.Encode.string)] + } else { + [] + } + } if validFormat { let body = switch paymentMode->getPaymentMode { | Card => @@ -239,7 +246,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~year, ~cardHolderName="", ~cvcNumber, - ~cardBrand, + ~cardBrand=cardNetwork, (), ) | CardNumberElement => @@ -251,7 +258,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~year, ~cardHolderName="", ~cvcNumber=localCvcNumber, - ~cardBrand, + ~cardBrand=cardNetwork, (), ) | _ => [] diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 01d79228e..85725a1ee 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -14,22 +14,59 @@ let make = ( open Utils open UtilityHooks - let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let {config, themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + let {innerLayout} = config.appearance let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) let (nickname, setNickname) = React.useState(_ => "") - let (_, _, cardNumber, _, _, _, _, _, _, _) = cardProps + let ( + isCardValid, + setIsCardValid, + cardNumber, + changeCardNumber, + handleCardBlur, + cardRef, + icon, + cardError, + setCardError, + maxCardLength, + ) = cardProps let cardBrand = React.useMemo(() => { cardNumber->CardUtils.getCardBrand }, [cardNumber]) + let ( + isExpiryValid, + setIsExpiryValid, + cardExpiry, + changeCardExpiry, + handleExpiryBlur, + expiryRef, + _, + expiryError, + setExpiryError, + ) = expiryProps + + let ( + isCVCValid, + setIsCVCValid, + cvcNumber, + _, + changeCVCNumber, + handleCVCBlur, + cvcRef, + _, + cvcError, + setCvcError, + ) = cvcProps let {displaySavedPaymentMethodsCheckbox} = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Card) let showFields = Recoil.useRecoilValueFromAtom(RecoilAtoms.showCardFieldsAtom) + let setComplete = Recoil.useSetRecoilState(RecoilAtoms.fieldsComplete) let (isSaveCardsChecked, setIsSaveCardsChecked) = React.useState(_ => false) let setUserError = message => { @@ -39,15 +76,23 @@ let make = ( let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make()) let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid) - let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty) - useHandlePostMessages( - ~complete=areRequiredFieldsValid, - ~empty=areRequiredFieldsEmpty, - ~paymentType="card", - ) + let complete = isAllValid(isCardValid, isCVCValid, isExpiryValid, true, "payment") + let empty = cardNumber == "" || cardExpiry == "" || cvcNumber == "" + React.useEffect(() => { + setComplete(_ => complete) + None + }, [complete]) + + useHandlePostMessages(~complete=complete && areRequiredFieldsValid, ~empty, ~paymentType="card") let isGuestCustomer = useIsGuestCustomer() + let isCvcValidValue = CardUtils.getBoolOptionVal(isCVCValid) + let (cardEmpty, cardComplete, cardInvalid) = CardUtils.useCardDetails( + ~cvcNumber, + ~isCVCValid, + ~isCvcValidValue, + ) let isCustomerAcceptanceRequired = useIsCustomerAcceptanceRequired( ~displaySavedPaymentMethodsCheckbox, @@ -58,9 +103,26 @@ let make = ( let submitCallback = React.useCallback((ev: Window.event) => { let json = ev.data->JSON.parseExn let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper + let (month, year) = CardUtils.getExpiryDates(cardExpiry) let onSessionBody = [("customer_acceptance", PaymentBody.customerAcceptanceBody)] - let defaultCardBody = PaymentBody.dynamicCardPaymentBody(~cardBrand, ~nickname, ()) + let cardNetwork = { + if cardBrand != "" { + [("card_network", cardBrand->JSON.Encode.string)] + } else { + [] + } + } + let defaultCardBody = PaymentBody.cardPaymentBody( + ~cardNumber, + ~month, + ~year, + ~cardHolderName="", + ~cvcNumber, + ~cardBrand=cardNetwork, + ~nickname, + (), + ) let banContactBody = PaymentBody.bancontactBody() let cardBody = if isCustomerAcceptanceRequired { defaultCardBody->Array.concat(onSessionBody) @@ -68,7 +130,11 @@ let make = ( defaultCardBody } if confirm.doSubmit { - let validFormat = areRequiredFieldsValid + let validFormat = + (isBancontact || + (isCVCValid->Option.getOr(false) && + isCardValid->Option.getOr(false) && + isExpiryValid->Option.getOr(false))) && areRequiredFieldsValid if validFormat && (showFields || isBancontact) { intent( ~bodyArr={ @@ -83,11 +149,32 @@ let make = ( ~handleUserError=false, (), ) - } else if !validFormat { - setUserError(localeString.enterValidDetailsText) + } else { + if cardNumber === "" { + setCardError(_ => localeString.cardNumberEmptyText) + setUserError(localeString.enterFieldsText) + } + if cardExpiry === "" { + setExpiryError(_ => localeString.cardExpiryDateEmptyText) + setUserError(localeString.enterFieldsText) + } + if !isBancontact && cvcNumber === "" { + setCvcError(_ => localeString.cvcNumberEmptyText) + setUserError(localeString.enterFieldsText) + } + if !validFormat { + setUserError(localeString.enterValidDetailsText) + } } } - }, (areRequiredFieldsValid, requiredFieldsBody, isCustomerAcceptanceRequired, nickname)) + }, ( + areRequiredFieldsValid, + requiredFieldsBody, + empty, + complete, + isCustomerAcceptanceRequired, + nickname, + )) useSubmitPaymentData(submitCallback) let paymentMethod = isBancontact ? "bank_redirect" : "card" @@ -101,12 +188,114 @@ let make = ( let nicknameFieldClassName = conditionsForShowingSaveCardCheckbox ? "pt-2" : "pt-5" + let compressedLayoutStyleForCvcError = + innerLayout === Compressed && cvcError->String.length > 0 ? "!border-l-0" : "" +
+ +
+ {React.string(localeString.cardHeader)} +
+
+ + String.length > 0 + ? "border-b-0" + : ""} + /> +
+
+ +
+
+ +
+
+ String.length > 0 || + cvcError->String.length > 0 || + expiryError->String.length > 0}> +
+ {React.string("Invalid input")} +
+
+
{ - 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 - | "user_card_number" => CardNumber - | "user_card_expiry_month" => CardExpiryMonth - | "user_card_expiry_year" => CardExpiryYear - | "user_card_cvc" => CardCvc +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 } } @@ -558,7 +558,7 @@ let getPaymentMethodsFieldTypeFromDict = dict => { } } -let getFieldType = dict => { +let getFieldType = (dict, isBancontact) => { let fieldClass = dict ->Dict.get("field_type") @@ -570,7 +570,7 @@ let getFieldType = dict => { None | Number(_val) => None | Array(_arr) => None - | String(val) => val->getPaymentMethodsFieldTypeFromString + | String(val) => val->getPaymentMethodsFieldTypeFromString(isBancontact) | Object(dict) => dict->getPaymentMethodsFieldTypeFromDict } } @@ -614,16 +614,6 @@ let getIsAnyBillingDetailEmpty = (requiredFields: array) => { }) } -let filterCardDetailsFromSavedPaymentMethod = fieldType => { - switch fieldType { - | CardNumber - | CardExpiryMonth - | CardExpiryYear - | CardCvc => true - | _ => false - } -} - let getPaymentMethodFields = ( paymentMethod, requiredFields: array, @@ -872,7 +862,7 @@ let getAchConnectors = (dict, str) => { ->getStrArray("elligible_connectors") } -let getDynamicFieldsFromJsonDict = dict => { +let getDynamicFieldsFromJsonDict = (dict, isBancontact) => { let requiredFields = Utils.getJsonFromDict(dict, "required_fields", JSON.Encode.null) ->Utils.getDictFromJson @@ -883,7 +873,7 @@ let getDynamicFieldsFromJsonDict = dict => { { required_field: requiredFieldsDict->Utils.getString("required_field", ""), display_name: requiredFieldsDict->Utils.getString("display_name", ""), - field_type: requiredFieldsDict->getFieldType, + field_type: requiredFieldsDict->getFieldType(isBancontact), value: requiredFieldsDict->Utils.getString("value", ""), } }) @@ -904,7 +894,9 @@ let getPaymentMethodTypes = (dict, str) => { bank_names: getBankNames(jsonDict, "bank_names"), bank_debits_connectors: getAchConnectors(jsonDict, "bank_debit"), bank_transfers_connectors: getAchConnectors(jsonDict, "bank_transfer"), - required_fields: jsonDict->getDynamicFieldsFromJsonDict, + required_fields: jsonDict->getDynamicFieldsFromJsonDict( + paymentMethodType === "bancontact_card", + ), surcharge_details: jsonDict->getSurchargeDetails, } }) @@ -1067,5 +1059,3 @@ let paymentMethodFieldToStrMapper = (field: paymentMethodsFields) => { | CardExpiryAndCvc => "CardExpiryAndCvc" } } - -let cardDetailsFields = [CardNumber, CardExpiryMonth, CardExpiryYear, CardCvc] diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index dc0e77bb5..56421f1d9 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -52,16 +52,6 @@ let getBillingAddressPathFromFieldType = (fieldType: PaymentMethodsRecord.paymen } } -let getCardAddressPathFromFieldType = (fieldType: PaymentMethodsRecord.paymentMethodsFields) => { - switch fieldType { - | CardNumber => "payment_method_data.card.card_number" - | CardExpiryMonth => "payment_method_data.card.card_exp_month" - | CardExpiryYear => "payment_method_data.card.card_exp_year" - | CardCvc => "payment_method_data.card.card_cvc" - | _ => "" - } -} - let removeBillingDetailsIfUseBillingAddress = ( requiredFields: array, billingAddress: PaymentType.billingAddress, @@ -154,6 +144,7 @@ let useRequiredFieldsEmptyAndValid = ( | StateAndCity => state.value !== "" && city.value !== "" | CountryAndPincode(countryArr) => (country !== "" || countryArr->Array.length === 0) && postalCode.value !== "" + | AddressCity => city.value !== "" | AddressPincode => postalCode.value !== "" | AddressState => state.value !== "" @@ -386,7 +377,6 @@ let useRequiredFieldsBody = ( ~isSavedCardFlow, ~isAllStoredCardsHaveName, ~setRequiredFieldsBody, - ~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList, ) => { let email = Recoil.useRecoilValueFromAtom(userEmailAddress) let fullName = Recoil.useRecoilValueFromAtom(userFullName) @@ -474,23 +464,6 @@ let useRequiredFieldsBody = ( } } - let addCardDetailsBodyIfFallback = requiredFieldsBody => { - if ( - (paymentMethodType === "debit" || paymentMethodType === "credit") && - paymentMethodListValue.payment_methods->Array.length === 0 && - !isSavedCardFlow - ) { - PaymentMethodsRecord.cardDetailsFields->Array.reduce(requiredFieldsBody, (acc, item) => { - let value = item->getFieldValueFromFieldType - let path = item->getCardAddressPathFromFieldType - acc->Dict.set(path, value->JSON.Encode.string) - acc - }) - } else { - requiredFieldsBody - } - } - React.useEffect(() => { let requiredFieldsBody = requiredFields @@ -519,7 +492,6 @@ let useRequiredFieldsBody = ( acc }) ->addBillingDetailsIfUseBillingAddress - ->addCardDetailsBodyIfFallback setRequiredFieldsBody(_ => requiredFieldsBody) None @@ -646,54 +618,26 @@ let combineCardExpiryAndCvc = arr => { } } -let addCardDetailsIfFallback = ( - fieldsArr, - ~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList, - ~paymentMethod, - ~isSavedCardFlow, -) => { - if ( - paymentMethod === "card" && - paymentMethodListValue.payment_methods->Array.length === 0 && - !isSavedCardFlow - ) { - fieldsArr->Array.concat(PaymentMethodsRecord.cardDetailsFields) - } else { - fieldsArr - } -} - let updateDynamicFields = ( arr: array, billingAddress, - ~paymentMethodListValue: PaymentMethodsRecord.paymentMethodList, - ~paymentMethod, - ~isSavedCardFlow, (), ) => { arr ->Utils.removeDuplicate ->Array.filter(item => item !== None) ->addBillingAddressIfUseBillingAddress(billingAddress) - ->addCardDetailsIfFallback(~paymentMethodListValue, ~paymentMethod, ~isSavedCardFlow) ->combineStateAndCity ->combineCountryAndPostal ->combineCardExpiryMonthAndYear ->combineCardExpiryAndCvc } -let useSubmitCallback = ( - ~cardNumber, - ~setCardError, - ~cardExpiry, - ~setExpiryError, - ~cvcNumber, - ~setCvcError, -) => { - let logger = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) - let (line1, setLine1) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressline1, "line1", logger) - let (line2, setLine2) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressline2, "line2", logger) - let (state, setState) = Recoil.useLoggedRecoilState(RecoilAtoms.userAddressState, "state", logger) +let useSubmitCallback = () => { + let logger = Recoil.useRecoilValueFromAtom(loggerAtom) + let (line1, setLine1) = Recoil.useLoggedRecoilState(userAddressline1, "line1", logger) + let (line2, setLine2) = Recoil.useLoggedRecoilState(userAddressline2, "line2", logger) + let (state, setState) = Recoil.useLoggedRecoilState(userAddressState, "state", logger) let (postalCode, setPostalCode) = Recoil.useLoggedRecoilState( userAddressPincode, "postal_code", @@ -738,17 +682,8 @@ let useSubmitCallback = ( errorString: localeString.cityEmptyText, }) } - if cardNumber === "" { - setCardError(_ => localeString.cardNumberEmptyText) - } - if cardExpiry === "" { - setExpiryError(_ => localeString.cardExpiryDateEmptyText) - } - if cvcNumber === "" { - setCvcError(_ => localeString.cvcNumberEmptyText) - } } - }, (line1, line2, state, city, postalCode, cardNumber, cardExpiry, cvcNumber)) + }, (line1, line2, state, city, postalCode)) } let usePaymentMethodTypeFromList = ( @@ -803,16 +738,3 @@ let removeRequiredFieldsDuplicates = ( requiredFields } - -let filterCardDetailsIfSavedCardsFlow = ( - requiredFields: array, - isSavedCardFlow, -) => { - if isSavedCardFlow { - requiredFields->Array.filter(requiredField => { - requiredField.field_type->PaymentMethodsRecord.filterCardDetailsFromSavedPaymentMethod->not - }) - } else { - requiredFields - } -} diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index e6f75bcea..d3b7ab42d 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -58,48 +58,17 @@ let cardPaymentBody = ( cardBody->Array.push(("nick_name", nickname->JSON.Encode.string))->ignore } - if cardBrand != "" { - cardBody->Array.push(("card_network", cardBrand->JSON.Encode.string))->ignore - } - [ ("payment_method", "card"->JSON.Encode.string), ( "payment_method_data", - [("card", cardBody->Dict.fromArray->JSON.Encode.object)] + [("card", cardBody->Array.concat(cardBrand)->Dict.fromArray->JSON.Encode.object)] ->Dict.fromArray ->JSON.Encode.object, ), ] } -let dynamicCardPaymentBody = (~cardBrand, ~nickname="", ()) => { - let cardBody = [] - - if nickname != "" { - cardBody->Array.push(("nick_name", nickname->JSON.Encode.string))->ignore - } - - if cardBrand != "" { - cardBody->Array.push(("card_network", cardBrand->JSON.Encode.string))->ignore - } - - let paymentMethodData = if cardBody->Array.length > 0 { - [ - ( - "payment_method_data", - [("card", cardBody->Dict.fromArray->JSON.Encode.object)] - ->Dict.fromArray - ->JSON.Encode.object, - ), - ] - } else { - [] - } - - [("payment_method", "card"->JSON.Encode.string)]->Array.concat(paymentMethodData) -} - let bancontactBody = () => [ ("payment_method", "bank_redirect"->JSON.Encode.string), ("payment_method_type", "bancontact_card"->JSON.Encode.string),