Skip to content

Commit

Permalink
feat: added Klarna as a one click widget
Browse files Browse the repository at this point in the history
  • Loading branch information
ArushKapoorJuspay committed Jun 5, 2024
1 parent 68bf927 commit 6bbd72d
Show file tree
Hide file tree
Showing 21 changed files with 361 additions and 137 deletions.
1 change: 1 addition & 0 deletions src/CardUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ let getCardBrandIcon = (cardType, paymentType) => {
| GooglePayElement
| PayPalElement
| ApplePayElement
| KlarnaElement
| ExpressCheckoutElement
| NONE =>
<Icon size=brandIconSize name="default-card" />
Expand Down
14 changes: 8 additions & 6 deletions src/Components/SurchargeUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions src/LoaderController.res
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
| GooglePayElement
| PayPalElement
| ApplePayElement
| KlarnaElement
| ExpressCheckoutElement
| Payment => {
let paymentOptions = PaymentType.itemToObjMapper(optionsDict, logger)
Expand Down
30 changes: 8 additions & 22 deletions src/PaymentElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -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, _) => {
Expand Down Expand Up @@ -106,6 +108,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
let (walletList, paymentOptionsList, actualList) = PaymentUtils.useGetPaymentMethodList(
~paymentOptions,
~paymentType,
~sessions,
)

React.useEffect(() => {
Expand Down Expand Up @@ -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(
Expand All @@ -243,25 +243,11 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
{switch selectedOption->PaymentModeType.paymentMode {
| Card => <CardPayment cardProps expiryProps cvcProps paymentType />
| Klarna =>
<SessionPaymentWrapper type_=Others>
{switch klarnaTokenObj {
| OtherTokenOptional(optToken) =>
switch optToken {
| Some(token) =>
<React.Suspense fallback={loader()}>
<KlarnaSDKLazy sessionObj=token />
</React.Suspense>
| None =>
<React.Suspense fallback={loader()}>
<KlarnaPaymentLazy paymentType />
</React.Suspense>
}
| _ =>
<React.Suspense fallback={loader()}>
<KlarnaPaymentLazy paymentType />
</React.Suspense>
}}
</SessionPaymentWrapper>
<RenderIf condition={isKlarnaRedirectFlow}>
<React.Suspense fallback={loader()}>
<KlarnaPaymentLazy paymentType />
</React.Suspense>
</RenderIf>
| ACHTransfer =>
<React.Suspense fallback={loader()}>
<ACHBankTransferLazy paymentType />
Expand Down
10 changes: 5 additions & 5 deletions src/Payments/ApplePay.res
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ let make = (~sessionObj: option<JSON.t>) => {
None
}, (isApplePayReady, isInvokeSDKFlow, paymentExperience))

<div>
<style> {React.string(css)} </style>
<RenderIf condition={showApplePay}>
<RenderIf condition={showApplePay}>
<div>
<style> {React.string(css)} </style>
{if showApplePayLoader {
<div className="apple-pay-loader-div">
<div className="apple-pay-loader" />
Expand All @@ -362,8 +362,8 @@ let make = (~sessionObj: option<JSON.t>) => {
<span className="logo" />
</button>
}}
</RenderIf>
</div>
</div>
</RenderIf>
}

let default = make
2 changes: 1 addition & 1 deletion src/Payments/GPay.res
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ let make = (~sessionObj: option<SessionsType.token>, ~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
Expand Down
167 changes: 103 additions & 64 deletions src/Payments/KlarnaSDK.res
Original file line number Diff line number Diff line change
@@ -1,71 +1,50 @@
open RecoilAtoms
type token = {client_token: string}
type loadType = {
container: option<string>,
color_text: option<string>,
payment_method_category: option<string>,
}
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({
Expand All @@ -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 = <InfoElement />
<div className="p-1 animate-slowShow">
<div id="klarna-payments" className="m-3 hidden" />
<Surcharge paymentMethod="pay_later" paymentMethodType="klarna" />
<Block bottomElement />
</div>
}, (status, stateJson, paymentMethodTypes))

<div style={height: `${height->Belt.Int.toString}px`} id="klarna-payments" className="w-full" />
}

let default = make
45 changes: 45 additions & 0 deletions src/Payments/KlarnaSDKTypes.res
Original file line number Diff line number Diff line change
@@ -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<unit>,
}
type some = {
init: token => unit,
load: (loadType, JSON.t => unit) => unit,
}
2 changes: 1 addition & 1 deletion src/Payments/PayPal.res
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 6bbd72d

Please sign in to comment.