Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added paypal tabs flow support for billing details #792

Merged
merged 10 commits into from
Nov 21, 2024
33 changes: 29 additions & 4 deletions src/PaymentElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
layout,
customerPaymentMethods,
displaySavedPaymentMethods,
sdkHandleConfirmPayment,
} = Recoil.useRecoilValueFromAtom(optionAtom)
let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(configAtom)
let optionAtomValue = Recoil.useRecoilValueFromAtom(optionAtom)
let paymentMethodList = Recoil.useRecoilValueFromAtom(paymentMethodList)
let (sessions, setSessions) = React.useState(_ => Dict.make()->JSON.Encode.object)
let (paymentOptions, setPaymentOptions) = React.useState(_ => [])
let (walletOptions, setWalletOptions) = React.useState(_ => [])
let {sdkHandleConfirmPayment} = Recoil.useRecoilValueFromAtom(optionAtom)

let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady)
let isGPayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady)
let isApplePayReady = Recoil.useRecoilValueFromAtom(isApplePayReady)
let isGPayReady = Recoil.useRecoilValueFromAtom(isGooglePayReady)

let (paymentMethodListValue, setPaymentMethodListValue) = Recoil.useRecoilState(
PaymentUtils.paymentMethodListValue,
Expand Down Expand Up @@ -158,6 +157,11 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
googlePayThirdPartySessionObj.sessionsToken,
Gpay,
)
let {
paypalToken,
isPaypalSDKFlow,
isPaypalRedirectFlow,
} = PayPalHelpers.usePaymentMethodExperience(~paymentMethodListValue, ~sessionObj)

