Skip to content

Commit

Permalink
feat: added support for collecting_billing_details_from_wallets
Browse files Browse the repository at this point in the history
  • Loading branch information
ArushKapoorJuspay committed Jul 29, 2024
1 parent e3049c4 commit 8716a58
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 73 deletions.
35 changes: 35 additions & 0 deletions src/PaymentElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
~sessions,
)

let dict = sessions->getDictFromJson
let sessionObj = SessionsType.itemToObjMapper(dict, Others)
let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject)
let applePayToken = SessionsType.getPaymentSessionObj(applePaySessionObj.sessionsToken, ApplePay)
let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay)
let googlePayThirdPartySessionObj = SessionsType.itemToObjMapper(dict, GooglePayThirdPartyObject)
let googlePayThirdPartyToken = SessionsType.getPaymentSessionObj(
googlePayThirdPartySessionObj.sessionsToken,
Gpay,
)

React.useEffect(() => {
switch paymentMethodList {
| Loaded(paymentlist) =>
Expand Down Expand Up @@ -317,6 +328,30 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod
<React.Suspense fallback={loader()}>
<BoletoLazy paymentType />
</React.Suspense>
| ApplePay =>
switch applePayToken {
| ApplePayTokenOptional(optToken) =>
<ApplePayLazy sessionObj=optToken walletOptions paymentType />
| _ => React.null
}
| GooglePay =>
<SessionPaymentWrapper type_={Wallet}>
{switch gPayToken {
| OtherTokenOptional(optToken) =>
switch googlePayThirdPartyToken {
| GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) =>
<GPayLazy
sessionObj=optToken
thirdPartySessionObj=googlePayThirdPartyOptToken
walletOptions
paymentType
/>
| _ =>
<GPayLazy sessionObj=optToken thirdPartySessionObj=None walletOptions paymentType />
}
| _ => React.null
}}
</SessionPaymentWrapper>
| _ =>
<React.Suspense fallback={loader()}>
<PaymentMethodsWrapperLazy paymentType paymentMethodName=selectedOption />
Expand Down
65 changes: 44 additions & 21 deletions src/Payments/ApplePay.res
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
open Utils
open Promise
@react.component
let make = (~sessionObj: option<JSON.t>) => {
let make = (~sessionObj: option<JSON.t>, ~walletOptions, ~paymentType: CardThemeType.mode) => {
let url = RescriptReactRouter.useUrl()
let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName")
let loggerState = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
Expand All @@ -21,6 +21,17 @@ let make = (~sessionObj: option<JSON.t>) => {
let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered)
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)

let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make())
let isWallet = walletOptions->Array.includes("apple_pay")

UtilityHooks.useHandlePostMessages(
~complete=areRequiredFieldsValid,
~empty=areRequiredFieldsEmpty,
~paymentType="apple_pay",
)

let applePayPaymentMethodType = React.useMemo(() => {
switch PaymentMethodsRecord.getPaymentMethodTypeFromList(
~paymentMethodListValue,
Expand Down Expand Up @@ -260,12 +271,15 @@ let make = (~sessionObj: option<JSON.t>) => {
~setApplePayClicked,
~syncPayment,
~isInvokeSDKFlow,
~isWallet,
~requiredFieldsBody,
)

React.useEffect(() => {
if (
(isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) &&
isApplePayReady
isApplePayReady &&
isWallet
) {
setShowApplePay(_ => true)
areOneClickWalletsRendered(prev => {
Expand All @@ -275,26 +289,35 @@ let make = (~sessionObj: option<JSON.t>) => {
setIsShowOrPayUsing(_ => true)
}
None
}, (isApplePayReady, isInvokeSDKFlow, paymentExperience))
}, (isApplePayReady, isInvokeSDKFlow, paymentExperience, isWallet))

<RenderIf condition={showApplePay}>
<div>
<style> {React.string(css)} </style>
{if showApplePayLoader {
<div className="apple-pay-loader-div">
<div className="apple-pay-loader" />
</div>
} else {
<button
disabled=applePayClicked
className="apple-pay-button-with-text apple-pay-button-black-with-text"
onClick={_ => onApplePayButtonClicked()}>
<span className="text"> {React.string("Pay with")} </span>
<span className="logo" />
</button>
}}
</div>
</RenderIf>
let submitCallback = ApplePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName)
useSubmitPaymentData(submitCallback)

if isWallet {
<RenderIf condition={showApplePay}>
<div>
<style> {React.string(css)} </style>
{if showApplePayLoader {
<div className="apple-pay-loader-div">
<div className="apple-pay-loader" />
</div>
} else {
<button
disabled=applePayClicked
className="apple-pay-button-with-text apple-pay-button-black-with-text"
onClick={_ => onApplePayButtonClicked()}>
<span className="text"> {React.string("Pay with")} </span>
<span className="logo" />
</button>
}}
</div>
</RenderIf>
} else {
<DynamicFields
paymentType paymentMethod="wallet" paymentMethodType="apple_pay" setRequiredFieldsBody
/>
}
}

let default = make
6 changes: 5 additions & 1 deletion src/Payments/ApplePay.resi
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
@react.component
let default: (~sessionObj: option<JSON.t>) => React.element
let default: (
~sessionObj: option<JSON.t>,
~walletOptions: array<string>,
~paymentType: CardThemeType.mode,
) => React.element
52 changes: 40 additions & 12 deletions src/Payments/GPay.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ open GooglePayType
open Promise

@react.component
let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: option<JSON.t>) => {
let make = (
~sessionObj: option<SessionsType.token>,
~thirdPartySessionObj: option<JSON.t>,
~walletOptions,
~paymentType: CardThemeType.mode,
) => {
let url = RescriptReactRouter.useUrl()
let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName")
let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom)
Expand All @@ -28,6 +33,17 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti

let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered)

let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
let (requiredFieldsBody, setRequiredFieldsBody) = React.useState(_ => Dict.make())
let isWallet = walletOptions->Array.includes("google_pay")

UtilityHooks.useHandlePostMessages(
~complete=areRequiredFieldsValid,
~empty=areRequiredFieldsEmpty,
~paymentType="google_pay",
)

let googlePayPaymentMethodType = switch PaymentMethodsRecord.getPaymentMethodTypeFromList(
~paymentMethodListValue,
~paymentMethod="wallet",
Expand Down Expand Up @@ -58,7 +74,7 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
->Option.getOr(false)
}, [thirdPartySessionObj])

GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent)
GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent, ~isWallet, ~requiredFieldsBody)

