Skip to content

Commit

Permalink
feat: pm auth connector integration - Plaid (#461)
Browse files Browse the repository at this point in the history
Co-authored-by: Pritish Budhiraja <pritish.budhiraja@gmail.com>
Co-authored-by: vsrivatsa-juspay <vrishab.srivatsa@juspay.in>
  • Loading branch information
3 people committed Jul 3, 2024
1 parent 1bbc48c commit 222b322
Show file tree
Hide file tree
Showing 17 changed files with 575 additions and 72 deletions.
1 change: 1 addition & 0 deletions src/App.res
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ let make = () => {
| _ =>
switch fullscreenMode {
| "paymentloader" => <PaymentLoader />
| "plaidSDK" => <PlaidSDKIframe />
| "fullscreen" =>
<div id="fullscreen">
<FullScreenDivDriver />
Expand Down
39 changes: 30 additions & 9 deletions src/Components/SavedCardItem.res
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
module RenderSavedPaymentMethodItem = {
@react.component
let make = (~paymentItem: PaymentType.customerMethods, ~paymentMethodType) => {
switch paymentItem.paymentMethod {
| "card" =>
<div className="flex flex-col items-start">
<div> {React.string(paymentItem.card.nickname)} </div>
<div className={`PickerItemLabel flex flex-row gap-3 items-center`}>
<div className="tracking-widest"> {React.string(`****`)} </div>
<div> {React.string(paymentItem.card.last4Digits)} </div>
</div>
</div>
| "bank_debit" =>
<div className="flex flex-col items-start">
<div>
{React.string(
`${paymentMethodType->String.toUpperCase} ${paymentItem.paymentMethod->Utils.snakeToTitleCase}`,
)}
</div>
<div className={`PickerItemLabel flex flex-row gap-3 items-center`}>
<div className="tracking-widest"> {React.string(`****`)} </div>
<div> {React.string(paymentItem.bank.mask)} </div>
</div>
</div>
| _ => <div> {React.string(paymentMethodType->Utils.snakeToTitleCase)} </div>
}
}
}

@react.component
let make = (
~setPaymentToken,
Expand Down Expand Up @@ -109,15 +138,7 @@ let make = (
<div className={`PickerItemIcon mx-3 flex items-center `}> brandIcon </div>
<div className="flex flex-col">
<div className="flex items-center gap-4">
{isCard
? <div className="flex flex-col items-start">
<div> {React.string(paymentItem.card.nickname)} </div>
<div className={`PickerItemLabel flex flex-row gap-3 items-center`}>
<div className="tracking-widest"> {React.string(`****`)} </div>
<div> {React.string(paymentItem.card.last4Digits)} </div>
</div>
</div>
: <div> {React.string(paymentMethodType->Utils.snakeToTitleCase)} </div>}
<RenderSavedPaymentMethodItem paymentItem={paymentItem} paymentMethodType />
<RenderIf
condition={displayDefaultSavedPaymentIcon &&
paymentItem.defaultPaymentMethodSet}>
Expand Down
1 change: 1 addition & 0 deletions src/Components/SavedMethods.res
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ let make = (
->Array.mapWithIndex((obj, i) => {
let brandIcon = switch obj.paymentMethod {
| "wallet" => getWalletBrandIcon(obj)
| "bank_debit" => <Icon size=brandIconSize name="bank" />
| _ =>
getCardBrandIcon(
switch obj.card.scheme {
Expand Down
1 change: 0 additions & 1 deletion src/LoaderController.res
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ open Utils
let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTimestamp) => {
open RecoilAtoms

Console.log("in LoaderController")
//<...>//
let (configAtom, setConfig) = Recoil.useRecoilState(configAtom)
let (keys, setKeys) = Recoil.useRecoilState(keys)
Expand Down
63 changes: 40 additions & 23 deletions src/Payments/ACHBankDebit.res
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ open PaymentModeType
@react.component
let make = (~paymentType: CardThemeType.mode) => {
let {themeObj} = Recoil.useRecoilValueFromAtom(configAtom)
let {displaySavedPaymentMethods} = Recoil.useRecoilValueFromAtom(optionAtom)
let isManualRetryEnabled = Recoil.useRecoilValueFromAtom(isManualRetryEnabled)

let loggerState = Recoil.useRecoilValueFromAtom(loggerAtom)
Expand All @@ -29,6 +30,15 @@ let make = (~paymentType: CardThemeType.mode) => {
let (state, _) = Recoil.useLoggedRecoilState(userAddressState, "state", loggerState)
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)

let pmAuthMapper = React.useMemo1(
() =>
PmAuthConnectorUtils.findPmAuthAllPMAuthConnectors(paymentMethodListValue.payment_methods),
[paymentMethodListValue.payment_methods],
)

let isVerifyPMAuthConnectorConfigured =
displaySavedPaymentMethods && pmAuthMapper->Dict.get("ach")->Option.isSome

OutsideClick.useOutsideClick(
~refs=ArrayOfRef([toolTipRef]),
~isActive=openToolTip,
Expand Down Expand Up @@ -94,30 +104,37 @@ let make = (~paymentType: CardThemeType.mode) => {
}, (email, modalData, fullName, isManualRetryEnabled))
useSubmitPaymentData(submitCallback)

<div className="flex flex-col animate-slowShow" style={gridGap: themeObj.spacingGridColumn}>
<FullNamePaymentInput paymentType={paymentType} />
<EmailPaymentInput paymentType />
<div className="flex flex-col">
<AddBankAccount modalData setModalData />
<RenderIf condition={bankError->String.length > 0}>
<div
className="Error pt-1"
style={
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
alignSelf: "start",
textAlign: "left",
}>
{React.string(bankError)}
<>
<RenderIf condition={isVerifyPMAuthConnectorConfigured}>
<AddBankDetails paymentMethodType="ach" />
</RenderIf>
<RenderIf condition={!isVerifyPMAuthConnectorConfigured}>
<div className="flex flex-col animate-slowShow" style={gridGap: themeObj.spacingGridColumn}>
<FullNamePaymentInput paymentType={paymentType} />
<EmailPaymentInput paymentType />
<div className="flex flex-col">
<AddBankAccount modalData setModalData />
<RenderIf condition={bankError->String.length > 0}>
<div
className="Error pt-1"
style={
color: themeObj.colorDangerText,
fontSize: themeObj.fontSizeSm,
alignSelf: "start",
textAlign: "left",
}>
{React.string(bankError)}
</div>
</RenderIf>
</div>
</RenderIf>
</div>
<Surcharge paymentMethod="bank_debit" paymentMethodType="ach" />
<Terms mode=ACHBankDebit />
<FullScreenPortal>
<BankDebitModal setModalData />
</FullScreenPortal>
</div>
<Surcharge paymentMethod="bank_debit" paymentMethodType="ach" />
<Terms mode=ACHBankDebit />
<FullScreenPortal>
<BankDebitModal setModalData />
</FullScreenPortal>
</div>
</RenderIf>
</>
}

let default = make
104 changes: 104 additions & 0 deletions src/Payments/AddBankDetails.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
module Loader = {
@react.component
let make = () => {
let {themeObj} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
<div className="w-full flex items-center justify-center">
<div className="w-8 h-8 animate-spin" style={color: themeObj.colorTextSecondary}>
<Icon size=32 name="loader" />
</div>
</div>
}
}

@react.component
let make = (~paymentMethodType) => {
open Utils
open Promise
let {publishableKey, clientSecret, iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
let {themeObj} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
let setOptionValue = Recoil.useSetRecoilState(RecoilAtoms.optionAtom)
let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue)
let setShowFields = Recoil.useSetRecoilState(RecoilAtoms.showCardFieldsAtom)
let (showLoader, setShowLoader) = React.useState(() => false)

let pmAuthConnectorsArr =
PmAuthConnectorUtils.findPmAuthAllPMAuthConnectors(
paymentMethodListValue.payment_methods,
)->PmAuthConnectorUtils.getAllRequiredPmAuthConnectors

React.useEffect0(() => {
let onPlaidCallback = (ev: Window.event) => {
let json = ev.data->JSON.parseExn
let dict = json->Utils.getDictFromJson
if dict->getBool("isPlaid", false) {
let publicToken = dict->getDictFromDict("data")->getString("publicToken", "")
let isExited = dict->getDictFromDict("data")->getBool("isExited", false)
setShowLoader(_ => !isExited)
if publicToken->String.length > 0 {
PaymentHelpers.callAuthExchange(
~publicToken,
~clientSecret,
~paymentMethodType,
~publishableKey,
~setOptionValue,
)
->then(_ => {
handlePostMessage([("fullscreen", false->JSON.Encode.bool)])
setShowFields(_ => false)
JSON.Encode.null->resolve
})
->catch(_ => JSON.Encode.null->resolve)
->ignore
}
}
}

Window.addEventListener("message", onPlaidCallback)
Some(
() => {
Window.removeEventListener("message", ev => onPlaidCallback(ev))
},
)
})

let submitCallback = React.useCallback((ev: Window.event) => {
let json = ev.data->JSON.parseExn
let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper
if confirm.doSubmit {
postFailedSubmitResponse(
~errortype="validation_error",
~message="Please add Bank Details and then confirm payment with the added payment methods.",
)
}
}, [])
useSubmitPaymentData(submitCallback)

let onClickHandler = () => {
setShowLoader(_ => true)
PaymentHelpers.callAuthLink(
~publishableKey,
~clientSecret,
~iframeId,
~paymentMethodType,
~pmAuthConnectorsArr,
)->ignore
}

<button
onClick={_ => onClickHandler()}
disabled={showLoader}
style={
width: "100%",
padding: "20px",
cursor: "pointer",
borderRadius: themeObj.borderRadius,
borderColor: themeObj.borderColor,
borderWidth: "2px",
}>
{if showLoader {
<Loader />
} else {
{React.string("Add Bank Details")}
}}
</button>
}
3 changes: 3 additions & 0 deletions src/Payments/PaymentMethodsRecord.res
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ type paymentMethodTypes = {
bank_transfers_connectors: array<string>,
required_fields: array<required_fields>,
surcharge_details: option<surchargeDetails>,
pm_auth_connector: option<string>,
}

type methods = {
Expand Down Expand Up @@ -788,6 +789,7 @@ let defaultPaymentMethodType = {
bank_transfers_connectors: [],
required_fields: [],
surcharge_details: None,
pm_auth_connector: None,
}

let defaultList = {
Expand Down Expand Up @@ -935,6 +937,7 @@ let getPaymentMethodTypes = (dict, str) => {
paymentMethodType === "bancontact_card",
),
surcharge_details: jsonDict->getSurchargeDetails,
pm_auth_connector: getOptionString(jsonDict, "pm_auth_connector"),
}
})
}
Expand Down
78 changes: 78 additions & 0 deletions src/Payments/PlaidSDKIframe.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
@react.component
let make = () => {
open Utils

let logger = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
let (linkToken, setLinkToken) = React.useState(_ => "")
let (isReady, setIsReady) = React.useState(_ => false)
let (pmAuthConnectorsArr, setPmAuthConnectorsArr) = React.useState(_ => [])

React.useEffect0(() => {
handlePostMessage([("iframeMountedCallback", true->JSON.Encode.bool)])
let handle = (ev: Window.event) => {
let json = ev.data->JSON.parseExn

let metaData = json->getDictFromJson->getDictFromDict("metadata")
let linkToken = metaData->getString("linkToken", "")
if linkToken->String.length > 0 {
let pmAuthConnectorArray =
metaData
->getArray("pmAuthConnectorArray")
->Array.map(ele => ele->JSON.Decode.string)

setLinkToken(_ => linkToken)
setPmAuthConnectorsArr(_ => pmAuthConnectorArray)
}
}
Window.addEventListener("message", handle)
Some(() => {Window.removeEventListener("message", handle)})
})

React.useEffect(() => {
PmAuthConnectorUtils.mountAllRequriedAuthConnectorScripts(
~pmAuthConnectorsArr,
~onScriptLoaded=authConnector => {
switch authConnector->PmAuthConnectorUtils.pmAuthNameToTypeMapper {
| PLAID => setIsReady(_ => true)
| NONE => ()
}
},
~logger,
)
None
}, [pmAuthConnectorsArr])

React.useEffect(() => {
if isReady && linkToken->String.length > 0 {
let handler = Plaid.create({
token: linkToken,
onSuccess: (publicToken, _) => {
handlePostMessage([
("isPlaid", true->JSON.Encode.bool),
("publicToken", publicToken->JSON.Encode.string),
])
},
onExit: _ => {
handlePostMessage([
("fullscreen", false->JSON.Encode.bool),
("isPlaid", true->JSON.Encode.bool),
("isExited", true->JSON.Encode.bool),
("publicToken", ""->JSON.Encode.string),
])
},
})

handler.open_()
}

None
}, (isReady, linkToken))

<div
className="PlaidIframe h-screen w-screen bg-black/40 backdrop-blur-sm m-auto"
style={
transition: "opacity .35s ease .1s,background-color 600ms linear",
opacity: "100",
}
/>
}
Loading

0 comments on commit 222b322

Please sign in to comment.