diff --git a/src/App.res b/src/App.res index 27c1140ff..f4d9644d4 100644 --- a/src/App.res +++ b/src/App.res @@ -30,6 +30,7 @@ let make = () => { | _ => switch fullscreenMode { | "paymentloader" => + | "plaidSDK" => | "fullscreen" =>
diff --git a/src/Components/SavedCardItem.res b/src/Components/SavedCardItem.res index 940b31aa9..777cfc3f0 100644 --- a/src/Components/SavedCardItem.res +++ b/src/Components/SavedCardItem.res @@ -1,3 +1,32 @@ +module RenderSavedPaymentMethodItem = { + @react.component + let make = (~paymentItem: PaymentType.customerMethods, ~paymentMethodType) => { + switch paymentItem.paymentMethod { + | "card" => +
+
{React.string(paymentItem.card.nickname)}
+
+
{React.string(`****`)}
+
{React.string(paymentItem.card.last4Digits)}
+
+
+ | "bank_debit" => +
+
+ {React.string( + `${paymentMethodType->String.toUpperCase} ${paymentItem.paymentMethod->Utils.snakeToTitleCase}`, + )} +
+
+
{React.string(`****`)}
+
{React.string(paymentItem.bank.mask)}
+
+
+ | _ =>
{React.string(paymentMethodType->Utils.snakeToTitleCase)}
+ } + } +} + @react.component let make = ( ~setPaymentToken, @@ -109,15 +138,7 @@ let make = (
brandIcon
- {isCard - ?
-
{React.string(paymentItem.card.nickname)}
-
-
{React.string(`****`)}
-
{React.string(paymentItem.card.last4Digits)}
-
-
- :
{React.string(paymentMethodType->Utils.snakeToTitleCase)}
} + diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res index 89ca5cd8d..7474006f8 100644 --- a/src/Components/SavedMethods.res +++ b/src/Components/SavedMethods.res @@ -65,6 +65,7 @@ let make = ( ->Array.mapWithIndex((obj, i) => { let brandIcon = switch obj.paymentMethod { | "wallet" => getWalletBrandIcon(obj) + | "bank_debit" => | _ => getCardBrandIcon( switch obj.card.scheme { diff --git a/src/LoaderController.res b/src/LoaderController.res index 2c1aa5ff6..70ff70b65 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -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) diff --git a/src/Payments/ACHBankDebit.res b/src/Payments/ACHBankDebit.res index 08bda5ebb..a2bccc7f6 100644 --- a/src/Payments/ACHBankDebit.res +++ b/src/Payments/ACHBankDebit.res @@ -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) @@ -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, @@ -94,30 +104,37 @@ let make = (~paymentType: CardThemeType.mode) => { }, (email, modalData, fullName, isManualRetryEnabled)) useSubmitPaymentData(submitCallback) -
- - -
- - String.length > 0}> -
- {React.string(bankError)} + <> + + + + +
+ + +
+ + String.length > 0}> +
+ {React.string(bankError)} +
+
- -
- - - - - -
+ + + + + +
+ + } let default = make diff --git a/src/Payments/AddBankDetails.res b/src/Payments/AddBankDetails.res new file mode 100644 index 000000000..5525fd905 --- /dev/null +++ b/src/Payments/AddBankDetails.res @@ -0,0 +1,104 @@ +module Loader = { + @react.component + let make = () => { + let {themeObj} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) +
+
+ +
+
+ } +} + +@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 + } + + +} diff --git a/src/Payments/PaymentMethodsRecord.res b/src/Payments/PaymentMethodsRecord.res index 783173150..175032769 100644 --- a/src/Payments/PaymentMethodsRecord.res +++ b/src/Payments/PaymentMethodsRecord.res @@ -745,6 +745,7 @@ type paymentMethodTypes = { bank_transfers_connectors: array, required_fields: array, surcharge_details: option, + pm_auth_connector: option, } type methods = { @@ -788,6 +789,7 @@ let defaultPaymentMethodType = { bank_transfers_connectors: [], required_fields: [], surcharge_details: None, + pm_auth_connector: None, } let defaultList = { @@ -935,6 +937,7 @@ let getPaymentMethodTypes = (dict, str) => { paymentMethodType === "bancontact_card", ), surcharge_details: jsonDict->getSurchargeDetails, + pm_auth_connector: getOptionString(jsonDict, "pm_auth_connector"), } }) } diff --git a/src/Payments/PlaidSDKIframe.res b/src/Payments/PlaidSDKIframe.res new file mode 100644 index 000000000..93d960c29 --- /dev/null +++ b/src/Payments/PlaidSDKIframe.res @@ -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)) + +
+} diff --git a/src/Payments/SepaBankDebit.res b/src/Payments/SepaBankDebit.res index 0abe9cf9a..9a279c816 100644 --- a/src/Payments/SepaBankDebit.res +++ b/src/Payments/SepaBankDebit.res @@ -11,6 +11,8 @@ let make = (~paymentType: CardThemeType.mode) => { let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), BankDebits) let {themeObj} = Recoil.useRecoilValueFromAtom(configAtom) + let {displaySavedPaymentMethods} = Recoil.useRecoilValueFromAtom(optionAtom) + let (modalData, setModalData) = React.useState(_ => None) let (fullName, _) = Recoil.useLoggedRecoilState(userFullName, "fullName", loggerState) @@ -22,6 +24,16 @@ let make = (~paymentType: CardThemeType.mode) => { let (postalCode, _) = Recoil.useLoggedRecoilState(userAddressPincode, "postal_code", loggerState) let (state, _) = Recoil.useLoggedRecoilState(userAddressState, "state", loggerState) let setComplete = Recoil.useSetRecoilState(fieldsComplete) + let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + + let pmAuthMapper = React.useMemo1( + () => + PmAuthConnectorUtils.findPmAuthAllPMAuthConnectors(paymentMethodListValue.payment_methods), + [paymentMethodListValue.payment_methods], + ) + + let isVerifyPMAuthConnectorConfigured = + displaySavedPaymentMethods && pmAuthMapper->Dict.get("sepa")->Option.isSome let complete = email.value != "" && @@ -82,21 +94,27 @@ let make = (~paymentType: CardThemeType.mode) => { } }, (email, fullName, modalData, isManualRetryEnabled)) useSubmitPaymentData(submitCallback) - -
- - - - - - - - -
+ <> + + + + +
+ + + + + + + + +
+
+ } let default = make diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index ecb76617d..0f682e839 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -119,6 +119,7 @@ type customerCard = { cardHolderName: option, nickname: string, } +type bank = {mask: string} type customerMethods = { paymentToken: string, customerId: string, @@ -130,6 +131,7 @@ type customerMethods = { defaultPaymentMethodSet: bool, requiresCvv: bool, lastUsedAt: string, + bank: bank, } type savedCardsLoadState = LoadingSavedCards | LoadedSavedCards(array, bool) | NoResult(bool) @@ -189,6 +191,7 @@ let defaultCustomerMethods = { defaultPaymentMethodSet: false, requiresCvv: true, lastUsedAt: "", + bank: {mask: ""}, } let defaultLayout = { defaultCollapsed: false, @@ -851,18 +854,18 @@ let getPaymentMethodType = dict => { dict->Dict.get("payment_method_type")->Option.flatMap(JSON.Decode.string) } +let getBank = dict => { + { + mask: dict + ->getDictFromDict("bank") + ->getString("mask", ""), + } +} + let itemToCustomerObjMapper = customerDict => { - let customerArr = - customerDict - ->Dict.get("customer_payment_methods") - ->Option.flatMap(JSON.Decode.array) - ->Option.getOr([]) + let customerArr = customerDict->getArray("customer_payment_methods") - let isGuestCustomer = - customerDict - ->Dict.get("is_guest_customer") - ->Option.flatMap(JSON.Decode.bool) - ->Option.getOr(false) + let isGuestCustomer = customerDict->getBool("is_guest_customer", false) let customerPaymentMethods = customerArr @@ -879,6 +882,7 @@ let itemToCustomerObjMapper = customerDict => { defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), requiresCvv: getBool(dict, "requires_cvv", true), lastUsedAt: getString(dict, "last_used_at", ""), + bank: dict->getBank, } }) @@ -914,6 +918,7 @@ let getCustomerMethods = (dict, str) => { defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), requiresCvv: getBool(dict, "requires_cvv", true), lastUsedAt: getString(dict, "last_used_at", ""), + bank: dict->getBank, } }) LoadedSavedCards(customerPaymentMethods, false) diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index a8f0539aa..67b7241b0 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -30,7 +30,7 @@ type paymentIntent = ( ~iframeId: string=?, ~isThirdPartyFlow: bool=?, ~intentCallback: Core__JSON.t => unit=?, - ~manualRetry:bool=?, + ~manualRetry: bool=?, unit, ) => unit @@ -50,7 +50,7 @@ let retrievePaymentIntent = ( ~isForceSync=false, ) => { open Promise - let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let endpoint = ApiEndpoint.getApiEndPoint() let forceSync = isForceSync ? "&force_sync=true" : "" let uri = `${endpoint}/payments/${paymentIntentID}?client_secret=${clientSecret}${forceSync}` @@ -490,8 +490,7 @@ let rec intentCall = ( resolve(failedSubmitResponse) } } else { - let paymentIntentID = - String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let endpoint = ApiEndpoint.getApiEndPoint( ~publishableKey=confirmParam.publishableKey, ~isConfirmCall=isConfirm, @@ -880,8 +879,7 @@ let rec intentCall = ( resolve(failedSubmitResponse) } } else { - let paymentIntentID = - String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let endpoint = ApiEndpoint.getApiEndPoint( ~publishableKey=confirmParam.publishableKey, ~isConfirmCall=isConfirm, @@ -939,7 +937,7 @@ let usePaymentSync = (optLogger: option, paymentType: pay (~handleUserError=false, ~confirmParam: ConfirmType.confirmParams, ~iframeId="", ()) => { switch keys.clientSecret { | Some(clientSecret) => - let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let headers = [("Content-Type", "application/json"), ("api-key", confirmParam.publishableKey)] let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey=confirmParam.publishableKey, ()) let uri = `${endpoint}/payments/${paymentIntentID}?force_sync=true&client_secret=${clientSecret}` @@ -1022,16 +1020,14 @@ let usePaymentIntent = (optLogger, paymentType) => { ) => { switch keys.clientSecret { | Some(clientSecret) => - let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let headers = [ ("Content-Type", "application/json"), ("api-key", confirmParam.publishableKey), ("X-Client-Source", paymentTypeFromUrl->CardThemeType.getPaymentModeToStrMapper), ] let returnUrlArr = [("return_url", confirmParam.return_url->JSON.Encode.string)] - let manual_retry = manualRetry - ? [("retry_action", "manual_retry"->JSON.Encode.string)] - : [] + let manual_retry = manualRetry ? [("retry_action", "manual_retry"->JSON.Encode.string)] : [] let body = [("client_secret", clientSecret->JSON.Encode.string)]->Array.concatMany([ returnUrlArr, @@ -1203,7 +1199,7 @@ let useCompleteAuthorize = (optLogger: option, paymentTyp ) => { switch keys.clientSecret { | Some(clientSecret) => - let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let headers = [ ("Content-Type", "application/json"), ("api-key", confirmParam.publishableKey), @@ -1265,7 +1261,7 @@ let fetchSessions = ( ) => { open Promise let headers = [("Content-Type", "application/json"), ("api-key", publishableKey)] - let paymentIntentID = String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let body = [ ("payment_id", paymentIntentID->JSON.Encode.string), @@ -1717,3 +1713,134 @@ let paymentIntentForPaymentSession = ( (), ) } + +let callAuthLink = ( + ~publishableKey, + ~clientSecret, + ~paymentMethodType, + ~pmAuthConnectorsArr, + ~iframeId, +) => { + open Promise + let endpoint = ApiEndpoint.getApiEndPoint() + let uri = `${endpoint}/payment_methods/auth/link` + let headers = [("Content-Type", "application/json"), ("api-key", publishableKey)]->Dict.fromArray + + fetchApi( + uri, + ~method=#POST, + ~bodyStr=[ + ("client_secret", clientSecret->Option.getOr("")->JSON.Encode.string), + ("payment_id", clientSecret->Option.getOr("")->getPaymentId->JSON.Encode.string), + ("payment_method", "bank_debit"->JSON.Encode.string), + ("payment_method_type", paymentMethodType->JSON.Encode.string), + ] + ->getJsonFromArrayOfJson + ->JSON.stringify, + ~headers, + (), + ) + ->then(res => { + let statusCode = res->Fetch.Response.status->Int.toString + if statusCode->String.charAt(0) !== "2" { + res + ->Fetch.Response.json + ->then(_ => { + JSON.Encode.null->resolve + }) + } else { + res + ->Fetch.Response.json + ->then(data => { + let metaData = + [ + ("linkToken", data->getDictFromJson->getString("link_token", "")->JSON.Encode.string), + ("pmAuthConnectorArray", pmAuthConnectorsArr->Identity.anyTypeToJson), + ]->getJsonFromArrayOfJson + + handlePostMessage([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "plaidSDK"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ("metadata", metaData), + ]) + JSON.Encode.null->resolve + }) + } + }) + ->catch(e => { + Console.log2("Unable to retrieve payment_methods auth/link because of ", e) + JSON.Encode.null->resolve + }) +} + +let callAuthExchange = ( + ~publicToken, + ~clientSecret, + ~paymentMethodType, + ~publishableKey, + ~setOptionValue: (PaymentType.options => PaymentType.options) => unit, +) => { + open Promise + open PaymentType + let endpoint = ApiEndpoint.getApiEndPoint() + let logger = OrcaLogger.make(~source=Elements(Payment), ()) + let uri = `${endpoint}/payment_methods/auth/exchange` + let updatedBody = [ + ("client_secret", clientSecret->Option.getOr("")->JSON.Encode.string), + ("payment_id", clientSecret->Option.getOr("")->getPaymentId->JSON.Encode.string), + ("payment_method", "bank_debit"->JSON.Encode.string), + ("payment_method_type", paymentMethodType->JSON.Encode.string), + ("public_token", publicToken->JSON.Encode.string), + ] + + let headers = [("Content-Type", "application/json"), ("api-key", publishableKey)]->Dict.fromArray + + fetchApi( + uri, + ~method=#POST, + ~bodyStr=updatedBody->getJsonFromArrayOfJson->JSON.stringify, + ~headers, + (), + ) + ->then(res => { + let statusCode = res->Fetch.Response.status->Int.toString + if statusCode->String.charAt(0) !== "2" { + res + ->Fetch.Response.json + ->then(_ => { + JSON.Encode.null->resolve + }) + } else { + fetchCustomerPaymentMethodList( + ~clientSecret=clientSecret->Option.getOr(""), + ~publishableKey, + ~optLogger=Some(logger), + ~switchToCustomPod=false, + ~endpoint, + ) + ->then(customerListResponse => { + let customerListResponse = + [("customerPaymentMethods", customerListResponse)]->Dict.fromArray + setOptionValue( + prev => { + ...prev, + customerPaymentMethods: createCustomerObjArr(customerListResponse), + }, + ) + JSON.Encode.null->resolve + }) + ->catch(e => { + Console.log2( + "Unable to retrieve customer/payment_methods after auth/exchange because of ", + e, + ) + JSON.Encode.null->resolve + }) + } + }) + ->catch(e => { + Console.log2("Unable to retrieve payment_methods auth/exchange because of ", e) + JSON.Encode.null->resolve + }) +} diff --git a/src/Utilities/PmAuthConnectorUtils.res b/src/Utilities/PmAuthConnectorUtils.res new file mode 100644 index 000000000..d1c8621da --- /dev/null +++ b/src/Utilities/PmAuthConnectorUtils.res @@ -0,0 +1,85 @@ +type pmAuthConnector = PLAID | NONE +type isPmAuthConnectorReady = {plaid: bool} +let pmAuthNameToTypeMapper = authConnectorName => { + switch authConnectorName { + | "plaid" => PLAID + | _ => NONE + } +} + +let pmAuthConnectorToScriptUrlMapper = authConnector => { + switch authConnector { + | PLAID => "https://cdn.plaid.com/link/v2/stable/link-initialize.js" + | NONE => "" + } +} + +let mountAuthConnectorScript = ( + ~authConnector, + ~onScriptLoaded, + ~logger: OrcaLogger.loggerMake, +) => { + let authConnector = authConnector->Option.getOr("") + let pmAuthConnectorScriptUrl = + authConnector->pmAuthNameToTypeMapper->pmAuthConnectorToScriptUrlMapper + let pmAuthConnectorScript = Window.createElement("script") + logger.setLogInfo( + ~value=`Pm Auth Connector ${authConnector} Script Loading`, + ~eventName=PM_AUTH_CONNECTOR_SCRIPT, + (), + ) + pmAuthConnectorScript->Window.elementSrc(pmAuthConnectorScriptUrl) + pmAuthConnectorScript->Window.elementOnerror(_ => { + logger.setLogInfo( + ~value=`Pm Auth Connector ${authConnector} Script Load Failure`, + ~eventName=PM_AUTH_CONNECTOR_SCRIPT, + (), + ) + }) + pmAuthConnectorScript->Window.elementOnload(_ => { + onScriptLoaded(authConnector) + logger.setLogInfo( + ~value=`Pm Auth Connector ${authConnector} Script Loaded`, + ~eventName=PM_AUTH_CONNECTOR_SCRIPT, + (), + ) + }) + Window.body->Window.appendChild(pmAuthConnectorScript) +} + +let mountAllRequriedAuthConnectorScripts = ( + ~pmAuthConnectorsArr, + ~onScriptLoaded, + ~logger: OrcaLogger.loggerMake, +) => { + pmAuthConnectorsArr->Array.forEach(item => { + mountAuthConnectorScript(~authConnector=item, ~onScriptLoaded, ~logger) + }) +} + +let findPmAuthAllPMAuthConnectors = ( + paymentMethodListValue: array, +) => { + let bankDebitPaymentMethodsArr = + paymentMethodListValue->Array.filter(item => item.payment_method == "bank_debit") + + let pmAuthConnectorDict = Dict.make() + + bankDebitPaymentMethodsArr->Array.forEach(item => { + item.payment_method_types->Array.forEach(item => { + if item.pm_auth_connector->Option.isSome { + pmAuthConnectorDict->Dict.set(item.payment_method_type, item.pm_auth_connector) + } + }) + }) + + pmAuthConnectorDict +} + +let getAllRequiredPmAuthConnectors = pmAuthConnectorsDict => { + let requiredPmAuthConnectorsArr = pmAuthConnectorsDict->Dict.valuesToArray + + requiredPmAuthConnectorsArr->Array.filterWithIndex((item, idx) => + idx == requiredPmAuthConnectorsArr->Array.indexOf(item) + ) +} diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 1164f404d..c3e79a6c2 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1336,3 +1336,6 @@ let handleFailureResponse = (~message, ~errorType) => ]->getJsonFromArrayOfJson, ), ]->getJsonFromArrayOfJson + +let getPaymentId = clientSecret => + String.split(clientSecret, "_secret_")->Array.get(0)->Option.getOr("") diff --git a/src/libraries/Plaid.res b/src/libraries/Plaid.res new file mode 100644 index 000000000..ff8b7165e --- /dev/null +++ b/src/libraries/Plaid.res @@ -0,0 +1,22 @@ +// * For Documentation - https://plaid.com/docs/link/web/ + +type createArgs = { + token: string, + onSuccess: (string, JSON.t) => unit, + onLoad?: unit => unit, + onExit?: JSON.t => unit, + onEvent?: JSON.t => unit, +} + +type createReturn = { + @as("open") open_: unit => unit, + exit: unit => unit, + destroy: unit => unit, + submit: unit => unit, +} + +@val @scope(("window", "Plaid")) +external create: createArgs => createReturn = "create" + +@val @scope(("window", "Plaid")) +external version: string = "version" diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 9cc489a13..261075ea0 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -114,12 +114,27 @@ let make = ( ) }) + let onPlaidCallback = mountedIframeRef => { + (ev: Types.event) => { + let json = ev.data->Identity.anyTypeToJson + let dict = json->getDictFromJson + let isPlaidExist = dict->getBool("isPlaid", false) + if isPlaidExist { + mountedIframeRef->Window.iframePostMessage( + [("isPlaid", true->JSON.Encode.bool), ("data", json)]->Dict.fromArray, + ) + } + } + } + let fetchPaymentsList = (mountedIframeRef, componentType) => { let handlePaymentMethodsLoaded = (event: Types.event) => { let json = event.data->Identity.anyTypeToJson let dict = json->getDictFromJson let isPaymentMethodsData = dict->getString("data", "") === "payment_methods" if isPaymentMethodsData { + addSmartEventListener("message", onPlaidCallback(mountedIframeRef), "onPlaidCallback") + let json = dict->getJsonFromDict("response", JSON.Encode.null) let isApplePayPresent = PaymentMethodsRecord.getPaymentMethodTypeFromList( ~paymentMethodListValue=json diff --git a/src/orca-loader/Hyper.res b/src/orca-loader/Hyper.res index 8379fa373..8fff0138a 100644 --- a/src/orca-loader/Hyper.res +++ b/src/orca-loader/Hyper.res @@ -216,7 +216,7 @@ let make = (publishableKey, options: option, analyticsInfo: optionArray.get(0)->Option.getOr("") + let paymentIntentID = clientSecret->getPaymentId let retrievePaymentUrl = `${endpoint}/payments/${paymentIntentID}?client_secret=${clientSecret}` open Promise logApi( diff --git a/src/orca-log-catcher/OrcaLogger.res b/src/orca-log-catcher/OrcaLogger.res index 3f222ca17..7242fae97 100644 --- a/src/orca-log-catcher/OrcaLogger.res +++ b/src/orca-log-catcher/OrcaLogger.res @@ -27,6 +27,7 @@ type eventName = | CREATE_CUSTOMER_PAYMENT_METHODS_CALL_INIT | CREATE_CUSTOMER_PAYMENT_METHODS_CALL | TRUSTPAY_SCRIPT + | PM_AUTH_CONNECTOR_SCRIPT | GOOGLE_PAY_SCRIPT | APPLE_PAY_FLOW | GOOGLE_PAY_FLOW @@ -98,6 +99,7 @@ let eventNameToStrMapper = eventName => { | CREATE_CUSTOMER_PAYMENT_METHODS_CALL_INIT => "CREATE_CUSTOMER_PAYMENT_METHODS_CALL_INIT" | CREATE_CUSTOMER_PAYMENT_METHODS_CALL => "CREATE_CUSTOMER_PAYMENT_METHODS_CALL" | TRUSTPAY_SCRIPT => "TRUSTPAY_SCRIPT" + | PM_AUTH_CONNECTOR_SCRIPT => "PM_AUTH_CONNECTOR_SCRIPT" | GOOGLE_PAY_SCRIPT => "GOOGLE_PAY_SCRIPT" | APPLE_PAY_FLOW => "APPLE_PAY_FLOW" | GOOGLE_PAY_FLOW => "GOOGLE_PAY_FLOW" @@ -568,6 +570,8 @@ let make = (~sessionId=?, ~source: source, ~clientSecret=?, ~merchantId=?, ~meta DISPLAY_QR_CODE_INFO_PAGE, DISPLAY_VOUCHER, LOADER_CHANGED, + PAYMENT_METHODS_CALL, + PAYMENT_METHOD_CHANGED, SESSIONS_CALL, RETRIEVE_CALL, DISPLAY_THREE_DS_SDK,