let (_, buttonType, _) = options.wallets.style.type_
let (_, heightType, _, _) = options.wallets.style.height
Expand Down Expand Up @@ -155,9 +171,10 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
React.useEffect(() => {
if (
status == "ready" &&
(isGPayReady ||
isDelayedSessionToken ||
paymentExperience == PaymentMethodsRecord.RedirectToURL)
(isGPayReady ||
isDelayedSessionToken ||
paymentExperience == PaymentMethodsRecord.RedirectToURL) &&
isWallet
) {
setIsShowOrPayUsing(_ => true)
addGooglePayButton()
Expand Down Expand Up @@ -190,7 +207,9 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
})

let isRenderGooglePayButton =
isGPayReady || paymentExperience == PaymentMethodsRecord.RedirectToURL || isDelayedSessionToken
(isGPayReady ||
paymentExperience == PaymentMethodsRecord.RedirectToURL ||
isDelayedSessionToken) && isWallet

React.useEffect(() => {
areOneClickWalletsRendered(prev => {
Expand All @@ -200,13 +219,22 @@ let make = (~sessionObj: option<SessionsType.token>, ~thirdPartySessionObj: opti
None
}, [isRenderGooglePayButton])

<RenderIf condition={isRenderGooglePayButton}>
<div
style={height: `${height->Int.toString}px`}
id="google-pay-button"
className={`w-full flex flex-row justify-center rounded-md`}
let submitCallback = GooglePayHelpers.useSubmitCallback(~isWallet, ~sessionObj, ~componentName)
useSubmitPaymentData(submitCallback)

if isWallet {
<RenderIf condition={isRenderGooglePayButton}>
<div
style={height: `${height->Int.toString}px`}
id="google-pay-button"
className={`w-full flex flex-row justify-center rounded-md`}
/>
</RenderIf>
} else {
<DynamicFields
paymentType paymentMethod="wallet" paymentMethodType="google_pay" setRequiredFieldsBody
/>
</RenderIf>
}
}

let default = make
2 changes: 2 additions & 0 deletions src/Payments/GPay.resi
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
let default: (
~sessionObj: option<SessionsType.token>,
~thirdPartySessionObj: option<JSON.t>,
~walletOptions: array<string>,
~paymentType: CardThemeType.mode,
) => React.element
7 changes: 7 additions & 0 deletions src/Payments/PaymentMethodsRecord.res
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@ type paymentMethodList = {
mandate_payment: option<mandate>,
payment_type: payment_type,
merchant_name: string,
collect_billing_details_from_wallets: bool,
}

let defaultPaymentMethodType = {
Expand All @@ -814,6 +815,7 @@ let defaultList = {
mandate_payment: None,
payment_type: NONE,
merchant_name: "",
collect_billing_details_from_wallets: true,
}
let getMethod = str => {
switch str {
Expand Down Expand Up @@ -1021,6 +1023,11 @@ let itemToObjMapper = dict => {
mandate_payment: getMandate(dict, "mandate_payment"),
payment_type: getString(dict, "payment_type", "")->paymentTypeMapper,
merchant_name: getString(dict, "merchant_name", ""),
collect_billing_details_from_wallets: getBool(
dict,
"collect_billing_details_from_wallets",
true,
),
}
}

Expand Down
13 changes: 10 additions & 3 deletions src/Payments/PaymentRequestButtonElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,15 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
switch googlePayThirdPartyToken {
| GooglePayThirdPartyTokenOptional(googlePayThirdPartyOptToken) =>
<GPayLazy
sessionObj=optToken thirdPartySessionObj=googlePayThirdPartyOptToken
sessionObj=optToken
thirdPartySessionObj=googlePayThirdPartyOptToken
walletOptions
paymentType
/>
| _ =>
<GPayLazy
sessionObj=optToken thirdPartySessionObj=None walletOptions paymentType
/>
| _ => <GPayLazy sessionObj=optToken thirdPartySessionObj=None />
}
| _ => React.null
}}
Expand All @@ -97,7 +103,8 @@ let make = (~sessions, ~walletOptions, ~paymentType) => {
</SessionPaymentWrapper>
| ApplePayWallet =>
switch applePayToken {
| ApplePayTokenOptional(optToken) => <ApplePayLazy sessionObj=optToken />
| ApplePayTokenOptional(optToken) =>
<ApplePayLazy sessionObj=optToken walletOptions paymentType />
| _ => React.null
}
| KlarnaWallet =>
Expand Down
52 changes: 49 additions & 3 deletions src/Utilities/ApplePayHelpers.res
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ let useHandleApplePayResponse = (
~syncPayment=() => (),
~isInvokeSDKFlow=true,
~isSavedMethodsFlow=false,
~isWallet=true,
~requiredFieldsBody=Dict.make(),
) => {
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
Expand Down Expand Up @@ -187,8 +189,18 @@ let useHandleApplePayResponse = (
~isSavedMethodsFlow,
)

let bodyArr = if isWallet {
applePayBody
} else {
applePayBody
->getJsonFromArrayOfJson
->flattenObject(true)
->mergeTwoFlattenedJsonDicts(requiredFieldsBody)
->getArrayOfTupleFromDict
}

processPayment(
~bodyArr=applePayBody,
~bodyArr,
~isThirdPartyFlow=false,
~isGuestCustomer,
~paymentMethodListValue,
Expand All @@ -199,7 +211,7 @@ let useHandleApplePayResponse = (
)
} else if dict->Dict.get("showApplePayButton")->Option.isSome {
setApplePayClicked(_ => false)
if isSavedMethodsFlow {
if isSavedMethodsFlow || !isWallet {
postFailedSubmitResponse(~errortype="server_error", ~message="Something went wrong")
}
} else if dict->Dict.get("applePaySyncPayment")->Option.isSome {
Expand All @@ -216,7 +228,14 @@ let useHandleApplePayResponse = (
Window.removeEventListener("message", handleApplePayMessages)
},
)
}, (isInvokeSDKFlow, processPayment, stateJson, isManualRetryEnabled))
}, (
isInvokeSDKFlow,
processPayment,
stateJson,
isManualRetryEnabled,
isWallet,
requiredFieldsBody,
))
}

let handleApplePayButtonClicked = (~sessionObj, ~componentName) => {
Expand All @@ -227,3 +246,30 @@ let handleApplePayButtonClicked = (~sessionObj, ~componentName) => {
]
handlePostMessage(message)
}

let useSubmitCallback = (~isWallet, ~sessionObj, ~componentName) => {
let areRequiredFieldsValid = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsValid)
let areRequiredFieldsEmpty = Recoil.useRecoilValueFromAtom(RecoilAtoms.areRequiredFieldsEmpty)
let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
let {localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)

React.useCallback((ev: Window.event) => {
if !isWallet {
let json = ev.data->JSON.parseExn
let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper
if confirm.doSubmit && areRequiredFieldsValid && !areRequiredFieldsEmpty {
options.readOnly ? () : handleApplePayButtonClicked(~sessionObj, ~componentName)
} else if areRequiredFieldsEmpty {
postFailedSubmitResponse(
~errortype="validation_error",
~message=localeString.enterFieldsText,
)
} else if !areRequiredFieldsValid {
postFailedSubmitResponse(
~errortype="validation_error",
~message=localeString.enterValidDetailsText,
)
}
}
}, (areRequiredFieldsValid, areRequiredFieldsEmpty, isWallet, sessionObj, componentName))
}
Loading

0 comments on commit 8716a58

Please sign in to comment.