React.useEffect(() => {
switch paymentMethodList {
Expand Down Expand Up @@ -355,6 +359,27 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
| _ => React.null
}}
</SessionPaymentWrapper>
| PayPal =>
<SessionPaymentWrapper type_={Wallet}>
{switch paypalToken {
| OtherTokenOptional(optToken) =>
switch (optToken, isPaypalSDKFlow, isPaypalRedirectFlow) {
| (Some(_token), true, _) => {
loggerState.setLogInfo(
~value="PayPal Invoke SDK Flow in Tabs",
~eventName=PAYPAL_SDK_FLOW,
)
React.null
}
| (_, _, true) => <PayPalLazy paymentType walletOptions />
| _ => React.null
}
| _ =>
<RenderIf condition={isPaypalRedirectFlow}>
<PayPalLazy paymentType walletOptions />
</RenderIf>
}}
</SessionPaymentWrapper>
| _ =>
<ReusableReactSuspense loaderComponent={loader()} componentName="PaymentMethodsWrapperLazy">
<PaymentMethodsWrapperLazy paymentType paymentMethodName=selectedOption />
Expand Down
85 changes: 64 additions & 21 deletions src/Payments/PayPal.res
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ module Loader = {
let payPalIcon = <Icon size=35 width=90 name="paypal" />

@react.component
let make = () => {
let make = (~paymentType, ~walletOptions) => {
let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom)
let (paypalClicked, setPaypalClicked) = React.useState(_ => false)
let sdkHandleIsThere = Recoil.useRecoilValueFromAtom(isPaymentButtonHandlerProvidedAtom)
let {publishableKey} = Recoil.useRecoilValueFromAtom(keys)
let options = Recoil.useRecoilValueFromAtom(optionAtom)
let areOneClickWalletsRendered = Recoil.useSetRecoilState(areOneClickWalletsRendered)
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)
let isWallet = walletOptions->Array.includes("paypal")
let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make())

let (_, _, labelType) = options.wallets.style.type_
let _label = switch labelType {
Expand Down Expand Up @@ -56,12 +58,16 @@ let make = () => {
let (connectors, _) =
paymentMethodListValue->PaymentUtils.getConnectors(Wallets(Paypal(Redirect)))
let body = PaymentBody.paypalRedirectionBody(~connectors)

let modifiedPaymentBody = PaymentUtils.appendedCustomerAcceptance(
let basePaymentBody = PaymentUtils.appendedCustomerAcceptance(
~isGuestCustomer,
~paymentType=paymentMethodListValue.payment_type,
~body,
)
let modifiedPaymentBody = if isWallet {
basePaymentBody
} else {
basePaymentBody->Utils.mergeAndFlattenToTuples(requiredFieldsBody)
}

intent(
~bodyArr=modifiedPaymentBody,
Expand All @@ -88,24 +94,61 @@ let make = () => {
None
})

<button
style={
display: "inline-block",
color: textColor,
height: `${height->Int.toString}px`,
borderRadius: `${options.wallets.style.buttonRadius->Int.toString}px`,
width: "100%",
backgroundColor: buttonColor,
}
onClick={_ => options.readOnly ? () : onPaypalClick()}>
<div className="justify-center" style={display: "flex", flexDirection: "row", color: textColor}>
{if !paypalClicked {
payPalIcon
} else {
<Loader />
}}
</div>
</button>
let useSubmitCallback = (~isWallet) => {
let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)

React.useCallback((ev: Window.event) => {
if !isWallet {
let json = ev.data->Utils.safeParse
let confirm = json->Utils.getDictFromJson->ConfirmType.itemToObjMapper
if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty {
onPaypalClick(ev)
} else if areRequiredFieldsEmpty {
Utils.postFailedSubmitResponse(
~errortype="validation_error",
~message=localeString.enterFieldsText,
)
} else if !areRequiredFieldsValid {
Utils.postFailedSubmitResponse(
~errortype="validation_error",
~message=localeString.enterValidDetailsText,
)
}
}
}, (areRequiredFieldsValid, areRequiredFieldsEmpty, isWallet, iframeId))
}

let submitCallback = useSubmitCallback(~isWallet)
Utils.useSubmitPaymentData(submitCallback)

if isWallet {
<button
style={
display: "inline-block",
color: textColor,
height: `${height->Int.toString}px`,
borderRadius: `${options.wallets.style.buttonRadius->Int.toString}px`,
width: "100%",
backgroundColor: buttonColor,
}
onClick={_ => options.readOnly ? () : onPaypalClick()}>
<div
className="justify-center" style={display: "flex", flexDirection: "row", color: textColor}>
{if !paypalClicked {
payPalIcon
} else {
<Loader />
}}
</div>
</button>
} else {
<DynamicFields
paymentType paymentMethod="wallet" paymentMethodType="paypal" setRequiredFieldsBody
/>
}
}

let default = make
2 changes: 1 addition & 1 deletion src/Payments/PayPal.resi
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@react.component
let default: unit => React.element
let default: (~paymentType: CardThemeType.mode, ~walletOptions: array<string>) => React.element
31 changes: 31 additions & 0 deletions src/Payments/PayPalHelpers.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
open PaymentMethodsRecord
open SessionsType

type paypalExperienceData = {
paypalToken: optionalTokenType,
isPaypalSDKFlow: bool,
isPaypalRedirectFlow: bool,
}

let usePaymentMethodExperience = (~paymentMethodListValue, ~sessionObj: sessions) => {
let paypalPaymentMethodExperience = React.useMemo(() => {
getPaymentExperienceTypeFromPML(
~paymentMethodList=paymentMethodListValue,
~paymentMethodName="wallet",
~paymentMethodType="paypal",
)
}, [paymentMethodListValue])

let paypalToken = React.useMemo(
() => getPaymentSessionObj(sessionObj.sessionsToken, Paypal),
[sessionObj],
)
let isPaypalSDKFlow = paypalPaymentMethodExperience->Array.includes(InvokeSDK)
let isPaypalRedirectFlow = paypalPaymentMethodExperience->Array.includes(RedirectToURL)

{
paypalToken,
isPaypalSDKFlow,
isPaypalRedirectFlow,
}
}
7 changes: 7 additions & 0 deletions src/Payments/PaymentMethodsRecord.res
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,13 @@ let paymentMethodsFields = [
fields: [InfoElement],
miniIcon: None,
},
{
paymentMethodName: "paypal",
icon: Some(icon("paypal", ~size=21, ~width=25)),
displayName: "Paypal",
fields: [],
miniIcon: None,
},
{
paymentMethodName: "local_bank_transfer_transfer",
fields: [InfoElement],
Expand Down
24 changes: 9 additions & 15 deletions src/Payments/PaymentRequestButtonElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,13 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)

let sessionObj = React.useMemo(() => itemToObjMapper(dict, Others), [dict])
let paypalToken = React.useMemo(
() => getPaymentSessionObj(sessionObj.sessionsToken, Paypal),
[sessionObj],
)
let paypalPaymentMethodExperience = React.useMemo(() => {
PaymentMethodsRecord.getPaymentExperienceTypeFromPML(
~paymentMethodList=paymentMethodListValue,
~paymentMethodName="wallet",
~paymentMethodType="paypal",
)
}, [paymentMethodListValue])

let {
paypalToken,
isPaypalSDKFlow,
isPaypalRedirectFlow,
} = PayPalHelpers.usePaymentMethodExperience(~paymentMethodListValue, ~sessionObj)

let gPayToken = getPaymentSessionObj(sessionObj.sessionsToken, Gpay)
let applePaySessionObj = itemToObjMapper(dict, ApplePayObject)
let applePayToken = getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay)
Expand All @@ -70,8 +66,6 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {

let {clientSecret} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
let isPaypalSDKFlow = paypalPaymentMethodExperience->Array.includes(InvokeSDK)
let isPaypalRedirectFlow = paypalPaymentMethodExperience->Array.includes(RedirectToURL)

<div className="flex flex-col gap-2 h-auto w-full">
{walletOptions
Expand Down Expand Up @@ -113,12 +107,12 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
| OtherTokenOptional(optToken) =>
switch (optToken, isPaypalSDKFlow, isPaypalRedirectFlow) {
| (Some(token), true, _) => <PaypalSDKLazy sessionObj=token paymentType />
| (_, _, true) => <PayPalLazy />
| (_, _, true) => <PayPalLazy paymentType walletOptions />
| _ => React.null
}
| _ =>
<RenderIf condition={isPaypalRedirectFlow}>
<PayPalLazy />
<PayPalLazy paymentType walletOptions />
</RenderIf>
}}
</SessionPaymentWrapper>
Expand Down
2 changes: 2 additions & 0 deletions src/Types/PaymentModeType.res
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type payment =
| GooglePay
| ApplePay
| Boleto
| PayPal
| NONE

let paymentMode = str => {
Expand All @@ -43,6 +44,7 @@ let paymentMode = str => {
| "google_pay" => GooglePay
| "apple_pay" => ApplePay
| "boleto" => Boleto
| "paypal" => PayPal
| _ => NONE
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Utilities/DynamicFieldsUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ let dynamicFieldsEnabledPaymentMethods = [
"bacs",
"pay_bright",
"multibanco_transfer",
"paypal",
]

let getName = (item: PaymentMethodsRecord.required_fields, field: RecoilAtomTypes.field) => {
Expand Down
Loading
Loading