From 155e2f15b1eee690fc7679189c860fe4e0f21bf0 Mon Sep 17 00:00:00 2001 From: Arush Date: Tue, 12 Mar 2024 01:36:00 +0530 Subject: [PATCH] fix: added Wallets to Saved Payment Methods fix #197 --- public/icons/orca.svg | 55 +++++++++++++++ src/CardUtils.res | 38 +++++------ src/Components/DynamicFields.res | 2 +- src/Components/SavedCardItem.res | 113 +++++++++++++++++++++---------- src/Components/SavedMethods.res | 81 +++++++++++++++------- src/LocaleString.res | 36 ++++++++++ src/PaymentElement.res | 30 +++++--- src/Payments/CardPayment.res | 10 +-- src/Types/PaymentType.res | 24 +++++-- src/Utilities/PaymentBody.res | 7 ++ src/Utilities/Utils.res | 2 + 11 files changed, 296 insertions(+), 102 deletions(-) diff --git a/public/icons/orca.svg b/public/icons/orca.svg index d732594ce..1d3e33e7f 100644 --- a/public/icons/orca.svg +++ b/public/icons/orca.svg @@ -2166,4 +2166,59 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL fill="#434343" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CardUtils.res b/src/CardUtils.res index 9784d253b..4419c4cc3 100644 --- a/src/CardUtils.res +++ b/src/CardUtils.res @@ -316,28 +316,28 @@ let calculateLuhn = value => { let getCardBrandIcon = (cardType, paymentType) => { open CardThemeType switch cardType { - | VISA => - | MASTERCARD => - | AMEX => - | MAESTRO => - | DINERSCLUB => - | DISCOVER => - | BAJAJ => - | SODEXO => - | RUPAY => - | JCB => - | CARTESBANCAIRES => - | UNIONPAY => - | INTERAC => + | VISA => + | MASTERCARD => + | AMEX => + | MAESTRO => + | DINERSCLUB => + | DISCOVER => + | BAJAJ => + | SODEXO => + | RUPAY => + | JCB => + | CARTESBANCAIRES => + | UNIONPAY => + | INTERAC => | NOTFOUND => switch paymentType { - | Payment => + | Payment => | Card | CardNumberElement | CardExpiryElement | CardCVCElement | NONE => - + } } } @@ -640,15 +640,15 @@ let getCvcDetailsFromCvcProps = cvcProps => { let setRightIconForCvc = (~cardEmpty, ~cardInvalid, ~color, ~cardComplete) => { if cardEmpty { - + } else if cardInvalid {
- +
} else if cardComplete { - + } else { - + } } diff --git a/src/Components/DynamicFields.res b/src/Components/DynamicFields.res index a9aed4c94..4310b59a5 100644 --- a/src/Components/DynamicFields.res +++ b/src/Components/DynamicFields.res @@ -433,7 +433,7 @@ let make = ( ->React.array}
{ let {themeObj, config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) let (cardBrand, setCardBrand) = Recoil.useRecoilState(RecoilAtoms.cardBrand) @@ -45,6 +47,13 @@ let make = ( None }, [isActive]) + let isCard = paymentItem.paymentMethod === "card" + + let paymentMethodType = switch paymentItem.paymentMethodType { + | Some(paymentMethodType) => paymentMethodType->Utils.snakeToTitleCase + | None => "debit" + } +
-
brandIcon
-
-
{React.string(`****`)}
-
{React.string({paymentItem.card.last4Digits})}
+
+ brandIcon
-
-
-
- {React.string( - `${paymentItem.card.expiryMonth} / ${paymentItem.card.expiryYear->CardUtils.formatExpiryToTwoDigit}`, - )} +
+ {isCard + ?
+
{React.string(`****`)}
+
{React.string({paymentItem.card.last4Digits})}
+
+ :
{React.string(paymentMethodType)}
} + + +
+ +
+
+ {React.string( + `${paymentItem.card.expiryMonth} / ${paymentItem.card.expiryYear->CardUtils.formatExpiryToTwoDigit}`, + )} +
+
+
-
-
{React.string("CVC: ")}
-
- + +
+
{React.string("CVC: ")}
+
+ +
-
+ + CardUtils.cardType} />
diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res index 36784020b..3a5bac83a 100644 --- a/src/Components/SavedMethods.res +++ b/src/Components/SavedMethods.res @@ -21,16 +21,30 @@ let make = ( let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Card) let (token, _) = paymentToken let savedCardlength = savedMethods->Js.Array2.length + + let getWalletBrandIcon = (obj: PaymentType.customerMethods) => { + switch obj.paymentMethodType { + | Some("apple_pay") => + | Some("google_pay") => + | Some("paypal") => + | _ => + } + } + let bottomElement = { savedMethods ->Js.Array2.mapi((obj, i) => { - let brandIcon = getCardBrandIcon( - switch obj.card.scheme { - | Some(ele) => ele - | None => "" - }->cardType, - ""->CardTheme.getPaymentMode, - ) + let brandIcon = switch obj.paymentMethod { + | "wallet" => getWalletBrandIcon(obj) + | _ => + getCardBrandIcon( + switch obj.card.scheme { + | Some(ele) => ele + | None => "" + }->cardType, + ""->CardTheme.getPaymentMode, + ) + } let isActive = token == obj.paymentToken Belt.Int.toString} @@ -43,6 +57,8 @@ let make = ( cvcProps paymentType list + savedMethods + setRequiredFieldsBody /> }) ->React.array @@ -59,11 +75,34 @@ let make = ( let json = ev.data->Js.Json.parseExn let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper let (token, customerId) = paymentToken - let savedCardBody = PaymentBody.savedCardBody(~paymentToken=token, ~customerId, ~cvcNumber) + let customerMethod = + savedMethods + ->Js.Array2.filter(savedMethod => { + savedMethod.paymentToken === token + }) + ->Belt.Array.get(0) + ->Belt.Option.getWithDefault(PaymentType.defaultCustomerMethods) + let isCardPaymentMethod = customerMethod.paymentMethod === "card" + let savedPaymentMethodBody = switch customerMethod.paymentMethod { + | "card" => PaymentBody.savedCardBody(~paymentToken=token, ~customerId, ~cvcNumber) + | _ => { + let paymentMethodType = switch customerMethod.paymentMethodType { + | Some("") + | None => Js.Json.null + | Some(paymentMethodType) => paymentMethodType->Js.Json.string + } + PaymentBody.savedPaymentMethodBody( + ~paymentToken=token, + ~customerId, + ~paymentMethod=customerMethod.paymentMethod, + ~paymentMethodType, + ) + } + } if confirm.doSubmit { - if areRequiredFieldsValid && complete && !empty { + if areRequiredFieldsValid && (!isCardPaymentMethod || (complete && !empty)) { intent( - ~bodyArr=savedCardBody + ~bodyArr=savedPaymentMethodBody ->Js.Dict.fromArray ->Js.Json.object_ ->OrcaUtils.flattenObject(true) @@ -81,15 +120,16 @@ let make = ( if !(isCVCValid->Belt.Option.getWithDefault(false)) { setUserError(localeString.enterValidDetailsText) } + if !areRequiredFieldsValid { + setUserError(localeString.enterValidDetailsText) + } } } }, (areRequiredFieldsValid, requiredFieldsBody, empty, complete)) submitPaymentData(submitCallback) <> -
+
{if ( savedCardlength === 0 && (loadSavedCards === PaymentType.LoadingSavedCards || !showFields) ) { @@ -112,19 +152,8 @@ let make = (
} else { - - - + {bottomElement} }} -
true) }}> - {React.string(localeString.addNewCard)} + {React.string(localeString.morePaymentMethods)}
diff --git a/src/LocaleString.res b/src/LocaleString.res index 57ff6934d..1c89b2324 100644 --- a/src/LocaleString.res +++ b/src/LocaleString.res @@ -63,6 +63,10 @@ type localeStrings = { billingDetailsText: string, socialSecurityNumberLabel: string, saveWalletDetails: string, + morePaymentMethods: string, + useExistingPaymentMethods: string, + selectPaymentMethodLabel: string, + savedPaymentMethodsLabel: string, } let defaultLocale = { @@ -141,6 +145,10 @@ let defaultLocale = { billingDetailsText: "Billing Details", socialSecurityNumberLabel: "Social Security Number", saveWalletDetails: "Wallets details will be saved upon selection", + morePaymentMethods: "More payment methods", + useExistingPaymentMethods: "Use saved payment methods", + selectPaymentMethodLabel: "Select Payment Method", + savedPaymentMethodsLabel: "Saved Payment Methods", } type locale = {localeStrings: array} @@ -221,6 +229,10 @@ let localeStrings = [ billingDetailsText: "Billing Details", socialSecurityNumberLabel: "Social Security Number", saveWalletDetails: "Wallets details will be saved upon selection", + morePaymentMethods: "More payment methods", + useExistingPaymentMethods: "Use saved payment methods", + selectPaymentMethodLabel: "Select Payment Method", + savedPaymentMethodsLabel: "Saved Payment Methods", }, { locale: "he", @@ -298,6 +310,10 @@ let localeStrings = [ billingDetailsText: `פרטי תשלום`, socialSecurityNumberLabel: `מספר ביטוח לאומי`, saveWalletDetails: "פרטי הארנק יישמרו בעת בחירה", + morePaymentMethods: `אמצעי תשלום נוספים`, + useExistingPaymentMethods: `השתמש באמצעי תשלום שמורים`, + selectPaymentMethodLabel: `בחר שיטת תשלום`, + savedPaymentMethodsLabel: `אמצעי תשלום שמורים`, }, { locale: `fr`, @@ -375,6 +391,10 @@ let localeStrings = [ billingDetailsText: `Détails de la facturation`, socialSecurityNumberLabel: `Numéro de sécurité sociale`, saveWalletDetails: "Les détails du portefeuille seront enregistrés lors de la sélection", + morePaymentMethods: `Plus de méthodes de paiement`, + useExistingPaymentMethods: `Utiliser les modes de paiement enregistrés`, + selectPaymentMethodLabel: `Sélectionnez le mode de paiement`, + savedPaymentMethodsLabel: `Modes de paiement enregistrés`, }, { locale: "en-GB", @@ -452,6 +472,10 @@ let localeStrings = [ billingDetailsText: "Billing Details", socialSecurityNumberLabel: "Social Security Number", saveWalletDetails: "Wallets details will be saved upon selection", + morePaymentMethods: "More payment methods", + useExistingPaymentMethods: "Use saved payment methods", + selectPaymentMethodLabel: "Select Payment Method", + savedPaymentMethodsLabel: "Saved Payment Methods", }, { locale: "ar", @@ -529,6 +553,10 @@ let localeStrings = [ billingDetailsText: `تفاصيل الفاتورة`, socialSecurityNumberLabel: `رقم الضمان الاجتماعي`, saveWalletDetails: "سيتم حفظ تفاصيل المحفظة عند الاختيار", + morePaymentMethods: `المزيد من طرق الدفع`, + useExistingPaymentMethods: `استخدم طرق الدفع المحفوظة`, + selectPaymentMethodLabel: `اختار طريقة الدفع`, + savedPaymentMethodsLabel: `طرق الدفع المحفوظة`, }, { locale: "ja", @@ -606,6 +634,10 @@ let localeStrings = [ billingDetailsText: `支払明細`, socialSecurityNumberLabel: `社会保障番号`, saveWalletDetails: "選択時にウォレットの詳細が保存されます", + morePaymentMethods: `その他の支払い方法`, + useExistingPaymentMethods: `保存した支払い方法を使用する`, + selectPaymentMethodLabel: `支払い方法を選択してください`, + savedPaymentMethodsLabel: `保存された支払い方法`, }, { locale: "de", @@ -683,5 +715,9 @@ let localeStrings = [ billingDetailsText: `Rechnungsdetails`, socialSecurityNumberLabel: `Sozialversicherungsnummer`, saveWalletDetails: "Wallet-Details werden beim Auswählen gespeichert", + morePaymentMethods: `Mehr Zahlungsmethoden`, + useExistingPaymentMethods: `Gespeicherte Zahlungsarten nutzen`, + selectPaymentMethodLabel: `Wählen Sie die Zahlungsmethode`, + savedPaymentMethodsLabel: `Gespeicherte Zahlungsarten`, }, ] diff --git a/src/PaymentElement.res b/src/PaymentElement.res index 151dd9fbd..f738c2bac 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -14,6 +14,7 @@ let make = ( ~countryProps, ~paymentType: CardThemeType.mode, ) => { + let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) let sessionsObj = Recoil.useRecoilValueFromAtom(sessions) let { showCardFormByDefault, @@ -51,10 +52,7 @@ let make = ( React.useEffect1(() => { switch customerPaymentMethods { | LoadingSavedCards => () - | LoadedSavedCards(arr, isGuestCustomer) => { - let savedCards = arr->Js.Array2.filter((item: PaymentType.customerMethods) => { - item.paymentMethod == "card" - }) + | LoadedSavedCards(savedCards, isGuestCustomer) => { setSavedMethods(_ => savedCards) setLoadSavedCards(_ => savedCards->Js.Array2.length == 0 @@ -81,12 +79,20 @@ let make = ( }, [displaySavedPaymentMethodsCheckbox]) React.useEffect1(() => { - let tokenobj = - savedMethods->Js.Array2.length > 0 - ? Some(savedMethods->Belt.Array.get(0)->Belt.Option.getWithDefault(defaultCustomerMethods)) - : None + let defaultPaymentMethod = + savedMethods + ->Js.Array2.find(savedMethod => savedMethod.defaultPaymentMethodSet) - switch tokenobj { + let isSavedMethodsEmpty = savedMethods->Js.Array2.length === 0 + + let tokenObj = switch (isSavedMethodsEmpty, defaultPaymentMethod) { + | (false, Some(defaultPaymentMethod)) => Some(defaultPaymentMethod) + | (false, None) => + Some(savedMethods->Belt.Array.get(0)->Belt.Option.getWithDefault(defaultCustomerMethods)) + | _ => None + } + + switch tokenObj { | Some(obj) => setPaymentToken(._ => (obj.paymentToken, obj.customerId)) | None => () } @@ -353,7 +359,13 @@ let make = ( }} } + + let paymentLabel = showFields + ? localeString.selectPaymentMethodLabel + : localeString.savedPaymentMethodsLabel + <> +
{React.string(paymentLabel)}
{ setShowFields(._ => false) }}> - - {React.string(localeString.useExisitingSavedCards)} + + {React.string(localeString.useExistingPaymentMethods)}
diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 4c1901413..a5c2784b4 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -121,6 +121,8 @@ type customerMethods = { paymentMethod: string, paymentMethodIssuer: option, card: customerCard, + paymentMethodType: option, + defaultPaymentMethodSet: bool, } type savedCardsLoadState = LoadingSavedCards | LoadedSavedCards(array, bool) | NoResult(bool) @@ -162,6 +164,8 @@ let defaultCustomerMethods = { paymentMethod: "", paymentMethodIssuer: None, card: defaultCardDetails, + paymentMethodType: None, + defaultPaymentMethodSet: false, } let defaultLayout = { defaultCollapsed: false, @@ -767,6 +771,10 @@ let getCardDetails = (dict, str) => { ->Belt.Option.getWithDefault(defaultCardDetails) } +let getPaymentMethodType = dict => { + dict->Js.Dict.get("payment_method_type")->Belt.Option.flatMap(Js.Json.decodeString) +} + let createCustomerObjArr = dict => { let customerDict = dict @@ -789,13 +797,15 @@ let createCustomerObjArr = dict => { let customerPaymentMethods = customerArr ->Belt.Array.keepMap(Js.Json.decodeObject) - ->Js.Array2.map(json => { + ->Js.Array2.map(dict => { { - paymentToken: getString(json, "payment_token", ""), - customerId: getString(json, "customer_id", ""), - paymentMethod: getString(json, "payment_method", ""), - paymentMethodIssuer: Some(getString(json, "payment_method_issuer", "")), - card: getCardDetails(json, "card"), + paymentToken: getString(dict, "payment_token", ""), + customerId: getString(dict, "customer_id", ""), + paymentMethod: getString(dict, "payment_method", ""), + paymentMethodIssuer: Some(getString(dict, "payment_method_issuer", "")), + card: getCardDetails(dict, "card"), + paymentMethodType: getPaymentMethodType(dict), + defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), } }) LoadedSavedCards(customerPaymentMethods, isGuestCustomer) @@ -816,6 +826,8 @@ let getCustomerMethods = (dict, str) => { paymentMethod: getString(json, "payment_method", ""), paymentMethodIssuer: Some(getString(json, "payment_method_issuer", "")), card: getCardDetails(json, "card"), + paymentMethodType: getPaymentMethodType(dict), + defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), } }) LoadedSavedCards(customerPaymentMethods, false) diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index ef29c34cd..dacfac091 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -76,6 +76,13 @@ let customerAcceptanceBody = ->Js.Dict.fromArray ->Js.Json.object_ +let savedPaymentMethodBody = (~paymentToken, ~customerId, ~paymentMethod, ~paymentMethodType) => [ + ("payment_method", paymentMethod->Js.Json.string), + ("payment_token", paymentToken->Js.Json.string), + ("customer_id", customerId->Js.Json.string), + ("payment_method_type", paymentMethodType), +] + let mandateBody = paymentType => { [ ( diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index fe3be5abf..a5b94ec38 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -857,3 +857,5 @@ let nbsp = `\u00A0` let callbackFuncForExtractingValFromDict = key => { x => x->Js.Dict.get(key) } + +let brandIconSize = 28