From f3f404733f7c13eb69589779ea2e2c8a6569259d Mon Sep 17 00:00:00 2001 From: ArushKapoorJuspay <121166031+ArushKapoorJuspay@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:23:14 +0530 Subject: [PATCH] feat: cvc nickname gpay (#224) Co-authored-by: Pritish Budhiraja <1805317@kiit.ac.in> --- src/Components/DynamicFields.res | 15 ++--- src/Components/NicknamePaymentInput.res | 19 +++++++ src/Components/SavedCardItem.res | 19 ++++--- src/Components/SavedMethods.res | 13 ++++- src/LocaleString.res | 18 ++++++ src/Payment.res | 2 + src/PaymentElement.res | 25 +++++++- src/Payments/CardPayment.res | 43 ++++++++++---- src/Types/PaymentType.res | 7 +++ src/Utilities/DynamicFieldsUtils.res | 21 +++++++ src/Utilities/PaymentBody.res | 76 +++++++++++++++---------- src/Utilities/PaymentUtils.res | 8 ++- 12 files changed, 200 insertions(+), 66 deletions(-) create mode 100644 src/Components/NicknamePaymentInput.res diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index 4310b59a..96a4af12 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -21,16 +21,11 @@ let make = ( let {billingAddress} = Recoil.useRecoilValueFromAtom(optionAtom) //<...>// - let paymentMethodTypes = React.useMemo3(() => { - PaymentMethodsRecord.getPaymentMethodTypeFromList( - ~list, - ~paymentMethod, - ~paymentMethodType=PaymentUtils.getPaymentMethodName( - ~paymentMethodType=paymentMethod, - ~paymentMethodName=paymentMethodType, - ), - )->Belt.Option.getWithDefault(PaymentMethodsRecord.defaultPaymentMethodType) - }, (list, paymentMethod, paymentMethodType)) + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~list, + ~paymentMethod, + ~paymentMethodType, + ) let requiredFieldsWithBillingDetails = React.useMemo3(() => { if paymentMethod === "card" { diff --git a/src/Components/NicknamePaymentInput.res b/src/Components/NicknamePaymentInput.res new file mode 100644 index 00000000..941a26df --- /dev/null +++ b/src/Components/NicknamePaymentInput.res @@ -0,0 +1,19 @@ +@react.component +let make = (~paymentType: CardThemeType.mode, ~value, ~setValue) => { + let {config, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) + + let onChange = ev => { + let val = ReactEvent.Form.target(ev)["value"] + setValue(_ => val) + } + + +} diff --git a/src/Components/SavedCardItem.res b/src/Components/SavedCardItem.res index 4bf71a45..59f9a243 100644 --- a/src/Components/SavedCardItem.res +++ b/src/Components/SavedCardItem.res @@ -48,6 +48,7 @@ let make = ( }, [isActive]) let isCard = paymentItem.paymentMethod === "card" + let isRenderCvv = isCard && paymentItem.requiresCvv let paymentMethodType = switch paymentItem.paymentMethodType { | Some(paymentMethodType) => paymentMethodType->Utils.snakeToTitleCase @@ -90,15 +91,15 @@ let make = ( border="1px solid currentColor" /> -
- brandIcon -
-
+
brandIcon
+
{isCard - ?
-
{React.string(`****`)}
-
{React.string({paymentItem.card.last4Digits})}
+ ?
+
{React.string(paymentItem.card.nickname)}
+
+
{React.string(`****`)}
+
{React.string(paymentItem.card.last4Digits)}
+
:
{React.string(paymentMethodType)}
} @@ -125,7 +126,7 @@ let make = (
- +
diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res index 3a5bac83..d85894e9 100644 --- a/src/Components/SavedMethods.res +++ b/src/Components/SavedMethods.res @@ -83,8 +83,16 @@ let make = ( ->Belt.Array.get(0) ->Belt.Option.getWithDefault(PaymentType.defaultCustomerMethods) let isCardPaymentMethod = customerMethod.paymentMethod === "card" + let isCardPaymentMethodValid = !customerMethod.requiresCvv || (complete && !empty) + let savedPaymentMethodBody = switch customerMethod.paymentMethod { - | "card" => PaymentBody.savedCardBody(~paymentToken=token, ~customerId, ~cvcNumber) + | "card" => + PaymentBody.savedCardBody( + ~paymentToken=token, + ~customerId, + ~cvcNumber, + ~requiresCvv=customerMethod.requiresCvv, + ) | _ => { let paymentMethodType = switch customerMethod.paymentMethodType { | Some("") @@ -99,8 +107,9 @@ let make = ( ) } } + if confirm.doSubmit { - if areRequiredFieldsValid && (!isCardPaymentMethod || (complete && !empty)) { + if areRequiredFieldsValid && (!isCardPaymentMethod || isCardPaymentMethodValid) { intent( ~bodyArr=savedPaymentMethodBody ->Js.Dict.fromArray diff --git a/src/LocaleString.res b/src/LocaleString.res index 1c89b232..26de81df 100644 --- a/src/LocaleString.res +++ b/src/LocaleString.res @@ -67,6 +67,8 @@ type localeStrings = { useExistingPaymentMethods: string, selectPaymentMethodLabel: string, savedPaymentMethodsLabel: string, + nicknameLabel: string, + nicknamePlaceholder: string, } let defaultLocale = { @@ -149,6 +151,8 @@ let defaultLocale = { useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", } type locale = {localeStrings: array} @@ -233,6 +237,8 @@ let localeStrings = [ useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", }, { locale: "he", @@ -314,6 +320,8 @@ let localeStrings = [ useExistingPaymentMethods: `השתמש באמצעי תשלום שמורים`, selectPaymentMethodLabel: `בחר שיטת תשלום`, savedPaymentMethodsLabel: `אמצעי תשלום שמורים`, + nicknameLabel: `כינוי לכרטיס`, + nicknamePlaceholder: `כינוי לכרטיס (אופציונלי)`, }, { locale: `fr`, @@ -395,6 +403,8 @@ let localeStrings = [ useExistingPaymentMethods: `Utiliser les modes de paiement enregistrés`, selectPaymentMethodLabel: `Sélectionnez le mode de paiement`, savedPaymentMethodsLabel: `Modes de paiement enregistrés`, + nicknameLabel: `Pseudonyme de la carte`, + nicknamePlaceholder: `Surnom de la carte (facultatif)`, }, { locale: "en-GB", @@ -476,6 +486,8 @@ let localeStrings = [ useExistingPaymentMethods: "Use saved payment methods", selectPaymentMethodLabel: "Select Payment Method", savedPaymentMethodsLabel: "Saved Payment Methods", + nicknameLabel: "Card Nickname", + nicknamePlaceholder: "Card Nickname (Optional)", }, { locale: "ar", @@ -557,6 +569,8 @@ let localeStrings = [ useExistingPaymentMethods: `استخدم طرق الدفع المحفوظة`, selectPaymentMethodLabel: `اختار طريقة الدفع`, savedPaymentMethodsLabel: `طرق الدفع المحفوظة`, + nicknameLabel: `الاسم علي الكارت`, + nicknamePlaceholder: `اسم البطاقة (اختياري)`, }, { locale: "ja", @@ -638,6 +652,8 @@ let localeStrings = [ useExistingPaymentMethods: `保存した支払い方法を使用する`, selectPaymentMethodLabel: `支払い方法を選択してください`, savedPaymentMethodsLabel: `保存された支払い方法`, + nicknameLabel: `カードのニックネーム`, + nicknamePlaceholder: `カードニックネーム(任意)`, }, { locale: "de", @@ -719,5 +735,7 @@ let localeStrings = [ useExistingPaymentMethods: `Gespeicherte Zahlungsarten nutzen`, selectPaymentMethodLabel: `Wählen Sie die Zahlungsmethode`, savedPaymentMethodsLabel: `Gespeicherte Zahlungsarten`, + nicknameLabel: `Spitzname der Karte`, + nicknamePlaceholder: `Kartenname (optional)`, }, ] diff --git a/src/Payment.res b/src/Payment.res index 8ec944f5..230f0fc5 100644 --- a/src/Payment.res +++ b/src/Payment.res @@ -252,6 +252,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~cardHolderName="", ~cvcNumber, ~cardBrand=cardNetwork, + (), ) | CardNumberElement => let (month, year) = getExpiryDates(getCardElementValue(iframeId, "card-expiry")) @@ -263,6 +264,7 @@ let make = (~paymentMode, ~integrateError, ~logger) => { ~cardHolderName="", ~cvcNumber=localCvcNumber, ~cardBrand=cardNetwork, + (), ) | _ => [] } diff --git a/src/PaymentElement.res b/src/PaymentElement.res index 9c38dbec..11547b33 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -108,7 +108,19 @@ let make = ( None }, [savedMethods]) - let (walletList, paymentOptionsList, actualList) = React.useMemo4(() => { + let areAllGooglePayRequiredFieldsPrefilled = DynamicFieldsUtils.useAreAllRequiredFieldsPrefilled( + ~list, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", + ) + + let areAllApplePayRequiredFieldsPrefilled = DynamicFieldsUtils.useAreAllRequiredFieldsPrefilled( + ~list, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + ) + + let (walletList, paymentOptionsList, actualList) = React.useMemo6(() => { switch methodslist { | Loaded(paymentlist) => let paymentOrder = @@ -119,6 +131,8 @@ let make = ( ~order=paymentOrder, ~showApplePay=isApplePayReady, ~showGooglePay=isGooglePayReady, + ~areAllGooglePayRequiredFieldsPrefilled, + ~areAllApplePayRequiredFieldsPrefilled, ) ( wallets->Utils.removeDuplicate, @@ -131,7 +145,14 @@ let make = ( : ([], [], []) | _ => ([], [], []) } - }, (methodslist, paymentMethodOrder, isApplePayReady, isGooglePayReady)) + }, ( + methodslist, + paymentMethodOrder, + isApplePayReady, + isGooglePayReady, + areAllGooglePayRequiredFieldsPrefilled, + areAllApplePayRequiredFieldsPrefilled, + )) React.useEffect4(() => { switch methodslist { diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index ff551570..2656b500 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -16,6 +16,9 @@ let make = ( let {config, themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) + + let (nickname, setNickname) = React.useState(_ => "") + let ( isCardValid, setIsCardValid, @@ -100,7 +103,15 @@ let make = ( ~isCvcValidValue, ) - let submitCallback = React.useCallback5((ev: Window.event) => { + let isCustomerAcceptanceRequired = React.useMemo1(() => { + if displaySavedPaymentMethodsCheckbox { + isSaveCardsChecked || list.payment_type === SETUP_MANDATE + } else { + !(isGuestCustomer || list.payment_type === NORMAL) + } + }, [isSaveCardsChecked]) + + let submitCallback = React.useCallback6((ev: Window.event) => { let json = ev.data->Js.Json.parseExn let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper let (month, year) = CardUtils.getExpiryDates(cardExpiry) @@ -120,18 +131,14 @@ let make = ( ~cardHolderName="", ~cvcNumber, ~cardBrand=cardNetwork, + ~nickname, + (), ) let banContactBody = PaymentBody.bancontactBody() - let cardBody = if displaySavedPaymentMethodsCheckbox { - if isSaveCardsChecked || list.payment_type === SETUP_MANDATE { - defaultCardBody->Js.Array2.concat(onSessionBody) - } else { - defaultCardBody - } - } else if isGuestCustomer || list.payment_type === NORMAL { - defaultCardBody - } else { + let cardBody = if isCustomerAcceptanceRequired { defaultCardBody->Js.Array2.concat(onSessionBody) + } else { + defaultCardBody } if confirm.doSubmit { let validFormat = @@ -171,7 +178,14 @@ let make = ( } } } - }, (areRequiredFieldsValid, requiredFieldsBody, empty, complete, isSaveCardsChecked)) + }, ( + areRequiredFieldsValid, + requiredFieldsBody, + empty, + complete, + isCustomerAcceptanceRequired, + nickname, + )) submitPaymentData(submitCallback) let paymentMethod = isBancontact ? "bank_redirect" : "card" @@ -182,6 +196,8 @@ let make = ( options.displaySavedPaymentMethodsCheckbox && !isBancontact + let nicknameFieldClassName = conditionsForShowingSaveCardCheckbox ? "pt-2" : "pt-5" +
+ +
+ +
+
Array.length > 0 && diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 43fa329c..86246d00 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -114,6 +114,7 @@ type customerCard = { expiryYear: string, cardToken: string, cardHolderName: option, + nickname: string, } type customerMethods = { paymentToken: string, @@ -123,6 +124,7 @@ type customerMethods = { card: customerCard, paymentMethodType: option, defaultPaymentMethodSet: bool, + requiresCvv: bool, } type savedCardsLoadState = LoadingSavedCards | LoadedSavedCards(array, bool) | NoResult(bool) @@ -164,6 +166,7 @@ let defaultCardDetails = { expiryYear: "", cardToken: "", cardHolderName: None, + nickname: "", } let defaultCustomerMethods = { paymentToken: "", @@ -173,6 +176,7 @@ let defaultCustomerMethods = { card: defaultCardDetails, paymentMethodType: None, defaultPaymentMethodSet: false, + requiresCvv: true, } let defaultLayout = { defaultCollapsed: false, @@ -780,6 +784,7 @@ let getCardDetails = (dict, str) => { expiryYear: getString(json, "expiry_year", ""), cardToken: getString(json, "card_token", ""), cardHolderName: Some(getString(json, "card_holder_name", "")), + nickname: getString(json, "nick_name", ""), } }) ->Belt.Option.getWithDefault(defaultCardDetails) @@ -820,6 +825,7 @@ let createCustomerObjArr = dict => { card: getCardDetails(dict, "card"), paymentMethodType: getPaymentMethodType(dict), defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), + requiresCvv: getBool(dict, "requires_cvv", true), } }) LoadedSavedCards(customerPaymentMethods, isGuestCustomer) @@ -842,6 +848,7 @@ let getCustomerMethods = (dict, str) => { card: getCardDetails(json, "card"), paymentMethodType: getPaymentMethodType(dict), defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), + requiresCvv: getBool(dict, "requires_cvv", true), } }) LoadedSavedCards(customerPaymentMethods, false) diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index d6a680df..fa1e8f47 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -695,3 +695,24 @@ let useSubmitCallback = () => { } }, (line1, line2, state, city, postalCode)) } + +let usePaymentMethodTypeFromList = (~list, ~paymentMethod, ~paymentMethodType) => { + React.useMemo3(() => { + PaymentMethodsRecord.getPaymentMethodTypeFromList( + ~list, + ~paymentMethod, + ~paymentMethodType=PaymentUtils.getPaymentMethodName( + ~paymentMethodType=paymentMethod, + ~paymentMethodName=paymentMethodType, + ), + )->Belt.Option.getWithDefault(PaymentMethodsRecord.defaultPaymentMethodType) + }, (list, paymentMethod, paymentMethodType)) +} + +let useAreAllRequiredFieldsPrefilled = (~list, ~paymentMethod, ~paymentMethodType) => { + let paymentMethodTypes = usePaymentMethodTypeFromList(~list, ~paymentMethod, ~paymentMethodType) + + paymentMethodTypes.required_fields->Js.Array2.reduce((acc, requiredField) => { + acc && requiredField.value != "" + }, true) +} diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index 21787104..a3904f0f 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -1,29 +1,38 @@ @val @scope("window") external btoa: string => string = "btoa" -let cardPaymentBody = (~cardNumber, ~month, ~year, ~cardHolderName, ~cvcNumber, ~cardBrand) => [ - ("payment_method", "card"->Js.Json.string), - ( - "payment_method_data", - [ - ( - "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), - ("card_cvc", cvcNumber->Js.Json.string), - ("card_issuer", ""->Js.Json.string), - ] - ->Js.Array2.concat(cardBrand) - ->Js.Dict.fromArray - ->Js.Json.object_, - ), - ] - ->Js.Dict.fromArray - ->Js.Json.object_, - ), -] +let cardPaymentBody = ( + ~cardNumber, + ~month, + ~year, + ~cardHolderName, + ~cvcNumber, + ~cardBrand, + ~nickname="", + (), +) => { + let cardBody = [ + ("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), + ("card_cvc", cvcNumber->Js.Json.string), + ("card_issuer", ""->Js.Json.string), + ] + + if nickname != "" { + cardBody->Js.Array2.push(("nick_name", nickname->Js.Json.string))->ignore + } + + [ + ("payment_method", "card"->Js.Json.string), + ( + "payment_method_data", + [("card", cardBody->Js.Array2.concat(cardBrand)->Js.Dict.fromArray->Js.Json.object_)] + ->Js.Dict.fromArray + ->Js.Json.object_, + ), + ] +} let bancontactBody = () => [ ("payment_method", "bank_redirect"->Js.Json.string), @@ -55,12 +64,19 @@ let boletoBody = (~socialSecurityNumber) => [ ), ] -let savedCardBody = (~paymentToken, ~customerId, ~cvcNumber) => [ - ("payment_method", "card"->Js.Json.string), - ("payment_token", paymentToken->Js.Json.string), - ("customer_id", customerId->Js.Json.string), - ("card_cvc", cvcNumber->Js.Json.string), -] +let savedCardBody = (~paymentToken, ~customerId, ~cvcNumber, ~requiresCvv) => { + let savedCardBody = [ + ("payment_method", "card"->Js.Json.string), + ("payment_token", paymentToken->Js.Json.string), + ("customer_id", customerId->Js.Json.string), + ] + + if requiresCvv { + savedCardBody->Js.Array2.push(("card_cvc", cvcNumber->Js.Json.string))->ignore + } + + savedCardBody +} let customerAcceptanceBody = [ diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index a26b8efc..3ceb7a72 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -3,6 +3,8 @@ let paymentListLookupNew = ( ~order, ~showApplePay, ~showGooglePay, + ~areAllGooglePayRequiredFieldsPrefilled, + ~areAllApplePayRequiredFieldsPrefilled, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] @@ -26,14 +28,16 @@ let paymentListLookupNew = ( let applePayFields = pmList->Js.Array2.find(item => item.paymentMethodName === "apple_pay") switch googlePayFields { | Some(val) => - if val.fields->Js.Array2.length > 0 && showGooglePay { + if ( + val.fields->Js.Array2.length > 0 && showGooglePay && !areAllGooglePayRequiredFieldsPrefilled + ) { walletToBeDisplayedInTabs->Js.Array2.push("google_pay")->ignore } | None => () } switch applePayFields { | Some(val) => - if val.fields->Js.Array2.length > 0 && showApplePay { + if val.fields->Js.Array2.length > 0 && showApplePay && !areAllApplePayRequiredFieldsPrefilled { walletToBeDisplayedInTabs->Js.Array2.push("apple_pay")->ignore } | None => ()