From 6bbd72d3380624c071a77be81035bcf64e56bea6 Mon Sep 17 00:00:00 2001 From: Arush Date: Wed, 5 Jun 2024 14:34:11 +0530 Subject: [PATCH] feat: added Klarna as a one click widget --- src/CardUtils.res | 1 + src/Components/SurchargeUtils.res | 14 +- src/LoaderController.res | 1 + src/PaymentElement.res | 30 +--- src/Payments/ApplePay.res | 10 +- src/Payments/GPay.res | 2 +- src/Payments/KlarnaSDK.res | 167 ++++++++++++------- src/Payments/KlarnaSDKTypes.res | 45 +++++ src/Payments/PayPal.res | 2 +- src/Payments/PaymentRequestButtonElement.res | 16 +- src/Payments/PaypalSDK.res | 2 +- src/RenderPaymentMethods.res | 1 + src/Types/CardThemeType.res | 3 + src/Types/PaymentModeType.res | 6 +- src/Types/PaymentType.res | 46 ++++- src/Utilities/DynamicFieldsUtils.res | 95 ++++++++--- src/Utilities/PaymentUtils.res | 44 ++++- src/Utilities/RecoilAtoms.res | 2 + src/Utilities/Utils.res | 4 +- src/WalletElement.res | 6 +- src/orca-loader/Elements.res | 1 + 21 files changed, 361 insertions(+), 137 deletions(-) create mode 100644 src/Payments/KlarnaSDKTypes.res diff --git a/src/CardUtils.res b/src/CardUtils.res index d60723150..39a0441d6 100644 --- a/src/CardUtils.res +++ b/src/CardUtils.res @@ -361,6 +361,7 @@ let getCardBrandIcon = (cardType, paymentType) => { | GooglePayElement | PayPalElement | ApplePayElement + | KlarnaElement | ExpressCheckoutElement | NONE => diff --git a/src/Components/SurchargeUtils.res b/src/Components/SurchargeUtils.res index e6f2ebd71..54fbaae22 100644 --- a/src/Components/SurchargeUtils.res +++ b/src/Components/SurchargeUtils.res @@ -6,6 +6,7 @@ let oneClickWallets = [ {paymentMethodType: "apple_pay", displayName: "ApplePay"}, {paymentMethodType: "paypal", displayName: "Paypal"}, {paymentMethodType: "google_pay", displayName: "GooglePay"}, + {paymentMethodType: "klarna", displayName: "Klarna"}, ] type walletSurchargeDetails = { @@ -20,17 +21,18 @@ let useSurchargeDetailsForOneClickWallets = (~paymentMethodListValue) => { React.useMemo(() => { oneClickWallets->Array.reduce([], (acc, wallet) => { - let isWalletBtnRendered = switch wallet.paymentMethodType { - | "apple_pay" => areOneClickWalletsRendered.isApplePay - | "paypal" => areOneClickWalletsRendered.isPaypal - | "google_pay" => areOneClickWalletsRendered.isGooglePay - | _ => false + let (isWalletBtnRendered, paymentMethod) = switch wallet.paymentMethodType { + | "apple_pay" => (areOneClickWalletsRendered.isApplePay, "wallet") + | "paypal" => (areOneClickWalletsRendered.isPaypal, "wallet") + | "google_pay" => (areOneClickWalletsRendered.isGooglePay, "wallet") + | "klarna" => (areOneClickWalletsRendered.isKlarna, "pay_later") + | _ => (false, "") } if isWalletBtnRendered { let paymentMethodType = PaymentMethodsRecord.getPaymentMethodTypeFromList( ~paymentMethodListValue, - ~paymentMethod="wallet", + ~paymentMethod, ~paymentMethodType=wallet.paymentMethodType, )->Option.getOr(PaymentMethodsRecord.defaultPaymentMethodType) switch paymentMethodType.surcharge_details { diff --git a/src/LoaderController.res b/src/LoaderController.res index 530066a3c..a22245d5f 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -82,6 +82,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime | GooglePayElement | PayPalElement | ApplePayElement + | KlarnaElement | ExpressCheckoutElement | Payment => { let paymentOptions = PaymentType.itemToObjMapper(optionsDict, logger) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index d5f04ccee..ea40f6166 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -42,6 +42,8 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod setLoadSavedCards: (savedCardsLoadState => savedCardsLoadState) => unit, ) = React.useState(_ => LoadingSavedCards) + let isKlarnaRedirectFlow = PaymentUtils.getIsKlarnaRedirectFlow(sessions) + React.useEffect(() => { switch (displaySavedPaymentMethods, customerPaymentMethods) { | (false, _) => { @@ -106,6 +108,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let (walletList, paymentOptionsList, actualList) = PaymentUtils.useGetPaymentMethodList( ~paymentOptions, ~paymentType, + ~sessions, ) React.useEffect(() => { @@ -225,9 +228,6 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let checkRenderOrComp = () => { walletOptions->Array.includes("paypal") || isShowOrPayUsing } - let dict = sessions->getDictFromJson - let sessionObj = SessionsType.itemToObjMapper(dict, Others) - let klarnaTokenObj = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Klarna) let loader = () => { handlePostMessageEvents( @@ -243,25 +243,11 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod {switch selectedOption->PaymentModeType.paymentMode { | Card => | Klarna => - - {switch klarnaTokenObj { - | OtherTokenOptional(optToken) => - switch optToken { - | Some(token) => - - - - | None => - - - - } - | _ => - - - - }} - + + + + + | ACHTransfer => diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index 2bd861ebd..3471ba4c6 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -346,9 +346,9 @@ let make = (~sessionObj: option) => { None }, (isApplePayReady, isInvokeSDKFlow, paymentExperience)) -
- - + +
+ {if showApplePayLoader {
@@ -362,8 +362,8 @@ let make = (~sessionObj: option) => { }} - -
+
+ } let default = make diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index aa3782079..b904b0456 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -144,7 +144,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti }, (paymentMethodTypes, stateJson)) let (_, buttonType, _) = options.wallets.style.type_ - let (_, heightType, _) = options.wallets.style.height + let (_, heightType, _, _) = options.wallets.style.height let height = switch heightType { | GooglePay(val) => val | _ => 48 diff --git a/src/Payments/KlarnaSDK.res b/src/Payments/KlarnaSDK.res index 2acaf11dd..63fb85b43 100644 --- a/src/Payments/KlarnaSDK.res +++ b/src/Payments/KlarnaSDK.res @@ -1,71 +1,50 @@ open RecoilAtoms -type token = {client_token: string} -type loadType = { - container: option, - color_text: option, - payment_method_category: option, -} +open Utils +open Promise +open KlarnaSDKTypes -type res = { - approved: bool, - show_form: bool, - authorization_token: string, - finalize_required: bool, -} -type some = { - init: token => unit, - load: (loadType, res => unit) => unit, - authorize: (loadType, JSON.t, res => unit) => unit, - loadPaymentReview: (loadType, res => unit) => unit, -} - -@val external klarnaInit: some = "Klarna.Payments" +@val external klarnaInit: some = "Klarna.Payments.Buttons" @react.component let make = (~sessionObj: SessionsType.token) => { - open Utils + let url = RescriptReactRouter.useUrl() + let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName") let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom) + let setIsShowOrPayUsing = Recoil.useSetRecoilState(RecoilAtoms.isShowOrPayUsing) + let {publishableKey, sdkHandleOneClickConfirmPayment} = Recoil.useRecoilValueFromAtom(keys) + let options = Recoil.useRecoilValueFromAtom(optionAtom) let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Other) let {iframeId} = Recoil.useRecoilValueFromAtom(keys) let status = CommonHooks.useScript("https://x.klarnacdn.net/kp/lib/v1/api.js") // Klarna SDK script let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + let setAreOneClickWalletsRendered = Recoil.useSetRecoilState(areOneClickWalletsRendered) + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) + + let (_, _, _, heightType) = options.wallets.style.height + let height = switch heightType { + | Klarna(val) => val + | _ => 48 + } + let handleCloseLoader = () => { Utils.handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) } - let submitCallback = React.useCallback((ev: Window.event) => { - let json = ev.data->JSON.parseExn - let confirm = json->Utils.getDictFromJson->ConfirmType.itemToObjMapper - - if confirm.doSubmit { - Utils.handlePostMessage([ - ("fullscreen", true->JSON.Encode.bool), - ("param", "paymentloader"->JSON.Encode.string), - ("iframeId", iframeId->JSON.Encode.string), - ]) - klarnaInit.authorize( - { - container: None, - color_text: None, - payment_method_category: Some("klarna"), - }, - Dict.make()->JSON.Encode.object, - (res: res) => { - let (connectors, _) = - paymentMethodListValue->PaymentUtils.getConnectors(PayLater(Klarna(SDK))) - let body = PaymentBody.klarnaSDKbody(~token=res.authorization_token, ~connectors) - res.approved - ? intent(~bodyArr=body, ~confirmParam=confirm.confirmParams, ~handleUserError=false, ()) - : handleCloseLoader() - }, - ) - } - }, [status]) - useSubmitPaymentData(submitCallback) + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="pay_later", + ~paymentMethodType="klarna", + ) + + PaymentUtils.useStatesJson(setStatesJson) React.useEffect(() => { - if status == "ready" { + if ( + status === "ready" && + stateJson !== JSON.Encode.null && + paymentMethodTypes !== PaymentMethodsRecord.defaultPaymentMethodType + ) { let klarnaWrapper = GooglePayType.getElementById(Utils.document, "klarna-payments") klarnaWrapper.innerHTML = "" klarnaInit.init({ @@ -74,24 +53,84 @@ let make = (~sessionObj: SessionsType.token) => { klarnaInit.load( { - container: Some("#klarna-payments"), - color_text: Some("#E51515"), - payment_method_category: Some("pay_later"), + container: "#klarna-payments", + theme: options.wallets.style.theme == Dark ? "default" : "outlined", + shape: "default", + on_click: authorize => { + makeOneClickHandlerPromise(sdkHandleOneClickConfirmPayment)->then( + result => { + let result = result->JSON.Decode.bool->Option.getOr(false) + if result { + Utils.handlePostMessage([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "paymentloader"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ]) + authorize( + {collect_shipping_address: componentName->getIsExpressCheckoutComponent}, + Dict.make()->JSON.Encode.object, + (res: res) => { + let (connectors, _) = + paymentMethodListValue->PaymentUtils.getConnectors(PayLater(Klarna(SDK))) + + let shippingContact = + res.collected_shipping_address->Option.getOr( + defaultCollectedShippingAddress, + ) + + let requiredFieldsBody = DynamicFieldsUtils.getKlarnaRequiredFields( + ~shippingContact, + ~paymentMethodTypes, + ~statesList=stateJson, + ) + + let klarnaSDKBody = PaymentBody.klarnaSDKbody( + ~token=res.authorization_token, + ~connectors, + ) + + let body = { + klarnaSDKBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict + } + + res.approved + ? intent( + ~bodyArr=body, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=false, + (), + ) + : handleCloseLoader() + }, + ) + } + resolve() + }, + ) + }, }, - (_res: res) => { - handlePostMessageEvents(~complete=true, ~empty=false, ~paymentType="klarna", ~loggerState) + _ => { + setAreOneClickWalletsRendered( + prev => { + ...prev, + isKlarna: true, + }, + ) + setIsShowOrPayUsing(_ => true) }, ) } None - }, [status]) - - let bottomElement = -
-
- - -
+ }, (status, stateJson, paymentMethodTypes)) + +
Belt.Int.toString}px`} id="klarna-payments" className="w-full" /> } let default = make diff --git a/src/Payments/KlarnaSDKTypes.res b/src/Payments/KlarnaSDKTypes.res new file mode 100644 index 000000000..f8db1adf7 --- /dev/null +++ b/src/Payments/KlarnaSDKTypes.res @@ -0,0 +1,45 @@ +type token = {client_token: string} +type collected_shipping_address = { + city: string, + country: string, + email: string, + family_name: string, + given_name: string, + phone: string, + postal_code: string, + region: string, + street_address: string, +} +let defaultCollectedShippingAddress = { + city: "", + country: "", + email: "", + family_name: "", + given_name: "", + phone: "", + postal_code: "", + region: "", + street_address: "", +} + +type res = { + approved: bool, + show_form: bool, + authorization_token: string, + finalize_required: bool, + collected_shipping_address?: collected_shipping_address, +} +type authorizeAttributes = {collect_shipping_address: bool} +type authorize = (authorizeAttributes, JSON.t, res => unit) => unit +type loadType = { + container?: string, + color_text?: string, + payment_method_category?: string, + theme?: string, + shape?: string, + on_click?: authorize => Promise.t, +} +type some = { + init: token => unit, + load: (loadType, JSON.t => unit) => unit, +} diff --git a/src/Payments/PayPal.res b/src/Payments/PayPal.res index d82417def..35caa351a 100644 --- a/src/Payments/PayPal.res +++ b/src/Payments/PayPal.res @@ -24,7 +24,7 @@ let make = () => { | Paypal(val) => val->PaypalSDKTypes.getLabel | _ => Paypal->PaypalSDKTypes.getLabel } - let (_, _, heightType) = options.wallets.style.height + let (_, _, heightType, _) = options.wallets.style.height let height = switch heightType { | Paypal(val) => val | _ => 48 diff --git a/src/Payments/PaymentRequestButtonElement.res b/src/Payments/PaymentRequestButtonElement.res index 0c44ede94..aa8ac6b0a 100644 --- a/src/Payments/PaymentRequestButtonElement.res +++ b/src/Payments/PaymentRequestButtonElement.res @@ -1,4 +1,4 @@ -type wallet = GPayWallet | PaypalWallet | ApplePayWallet | NONE +type wallet = GPayWallet | PaypalWallet | ApplePayWallet | KlarnaWallet | NONE let paymentMode = str => { switch str { | "gpay" @@ -8,6 +8,7 @@ let paymentMode = str => { | "applepay" | "apple_pay" => ApplePayWallet + | "klarna" => KlarnaWallet | _ => NONE } } @@ -56,6 +57,8 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { Gpay, ) + let klarnaTokenObj = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Klarna) + let {clientSecret} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
@@ -97,6 +100,17 @@ let make = (~sessions, ~walletOptions, ~paymentType) => { | ApplePayTokenOptional(optToken) => | _ => React.null } + | KlarnaWallet => + + {switch klarnaTokenObj { + | OtherTokenOptional(optToken) => + switch optToken { + | Some(token) => + | None => React.null + } + | _ => React.null + }} + | NONE => React.null } diff --git a/src/Payments/PaypalSDK.res b/src/Payments/PaypalSDK.res index bb58dd951..c1df5c1ae 100644 --- a/src/Payments/PaypalSDK.res +++ b/src/Payments/PaypalSDK.res @@ -22,7 +22,7 @@ let make = (~sessionObj: SessionsType.token, ~paymentType: CardThemeType.mode) = let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) let (_, _, buttonType) = options.wallets.style.type_ - let (_, _, heightType) = options.wallets.style.height + let (_, _, heightType, _) = options.wallets.style.height let buttonStyle = { layout: "vertical", color: options.wallets.style.theme == Outline diff --git a/src/RenderPaymentMethods.res b/src/RenderPaymentMethods.res index 960c0d720..1023c4881 100644 --- a/src/RenderPaymentMethods.res +++ b/src/RenderPaymentMethods.res @@ -82,6 +82,7 @@ let make = ( | GooglePayElement | PayPalElement | ApplePayElement + | KlarnaElement | ExpressCheckoutElement | Payment => { | "googlePay" => GooglePayElement | "payPal" => PayPalElement | "applePay" => ApplePayElement + | "klarna" => KlarnaElement | "expressCheckout" => ExpressCheckoutElement | _ => NONE } @@ -113,6 +115,7 @@ let getPaymentModeToStrMapper = val => { | GooglePayElement => "GooglePayElement" | PayPalElement => "PayPalElement" | ApplePayElement => "ApplePayElement" + | KlarnaElement => "KlarnaElement" | ExpressCheckoutElement => "ExpressCheckoutElement" | NONE => "None" } diff --git a/src/Types/PaymentModeType.res b/src/Types/PaymentModeType.res index c95ff75c6..14d29b396 100644 --- a/src/Types/PaymentModeType.res +++ b/src/Types/PaymentModeType.res @@ -49,6 +49,9 @@ let paymentMode = str => { let defaultOrder = [ "card", + "apple_pay", + "google_pay", + "paypal", "klarna", "affirm", "afterpay_clearpay", @@ -63,9 +66,6 @@ let defaultOrder = [ "giropay", "ideal", "eps", - "apple_pay", - "google_pay", - "paypal", "crypto", "bancontact_card", "boleto", diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 2b00dc7c8..4bf8b4ac5 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -60,7 +60,7 @@ type terms = { usBankAccount: showTerms, } type buttonHeight = Default | Custom -type heightType = ApplePay(int) | GooglePay(int) | Paypal(int) +type heightType = ApplePay(int) | GooglePay(int) | Paypal(int) | Klarna(int) type googlePayStyleType = Default | Buy | Donate | Checkout | Subscribe | Book | Pay | Order type paypalStyleType = Paypal | Checkout | Buynow | Pay | Installment type applePayStyleType = @@ -86,13 +86,14 @@ type theme = Dark | Light | Outline type style = { type_: styleTypeArray, theme: theme, - height: (heightType, heightType, heightType), + height: (heightType, heightType, heightType, heightType), } type wallets = { walletReturnUrl: string, applePay: showType, googlePay: showType, payPal: showType, + klarna: showType, style: style, } type business = {name: string} @@ -253,13 +254,14 @@ let defaultFields = { let defaultStyle = { type_: (ApplePay(Default), GooglePay(Default), Paypal(Paypal)), theme: Light, - height: (ApplePay(48), GooglePay(48), Paypal(48)), + height: (ApplePay(48), GooglePay(48), Paypal(48), Klarna(48)), } let defaultWallets = { walletReturnUrl: "", applePay: Auto, googlePay: Auto, payPal: Auto, + klarna: Auto, style: defaultStyle, } let defaultBillingAddress = { @@ -713,6 +715,31 @@ let getPaypalHeight = (val, logger) => { : Paypal(val) val } +let getKlarnaHeight = (val, logger) => { + let val: heightType = + val < 40 + ? { + valueOutRangeWarning( + val, + "options.style.height", + "[40-60] - Klarna. Value set to min", + ~logger, + ) + Klarna(40) + } + : val > 60 + ? { + valueOutRangeWarning( + val, + "options.style.height", + "[40-60] - Paypal. Value set to max", + ~logger, + ) + Klarna(60) + } + : Klarna(val) + val +} let getTheme = (str, logger) => { switch str { | "outline" => Outline @@ -724,7 +751,12 @@ let getTheme = (str, logger) => { } } let getHeightArray = (val, logger) => { - (val->getApplePayHeight(logger), val->getGooglePayHeight(logger), val->getPaypalHeight(logger)) + ( + val->getApplePayHeight(logger), + val->getGooglePayHeight(logger), + val->getPaypalHeight(logger), + val->getKlarnaHeight(logger), + ) } let getStyle = (dict, str, logger) => { dict @@ -747,7 +779,7 @@ let getWallets = (dict, str, logger) => { ->Option.flatMap(JSON.Decode.object) ->Option.map(json => { unknownKeysWarning( - ["applePay", "googlePay", "style", "walletReturnUrl", "payPal"], + ["applePay", "googlePay", "style", "walletReturnUrl", "payPal", "klarna"], json, "options.wallets", ~logger, @@ -767,6 +799,10 @@ let getWallets = (dict, str, logger) => { "options.wallets.payPal", logger, ), + klarna: getWarningString(json, "klarna", "auto", ~logger)->getShowType( + "options.wallets.klarna", + logger, + ), style: getStyle(json, "style", logger), } }) diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 207f95b2f..1a0e770fe 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -753,6 +753,36 @@ let removeRequiredFieldsDuplicates = ( requiredFields } +let getNameFromString = (name, requiredFieldsArr) => { + let nameArr = name->String.split(" ") + let nameArrLength = nameArr->Array.length + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { + | "first_name" => { + let end = nameArrLength === 1 ? nameArrLength : nameArrLength - 1 + nameArr + ->Array.slice(~start=0, ~end) + ->Array.reduce("", (acc, item) => { + acc ++ " " ++ item + }) + } + | "last_name" => + if nameArrLength === 1 { + "" + } else { + nameArr->Array.get(nameArrLength - 1)->Option.getOr(name) + } + | _ => name + }->String.trim +} + +let getNameFromFirstAndLastName = (~firstName, ~lastName, ~requiredFieldsArr) => { + switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { + | "first_name" => firstName + | "last_name" => lastName + | _ => firstName->String.concatMany([" ", lastName]) + }->String.trim +} + let getApplePayRequiredFields = ( ~billingContact: ApplePayTypes.billingContact, ~shippingContact: ApplePayTypes.shippingContact, @@ -777,7 +807,11 @@ let getApplePayRequiredFields = ( let fieldVal = switch item.field_type { | FullName | BillingName => - getName(billingContact.givenName, billingContact.familyName) + getNameFromFirstAndLastName( + ~firstName=billingContact.givenName, + ~lastName=billingContact.familyName, + ~requiredFieldsArr, + ) | AddressLine1 => billingContact.addressLines->getAddressLine(0) | AddressLine2 => billingContact.addressLines->getAddressLine(1) | AddressCity => billingContact.locality @@ -816,28 +850,6 @@ let getApplePayRequiredFields = ( }) } -let getNameFromString = (name, requiredFieldsArr) => { - let nameArr = name->String.split(" ") - let nameArrLength = nameArr->Array.length - switch requiredFieldsArr->Array.get(requiredFieldsArr->Array.length - 1)->Option.getOr("") { - | "first_name" => { - let end = nameArrLength === 1 ? nameArrLength : nameArrLength - 1 - nameArr - ->Array.slice(~start=0, ~end) - ->Array.reduce("", (acc, item) => { - acc ++ " " ++ item - }) - } - | "last_name" => - if nameArrLength === 1 { - "" - } else { - nameArr->Array.get(nameArrLength - 1)->Option.getOr(name) - } - | _ => name - }->String.trim -} - let getGooglePayRequiredFields = ( ~billingContact: GooglePayType.billingContact, ~shippingContact: GooglePayType.billingContact, @@ -930,3 +942,40 @@ let getPaypalRequiredFields = ( acc }) } + +let getKlarnaRequiredFields = ( + ~shippingContact: KlarnaSDKTypes.collected_shipping_address, + ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~statesList, +) => { + paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + let requiredFieldsArr = item.required_field->String.split(".") + + let fieldVal = switch item.field_type { + | ShippingName => + getNameFromFirstAndLastName( + ~firstName=shippingContact.given_name, + ~lastName=shippingContact.family_name, + ~requiredFieldsArr, + ) + | ShippingAddressLine1 => shippingContact.street_address + | ShippingAddressCity => shippingContact.city + | ShippingAddressState => { + let administrativeArea = shippingContact.region + let countryCode = shippingContact.country + Utils.getStateNameFromStateCodeAndCountry(statesList, administrativeArea, countryCode) + } + | ShippingAddressCountry(_) => shippingContact.country + | ShippingAddressPincode => shippingContact.postal_code + | Email => shippingContact.email + | PhoneNumber => shippingContact.phone + | _ => "" + } + + if fieldVal !== "" { + acc->Dict.set(item.required_field, fieldVal->JSON.Encode.string) + } + + acc + }) +} diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index 39d975692..f9b1a654c 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -4,6 +4,8 @@ let paymentListLookupNew = ( list: PaymentMethodsRecord.paymentMethodList, ~order, ~isShowPaypal, + ~isShowKlarnaOneClick, + ~isKlarnaRedirectFlow, ) => { let pmList = list->PaymentMethodsRecord.buildFromPaymentList let walletsList = [] @@ -40,6 +42,12 @@ let paymentListLookupNew = ( otherPaymentList->Array.push("card")->ignore } else if item.methodType == "reward" { otherPaymentList->Array.push(item.paymentMethodName)->ignore + } else if item.methodType == "pay_later" { + if item.paymentMethodName === "klarna" && !isKlarnaRedirectFlow && isShowKlarnaOneClick { + walletsList->Array.push(item.paymentMethodName)->ignore + } else { + otherPaymentList->Array.push(item.paymentMethodName)->ignore + } } else { otherPaymentList->Array.push(item.paymentMethodName)->ignore } @@ -257,7 +265,17 @@ let useAreAllRequiredFieldsPrefilled = ( }) } -let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { +let getIsKlarnaRedirectFlow = sessions => { + let dict = sessions->Utils.getDictFromJson + let sessionObj = SessionsType.itemToObjMapper(dict, Others) + let klarnaTokenObj = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Klarna) + switch klarnaTokenObj { + | OtherTokenOptional(optToken) => optToken->Option.isNone + | _ => true + } +} + +let useGetPaymentMethodList = (~paymentOptions, ~paymentType, ~sessions) => { open Utils let methodslist = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentMethodList) @@ -268,6 +286,14 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { let paymentOrder = paymentMethodOrder->getOptionalArr->removeDuplicate + let isKlarnaRedirectFlow = getIsKlarnaRedirectFlow(sessions) + + let filterPaymentMethods = (paymentOptionsList: array, ~isKlarnaRedirectFlow) => { + paymentOptionsList->Array.filter(paymentOptionsName => + !(paymentOptionsName === "klarna" && !isKlarnaRedirectFlow) + ) + } + React.useMemo(() => { switch methodslist { | Loaded(paymentlist) => @@ -278,10 +304,15 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { plist->paymentListLookupNew( ~order=paymentOrder, ~isShowPaypal=optionAtomValue.wallets.payPal === Auto, + ~isShowKlarnaOneClick=optionAtomValue.wallets.klarna === Auto, + ~isKlarnaRedirectFlow, ) ( wallets->removeDuplicate->Utils.getWalletPaymentMethod(paymentType), - paymentOptions->Array.concat(otherOptions)->removeDuplicate, + paymentOptions + ->Array.concat(otherOptions) + ->removeDuplicate + ->filterPaymentMethods(~isKlarnaRedirectFlow), otherOptions, ) | SemiLoaded => @@ -290,7 +321,14 @@ let useGetPaymentMethodList = (~paymentOptions, ~paymentType) => { : ([], [], []) | _ => ([], [], []) } - }, (methodslist, paymentMethodOrder, optionAtomValue.wallets.payPal, paymentType)) + }, ( + methodslist, + paymentMethodOrder, + optionAtomValue.wallets.payPal, + optionAtomValue.wallets.klarna, + paymentType, + isKlarnaRedirectFlow, + )) } let useStatesJson = setStatesJson => { diff --git a/src/Utilities/RecoilAtoms.res b/src/Utilities/RecoilAtoms.res index e0014550a..40beb7a88 100644 --- a/src/Utilities/RecoilAtoms.res +++ b/src/Utilities/RecoilAtoms.res @@ -62,12 +62,14 @@ type areOneClickWalletsRendered = { isGooglePay: bool, isApplePay: bool, isPaypal: bool, + isKlarna: bool, } let defaultAreOneClickWalletsRendered = { isGooglePay: false, isApplePay: false, isPaypal: false, + isKlarna: false, } let areOneClickWalletsRendered = Recoil.atom( diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 7bddcce44..b7714e427 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1226,11 +1226,12 @@ let getWalletPaymentMethod = (wallets, paymentType: CardThemeType.mode) => { | GooglePayElement => wallets->Array.filter(item => item === "google_pay") | PayPalElement => wallets->Array.filter(item => item === "paypal") | ApplePayElement => wallets->Array.filter(item => item === "apple_pay") + | KlarnaElement => wallets->Array.filter(item => item === "klarna") | _ => wallets } } -let expressCheckoutComponents = ["googlePay", "payPal", "applePay", "expressCheckout"] +let expressCheckoutComponents = ["googlePay", "payPal", "applePay", "klarna", "expressCheckout"] let componentsForPaymentElementCreate = ["payment"]->Array.concat(expressCheckoutComponents) @@ -1246,6 +1247,7 @@ let walletElementPaymentType: array = [ GooglePayElement, PayPalElement, ApplePayElement, + KlarnaElement, ExpressCheckoutElement, ] diff --git a/src/WalletElement.res b/src/WalletElement.res index 3e22e8e3e..897286d7e 100644 --- a/src/WalletElement.res +++ b/src/WalletElement.res @@ -7,7 +7,11 @@ let make = (~paymentType) => { let setPaymentMethodListValue = Recoil.useSetRecoilState(PaymentUtils.paymentMethodListValue) - let (walletList, _, _) = PaymentUtils.useGetPaymentMethodList(~paymentOptions=[], ~paymentType) + let (walletList, _, _) = PaymentUtils.useGetPaymentMethodList( + ~paymentOptions=[], + ~paymentType, + ~sessions, + ) React.useEffect(() => { switch methodslist { diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 91a528c7c..4142b429b 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -260,6 +260,7 @@ let make = ( | "googlePay" | "payPal" | "applePay" + | "klarna" | "expressCheckout" | "payment" => () | str => manageErrorWarning(UNKNOWN_KEY, ~dynamicStr=`${str} type in create`, ~logger, ())