diff --git a/aws/hyperswitch_web_aws_production_deployment.sh b/aws/hyperswitch_web_aws_production_deployment.sh index 0f3fdd9a6..62c9ed923 100755 --- a/aws/hyperswitch_web_aws_production_deployment.sh +++ b/aws/hyperswitch_web_aws_production_deployment.sh @@ -76,12 +76,12 @@ echo $( (aws s3api put-bucket-policy --bucket $MY_AWS_S3_BUCKET_NAME --policy "$ echo "Bucket configuration updated" -echo "Enter the backend endpoint your Hyperswitch Client will hit (hosted Hyperswitch Backend, https://sandbox.hyperswitch.io is taken by default):" +echo "Enter the backend endpoint your Hyperswitch Client will hit (hosted Hyperswitch Backend, https://beta.hyperswitch.io/api is taken by default):" read AWS_BACKEND_URL { let clientSecret = CardUtils.getQueryParamsDictforKey(url.search, "clientSecret") let sessionId = CardUtils.getQueryParamsDictforKey(url.search, "sessionId") let publishableKey = CardUtils.getQueryParamsDictforKey(url.search, "publishableKey") - let endpoint = - CardUtils.getQueryParamsDictforKey(url.search, "endpoint")->decodeURIComponent - + } | "achBankTransfer" | "bacsBankTransfer" diff --git a/src/Payments/PreMountLoader.res b/src/Payments/PreMountLoader.res index 214e2ecd0..ba75d49b1 100644 --- a/src/Payments/PreMountLoader.res +++ b/src/Payments/PreMountLoader.res @@ -1,83 +1,108 @@ @react.component -let make = (~sessionId as _, ~publishableKey as _, ~clientSecret as _, ~endpoint as _) => { - // open Utils - // let logger = OrcaLogger.make( - // ~sessionId, - // ~source=Loader, - // ~merchantId=publishableKey, - // ~clientSecret, - // (), - // ) +let make = (~sessionId, ~publishableKey, ~clientSecret) => { + open Utils + let (paymentMethodsResponseSent, setPaymentMethodsResponseSent) = React.useState(_ => false) + let ( + customerPaymentMethodsResponseSent, + setCustomerPaymentMethodsResponseSent, + ) = React.useState(_ => false) + let (sessionTokensResponseSent, setSessionTokensResponseSent) = React.useState(_ => false) + let logger = OrcaLogger.make( + ~sessionId, + ~source=Loader, + ~merchantId=publishableKey, + ~clientSecret, + (), + ) - // // let ( - // // paymentMethodsResponse, - // // customerPaymentMethodsResponse, - // // sessionTokensResponse, - // // ) = React.useMemo0(() => { - // // ( - // // PaymentHelpers.fetchPaymentMethodList( - // // ~clientSecret, - // // ~publishableKey, - // // ~logger, - // // ~switchToCustomPod=false, - // // ~endpoint, - // // ), - // // PaymentHelpers.fetchCustomerDetails( - // // ~clientSecret, - // // ~publishableKey, - // // ~optLogger=Some(logger), - // // ~switchToCustomPod=false, - // // ~endpoint, - // // ), - // // PaymentHelpers.fetchSessions( - // // ~clientSecret, - // // ~publishableKey, - // // ~optLogger=Some(logger), - // // ~switchToCustomPod=false, - // // ~endpoint, - // // (), - // // ), - // // ) - // // }) + let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey, ()) - // // let sendPromiseData = (promise, key) => { - // // open Promise - // // promise - // // ->then(res => { - // // handlePostMessage([("response", res), ("data", key->JSON.Encode.string)]) - // // resolve() - // // }) - // // ->catch(_err => { - // // handlePostMessage([("response", JSON.Encode.null), ("data", key->JSON.Encode.string)]) - // // resolve() - // // }) - // // ->ignore - // // } + let paymentMethodsResponse = React.useMemo0(() => + PaymentHelpers.fetchPaymentMethodList( + ~clientSecret, + ~publishableKey, + ~logger, + ~switchToCustomPod=false, + ~endpoint, + ) + ) - // // React.useEffect0(() => { - // // let handle = (ev: Window.event) => { - // // let json = try { - // // ev.data->JSON.parseExn - // // } catch { - // // | _ => JSON.Encode.null - // // } - // // let dict = json->Utils.getDictFromJson - // // if dict->Dict.get("sendPaymentMethodsResponse")->Option.isSome { - // // paymentMethodsResponse->sendPromiseData("payment_methods") - // // } else if dict->Dict.get("sendCustomerPaymentMethodsResponse")->Option.isSome { - // // customerPaymentMethodsResponse->sendPromiseData("customer_payment_methods") - // // } else if dict->Dict.get("sendSessionTokensResponse")->Option.isSome { - // // sessionTokensResponse->sendPromiseData("session_tokens") - // // } - // // } - // // Window.addEventListener("message", handle) - // // handlePostMessage([("preMountLoaderInitCallback", true->JSON.Encode.bool)]) - // // Some( - // // () => { - // // Window.removeEventListener("message", handle) - // // }, - // // ) - // // }) + let customerPaymentMethodsResponse = React.useMemo0(() => + PaymentHelpers.fetchCustomerPaymentMethodList( + ~clientSecret, + ~publishableKey, + ~optLogger=Some(logger), + ~switchToCustomPod=false, + ~endpoint, + ) + ) + + let sessionTokensResponse = React.useMemo0(() => + PaymentHelpers.fetchSessions( + ~clientSecret, + ~publishableKey, + ~optLogger=Some(logger), + ~switchToCustomPod=false, + ~endpoint, + (), + ) + ) + + let sendPromiseData = (promise, key) => { + open Promise + promise + ->then(res => { + handlePostMessage([("response", res), ("data", key->JSON.Encode.string)]) + switch key { + | "payment_methods" => setPaymentMethodsResponseSent(_ => true) + | "session_tokens" => setSessionTokensResponseSent(_ => true) + | "customer_payment_methods" => setCustomerPaymentMethodsResponseSent(_ => true) + | _ => () + } + resolve() + }) + ->catch(_err => { + handlePostMessage([("response", JSON.Encode.null), ("data", key->JSON.Encode.string)]) + resolve() + }) + ->ignore + } + + let handle = (ev: Window.event) => { + let json = try { + ev.data->JSON.parseExn + } catch { + | _ => JSON.Encode.null + } + let dict = json->Utils.getDictFromJson + if dict->Dict.get("sendPaymentMethodsResponse")->Belt.Option.isSome { + paymentMethodsResponse->sendPromiseData("payment_methods") + } else if dict->Dict.get("sendCustomerPaymentMethodsResponse")->Belt.Option.isSome { + customerPaymentMethodsResponse->sendPromiseData("customer_payment_methods") + } else if dict->Dict.get("sendSessionTokensResponse")->Belt.Option.isSome { + sessionTokensResponse->sendPromiseData("session_tokens") + } + } + + React.useEffect0(() => { + Window.addEventListener("message", handle) + handlePostMessage([("preMountLoaderIframeMountedCallback", true->JSON.Encode.bool)]) + Some( + () => { + Window.removeEventListener("message", handle) + }, + ) + }) + + React.useEffect3(() => { + if ( + paymentMethodsResponseSent && customerPaymentMethodsResponseSent && sessionTokensResponseSent + ) { + handlePostMessage([("preMountLoaderIframeUnMount", true->JSON.Encode.bool)]) + Window.removeEventListener("message", handle) + } + None + }, (paymentMethodsResponseSent, customerPaymentMethodsResponseSent, sessionTokensResponseSent)) React.null } diff --git a/src/Utilities/ApiEndpoint.res b/src/Utilities/ApiEndpoint.res index 0bd259d16..d3ede5855 100644 --- a/src/Utilities/ApiEndpoint.res +++ b/src/Utilities/ApiEndpoint.res @@ -14,7 +14,7 @@ let getApiEndPoint = (~publishableKey="", ~isConfirmCall=false, ()) => { | Some(str) => str | None => let backendEndPoint = isConfirmCall ? GlobalVars.confirmEndPoint : GlobalVars.backendEndPoint - GlobalVars.isProd && testMode ? "https://sandbox.hyperswitch.io" : backendEndPoint + GlobalVars.isProd && testMode ? "https://beta.hyperswitch.io/api" : backendEndPoint } } diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 83937b42d..3ee78e9f1 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -1168,7 +1168,7 @@ let fetchPaymentMethodList = ( }) } -let fetchCustomerDetails = ( +let fetchCustomerPaymentMethodList = ( ~clientSecret, ~publishableKey, ~endpoint, diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index af84cfe38..da69a2211 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1234,3 +1234,14 @@ let makeOneClickHandlerPromise = sdkHandleOneClickConfirmPayment => { } }) } + +let generateRandomString = length => { + let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + let result = ref("") + let charactersLength = characters->String.length + Int.range(0, length)->Array.forEach(_ => { + let charIndex = mod((Math.random() *. 100.0)->Float.toInt, charactersLength) + result := result.contents ++ characters->String.charAt(charIndex) + }) + result.contents +} diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index d093d55a5..1297d5895 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -55,105 +55,135 @@ let make = ( ->Option.flatMap(JSON.Decode.bool) ->Option.getOr(false) - let paymentMethodListPromise = PaymentHelpers.fetchPaymentMethodList( - ~clientSecret, - ~publishableKey, - ~endpoint, - ~switchToCustomPod, - ~logger, - ) - - let customerDetailsPromise = PaymentHelpers.fetchCustomerDetails( - ~clientSecret, - ~publishableKey, - ~endpoint, - ~switchToCustomPod, - ~optLogger=Some(logger), - ) - - let sessionsPromise = PaymentHelpers.fetchSessions( - ~clientSecret, - ~publishableKey, - ~endpoint, - ~switchToCustomPod, - ~optLogger=Some(logger), - (), - ) + let localSelectorString = "hyper-preMountLoader-iframe" + let mountPreMountLoaderIframe = () => { + if ( + Window.querySelector( + `#orca-payment-element-iframeRef-${localSelectorString}`, + )->Js.Nullable.isNullable + ) { + let componentType = "preMountLoader" + let iframeDivHtml = `` + let iframeDiv = Window.createElement("div") + iframeDiv->Window.innerHTML(iframeDivHtml) + Window.body->Window.appendChild(iframeDiv) + } + + let elem = Window.querySelector(`#orca-payment-element-iframeRef-${localSelectorString}`) + elem + } let locale = localOptions->getJsonStringFromDict("locale", "") let loader = localOptions->getJsonStringFromDict("loader", "") let clientSecret = localOptions->getRequiredString("clientSecret", "", ~logger) - let clientSecretReMatch = RegExp.test(`.+_secret_[A-Za-z0-9]+`->RegExp.fromString, clientSecret) + let clientSecretReMatch = Re.test(`.+_secret_[A-Za-z0-9]+`->Re.fromString, clientSecret) + + let preMountLoaderIframeDiv = mountPreMountLoaderIframe() + + let unMountPreMountLoaderIframe = () => { + switch preMountLoaderIframeDiv->Nullable.toOption { + | Some(iframe) => iframe->remove + | None => () + } + } + + let preMountLoaderMountedPromise = Promise.make((resolve, _reject) => { + let preMountLoaderIframeCallback = (ev: Types.event) => { + let json = ev.data->Identity.anyTypeToJson + let dict = json->Utils.getDictFromJson + if dict->Dict.get("preMountLoaderIframeMountedCallback")->Belt.Option.isSome { + resolve(true->JSON.Encode.bool) + } else if dict->Dict.get("preMountLoaderIframeUnMount")->Belt.Option.isSome { + unMountPreMountLoaderIframe() + } + } + addSmartEventListener( + "message", + preMountLoaderIframeCallback, + "onPreMountLoaderIframeCallback", + ) + }) + let fetchPaymentsList = mountedIframeRef => { - open Promise - paymentMethodListPromise - ->then(json => { - let isApplePayPresent = - PaymentMethodsRecord.getPaymentMethodTypeFromList( - ~list=json->getDictFromJson->PaymentMethodsRecord.itemToObjMapper, - ~paymentMethod="wallet", - ~paymentMethodType="apple_pay", - )->Option.isSome - - let isGooglePayPresent = - PaymentMethodsRecord.getPaymentMethodTypeFromList( - ~list=json->getDictFromJson->PaymentMethodsRecord.itemToObjMapper, - ~paymentMethod="wallet", - ~paymentMethodType="google_pay", - )->Option.isSome - - if isApplePayPresent || isGooglePayPresent { - if ( - Window.querySelectorAll(`script[src="https://tpgw.trustpay.eu/js/v1.js"]`)->Array.length === 0 && - Window.querySelectorAll(`script[src="https://test-tpgw.trustpay.eu/js/v1.js"]`)->Array.length === 0 - ) { - let trustPayScriptURL = - publishableKey->String.startsWith("pk_prd_") - ? "https://tpgw.trustpay.eu/js/v1.js" - : "https://test-tpgw.trustpay.eu/js/v1.js" - let trustPayScript = Window.createElement("script") - trustPayScript->Window.elementSrc(trustPayScriptURL) - trustPayScript->Window.elementOnerror(err => { - logInfo(Console.log2("ERROR DURING LOADING TRUSTPAY APPLE PAY", err)) - }) - Window.body->Window.appendChild(trustPayScript) - logger.setLogInfo(~value="TrustPay Script Loaded", ~eventName=TRUSTPAY_SCRIPT, ()) + let handlePaymentMethodsLoaded = (event: Types.event) => { + let json = event.data->Identity.anyTypeToJson + let dict = json->getDictFromJson + let isPaymentMethodsData = dict->Utils.getString("data", "") === "payment_methods" + if isPaymentMethodsData { + let json = dict->Utils.getJsonFromDict("response", JSON.Encode.null) + let isApplePayPresent = + PaymentMethodsRecord.getPaymentMethodTypeFromList( + ~list=json->Utils.getDictFromJson->PaymentMethodsRecord.itemToObjMapper, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + )->Belt.Option.isSome + + let isGooglePayPresent = + PaymentMethodsRecord.getPaymentMethodTypeFromList( + ~list=json->Utils.getDictFromJson->PaymentMethodsRecord.itemToObjMapper, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", + )->Belt.Option.isSome + + if isApplePayPresent || isGooglePayPresent { + if ( + Window.querySelectorAll(`script[src="https://tpgw.trustpay.eu/js/v1.js"]`)->Array.length === 0 && + Window.querySelectorAll(`script[src="https://test-tpgw.trustpay.eu/js/v1.js"]`)->Array.length === 0 + ) { + let trustPayScriptURL = + publishableKey->String.startsWith("pk_prd_") + ? "https://tpgw.trustpay.eu/js/v1.js" + : "https://test-tpgw.trustpay.eu/js/v1.js" + let trustPayScript = Window.createElement("script") + trustPayScript->Window.elementSrc(trustPayScriptURL) + trustPayScript->Window.elementOnerror(err => { + Utils.logInfo(Console.log2("ERROR DURING LOADING TRUSTPAY APPLE PAY", err)) + }) + Window.body->Window.appendChild(trustPayScript) + logger.setLogInfo(~value="TrustPay Script Loaded", ~eventName=TRUSTPAY_SCRIPT, ()) + } + } + let msg = [("paymentMethodList", json)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + } + } + let msg = [("sendPaymentMethodsResponse", true->JSON.Encode.bool)]->Dict.fromArray + addSmartEventListener("message", handlePaymentMethodsLoaded, "onPaymentMethodsLoaded") + preMountLoaderIframeDiv->Window.iframePostMessage(msg) + } + let fetchCustomerPaymentMethods = (mountedIframeRef, disableSaveCards) => { + if !disableSaveCards { + let handleCustomerPaymentMethodsLoaded = (event: Types.event) => { + let json = event.data->Identity.anyTypeToJson + let dict = json->getDictFromJson + let isCustomerPaymentMethodsData = + dict->Utils.getString("data", "") === "customer_payment_methods" + if isCustomerPaymentMethodsData { + let json = dict->Utils.getJsonFromDict("response", JSON.Encode.null) + let msg = [("customerPaymentMethods", json)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) } } - - // setTimeout(() => { - let msg = [("paymentMethodList", json)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - let maskedPayload = json->PaymentHelpers.maskPayload->JSON.stringify - logger.setLogInfo( - ~value="", - ~internalMetadata=maskedPayload, - ~eventName=PAYMENT_METHODS_RESPONSE, - (), + addSmartEventListener( + "message", + handleCustomerPaymentMethodsLoaded, + "onCustomerPaymentMethodsLoaded", ) - // }, 5000)->ignore - json->resolve - }) - ->ignore - } - let fetchCustomerDetails = mountedIframeRef => { - open Promise - customerDetailsPromise - ->then(json => { - // setTimeout(() => { - let msg = [("customerPaymentMethods", json)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - // }, 5000)->ignore - json->resolve - }) - ->catch(_err => { - let dict = - [("customer_payment_methods", []->JSON.Encode.array)]->Dict.fromArray->JSON.Encode.object - let msg = [("customerPaymentMethods", dict)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve(msg->JSON.Encode.object) - }) - ->ignore + } + let msg = + [ + ("sendCustomerPaymentMethodsResponse", !disableSaveCards->JSON.Encode.bool), + ]->Dict.fromArray + preMountLoaderIframeDiv->Window.iframePostMessage(msg) } !clientSecretReMatch @@ -246,7 +276,6 @@ let make = ( ("sdkSessionId", sdkSessionId->JSON.Encode.string), ("blockConfirm", blockConfirm->JSON.Encode.bool), ("switchToCustomPod", switchToCustomPod->JSON.Encode.bool), - ("endpoint", endpoint->JSON.Encode.string), ("sdkHandleOneClickConfirmPayment", sdkHandleOneClickConfirmPayment->JSON.Encode.bool), ("parentURL", "*"->JSON.Encode.string), ("analyticsMetadata", analyticsMetadata), @@ -396,372 +425,391 @@ let make = ( addSmartEventListener("message", handleApplePayMounted, "onApplePayMount") addSmartEventListener("message", handleGooglePayThirdPartyFlow, "onGooglePayThirdParty") + Window.removeEventListener("message", handleApplePayMessages.contents) - sessionsPromise - ->then(json => { - let sessionsArr = - json - ->JSON.Decode.object - ->Option.getOr(Dict.make()) - ->SessionsType.getSessionsTokenJson("session_token") - - let applePayPresent = sessionsArr->Array.find(item => { - let x = - item - ->JSON.Decode.object - ->Option.flatMap( - x => { - x->Dict.get("wallet_name") - }, - ) - ->Option.flatMap(JSON.Decode.string) - ->Option.getOr("") - x === "apple_pay" || x === "applepay" - }) - if !(applePayPresent->Option.isSome) { - let msg = [("applePaySessionObjNotPresent", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - } - let googlePayPresent = sessionsArr->Array.find(item => { - let x = - item - ->JSON.Decode.object - ->Option.flatMap( - x => { - x->Dict.get("wallet_name") - }, - ) - ->Option.flatMap(JSON.Decode.string) - ->Option.getOr("") - x === "google_pay" || x === "googlepay" - }) + let fetchSessionTokens = mountedIframeRef => { + let handleSessionTokensLoaded = (event: Types.event) => { + let json = event.data->Identity.anyTypeToJson + let dict = json->getDictFromJson + let sessionTokensData = dict->Utils.getString("data", "") === "session_tokens" + if sessionTokensData { + let json = dict->Utils.getJsonFromDict("response", JSON.Encode.null) - (json, applePayPresent, googlePayPresent)->resolve - }) - ->then(res => { - let (json, applePayPresent, googlePayPresent) = res - if componentType === "payment" && applePayPresent->Option.isSome { - //do operations here - let processPayment = (token: JSON.t) => { - //let body = PaymentBody.applePayBody(~token) - let msg = [("applePayProcessPayment", token)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - } + { + let sessionsArr = + json + ->JSON.Decode.object + ->Belt.Option.getWithDefault(Dict.make()) + ->SessionsType.getSessionsTokenJson("session_token") + + let applePayPresent = sessionsArr->Array.find(item => { + let x = + item + ->JSON.Decode.object + ->Belt.Option.flatMap(x => { + x->Dict.get("wallet_name") + }) + ->Belt.Option.flatMap(JSON.Decode.string) + ->Belt.Option.getWithDefault("") + x === "apple_pay" || x === "applepay" + }) + if !(applePayPresent->Belt.Option.isSome) { + let msg = + [("applePaySessionObjNotPresent", true->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + } + let googlePayPresent = sessionsArr->Array.find(item => { + let x = + item + ->JSON.Decode.object + ->Belt.Option.flatMap(x => { + x->Dict.get("wallet_name") + }) + ->Belt.Option.flatMap(JSON.Decode.string) + ->Belt.Option.getWithDefault("") + x === "google_pay" || x === "googlepay" + }) - handleApplePayMessages := - ( - (event: Types.event) => { - let json = event.data->anyTypeToJson - let dict = json->getDictFromJson - switch dict->Dict.get("applePayButtonClicked") { - | Some(val) => - if val->JSON.Decode.bool->Option.getOr(false) { - let isDelayedSessionToken = - applePayPresent - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - ->Dict.get("delayed_session_token") - ->Option.getOr(JSON.Encode.null) - ->JSON.Decode.bool - ->Option.getOr(false) - - if isDelayedSessionToken { - logger.setLogInfo( - ~value="Delayed Session Token Flow", - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - (), - ) + (json, applePayPresent, googlePayPresent)->resolve + } + ->then(res => { + let (json, applePayPresent, googlePayPresent) = res + if componentType === "payment" && applePayPresent->Belt.Option.isSome { + //do operations here + let processPayment = (token: JSON.t) => { + //let body = PaymentBody.applePayBody(~token) + let msg = [("applePayProcessPayment", token)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + } - let applePayPresent = - dict - ->Dict.get("applePayPresent") - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - - let connector = - applePayPresent - ->Dict.get("connector") - ->Option.getOr(JSON.Encode.null) - ->JSON.Decode.string - ->Option.getOr("") - - switch connector { - | "trustpay" => - logger.setLogInfo( - ~value="TrustPay Connector Flow", - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - (), - ) - let secrets = - applePayPresent - ->Dict.get("session_token_data") - ->Option.getOr(JSON.Encode.null) - ->JSON.Decode.object - ->Option.getOr(Dict.make()) - ->Dict.get("secrets") - ->Option.getOr(JSON.Encode.null) - - let paymentRequest = - applePayPresent - ->Dict.get("payment_request_data") - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - ->jsonToPaymentRequestDataType - - let payment = - secrets - ->JSON.Decode.object - ->Option.getOr(Dict.make()) - ->Dict.get("payment") - ->Option.getOr(JSON.Encode.null) - ->JSON.Decode.string - ->Option.getOr("") - - try { - let trustpay = trustPayApi(secrets) - trustpay.finishApplePaymentV2(payment, paymentRequest) - ->then(res => { - logger.setLogInfo( - ~value="TrustPay ApplePay Success Response", - ~internalMetadata=res->JSON.stringify, - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - (), - ) - let msg = - [("applePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) - ->catch(err => { - let exceptionMessage = err->formatException->JSON.stringify - logger.setLogInfo( - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - ~value=exceptionMessage, - (), - ) - let msg = - [("applePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }) - ->ignore - } catch { - | exn => { + handleApplePayMessages := + ( + (event: Types.event) => { + let json = event.data->Identity.anyTypeToJson + let dict = json->getDictFromJson + switch dict->Dict.get("applePayButtonClicked") { + | Some(val) => + if val->JSON.Decode.bool->Belt.Option.getWithDefault(false) { + let isDelayedSessionToken = + applePayPresent + ->Belt.Option.flatMap(JSON.Decode.object) + ->Belt.Option.getWithDefault(Dict.make()) + ->Dict.get("delayed_session_token") + ->Belt.Option.getWithDefault(JSON.Encode.null) + ->JSON.Decode.bool + ->Belt.Option.getWithDefault(false) + + if isDelayedSessionToken { logger.setLogInfo( - ~value=exn->formatException->JSON.stringify, + ~value="Delayed Session Token Flow", ~eventName=APPLE_PAY_FLOW, ~paymentMethod="APPLE_PAY", (), ) - let msg = - [("applePaySyncPayment", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) + + let applePayPresent = + dict + ->Dict.get("applePayPresent") + ->Belt.Option.flatMap(JSON.Decode.object) + ->Belt.Option.getWithDefault(Dict.make()) + + let connector = + applePayPresent + ->Dict.get("connector") + ->Belt.Option.getWithDefault(JSON.Encode.null) + ->JSON.Decode.string + ->Belt.Option.getWithDefault("") + + switch connector { + | "trustpay" => + logger.setLogInfo( + ~value="TrustPay Connector Flow", + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + (), + ) + let secrets = + applePayPresent + ->Dict.get("session_token_data") + ->Belt.Option.getWithDefault(JSON.Encode.null) + ->JSON.Decode.object + ->Belt.Option.getWithDefault(Dict.make()) + ->Dict.get("secrets") + ->Belt.Option.getWithDefault(JSON.Encode.null) + + let paymentRequest = + applePayPresent + ->Dict.get("payment_request_data") + ->Belt.Option.flatMap(JSON.Decode.object) + ->Belt.Option.getWithDefault(Dict.make()) + ->ApplePayTypes.jsonToPaymentRequestDataType + + let payment = + secrets + ->JSON.Decode.object + ->Belt.Option.getWithDefault(Dict.make()) + ->Dict.get("payment") + ->Belt.Option.getWithDefault(JSON.Encode.null) + ->JSON.Decode.string + ->Belt.Option.getWithDefault("") + + try { + let trustpay = trustPayApi(secrets) + trustpay.finishApplePaymentV2(payment, paymentRequest) + ->then(res => { + logger.setLogInfo( + ~value="TrustPay ApplePay Success Response", + ~internalMetadata=res->JSON.stringify, + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + (), + ) + let msg = + [ + ("applePaySyncPayment", true->JSON.Encode.bool), + ]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + logger.setLogInfo( + ~value="", + ~eventName=PAYMENT_DATA_FILLED, + ~paymentMethod="APPLE_PAY", + (), + ) + resolve() + }) + ->catch(err => { + let exceptionMessage = + err->Utils.formatException->JSON.stringify + logger.setLogInfo( + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + ~value=exceptionMessage, + (), + ) + let msg = + [ + ("applePaySyncPayment", true->JSON.Encode.bool), + ]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }) + ->ignore + } catch { + | exn => { + logger.setLogInfo( + ~value=exn->Utils.formatException->JSON.stringify, + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + (), + ) + let msg = + [ + ("applePaySyncPayment", true->JSON.Encode.bool), + ]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + } + } + | _ => () + } + } else { + let paymentRequest = + applePayPresent + ->Belt.Option.flatMap(JSON.Decode.object) + ->Belt.Option.getWithDefault(Dict.make()) + ->Dict.get("payment_request_data") + ->Belt.Option.getWithDefault(Dict.make()->JSON.Encode.object) + ->Utils.transformKeys(Utils.CamelCase) + + let ssn = applePaySession(3, paymentRequest) + switch applePaySessionRef.contents->Nullable.toOption { + | Some(session) => + try { + session.abort() + } catch { + | error => Console.log2("Abort fail", error) + } + | None => () + } + + ssn.begin() + applePaySessionRef := ssn->Js.Nullable.return + + ssn.onvalidatemerchant = _event => { + let merchantSession = + applePayPresent + ->Belt.Option.flatMap(JSON.Decode.object) + ->Belt.Option.getWithDefault(Dict.make()) + ->Dict.get("session_token_data") + ->Belt.Option.getWithDefault(Dict.make()->JSON.Encode.object) + ->Utils.transformKeys(Utils.CamelCase) + ssn.completeMerchantValidation(merchantSession) + } + + ssn.onpaymentauthorized = event => { + ssn.completePayment( + {"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson, + ) + applePaySessionRef := Nullable.null + processPayment(event.payment.token) + } + ssn.oncancel = _ev => { + let msg = + [("showApplePayButton", true->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + applePaySessionRef := Nullable.null + Utils.logInfo(Console.log("Apple Pay payment cancelled")) + } } + } else { + () } - | _ => () + | None => () } - } else { - try { - let paymentRequest = - applePayPresent - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - ->Dict.get("payment_request_data") - ->Option.getOr(Dict.make()->JSON.Encode.object) - ->transformKeys(CamelCase) - - let ssn = applePaySession(3, paymentRequest) - switch applePaySessionRef.contents->Nullable.toOption { - | Some(session) => - try { - session.abort() - } catch { - | error => Console.log2("Abort fail", error) - } - | None => () - } + } + ) - applePaySessionRef := ssn->Nullable.make + addSmartEventListener( + "message", + handleApplePayMessages.contents, + "onApplePayMessages", + ) + } + if componentType === "payment" && googlePayPresent->Belt.Option.isSome { + let dict = json->getDictFromJson + let sessionObj = SessionsType.itemToObjMapper(dict, Others) + let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) - ssn.onvalidatemerchant = _event => { - let merchantSession = - applePayPresent - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - ->Dict.get("session_token_data") - ->Option.getOr(Dict.make()->JSON.Encode.object) - ->transformKeys(CamelCase) - ssn.completeMerchantValidation(merchantSession) - } + let tokenObj = switch gPayToken { + | OtherTokenOptional(optToken) => optToken + | _ => Some(SessionsType.defaultToken) + } - ssn.onpaymentauthorized = event => { - ssn.completePayment({"status": ssn.\"STATUS_SUCCESS"}->anyTypeToJson) - applePaySessionRef := Nullable.null - processPayment(event.payment.token) - } - ssn.oncancel = _ev => { - let msg = - [("showApplePayButton", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - applePaySessionRef := Nullable.null - logInfo(Console.log("Apple Pay payment cancelled")) - } + let gpayobj = switch tokenObj { + | Some(val) => val + | _ => SessionsType.defaultToken + } - ssn.begin() - } catch { - | exn => { - logger.setLogInfo( - ~value=exn->formatException->JSON.stringify, - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - (), - ) - logInfo(Console.error2("Apple Pay Error", exn)) - - let msg = - [("showApplePayButton", true->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - applePaySessionRef := Nullable.null - } - } - } - } else { - () - } - | None => () + let baseRequest = { + "apiVersion": 2, + "apiVersionMinor": 0, } - } - ) + let paymentDataRequest = GooglePayType.assign2( + Dict.make()->JSON.Encode.object, + baseRequest->Identity.anyTypeToJson, + ) - addSmartEventListener("message", handleApplePayMessages.contents, "onApplePayMessages") - } - if componentType === "payment" && googlePayPresent->Option.isSome { - let dict = json->getDictFromJson - let sessionObj = SessionsType.itemToObjMapper(dict, Others) - let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) + let payRequest = GooglePayType.assign( + Dict.make()->JSON.Encode.object, + baseRequest->Identity.anyTypeToJson, + { + "allowedPaymentMethods": gpayobj.allowed_payment_methods->arrayJsonToCamelCase, + }->Identity.anyTypeToJson, + ) + paymentDataRequest.allowedPaymentMethods = + gpayobj.allowed_payment_methods->arrayJsonToCamelCase + paymentDataRequest.transactionInfo = + gpayobj.transaction_info->transformKeys(CamelCase) + paymentDataRequest.merchantInfo = gpayobj.merchant_info->transformKeys(CamelCase) + try { + let gPayClient = GooglePayType.google( + { + "environment": publishableKey->String.startsWith("pk_prd_") + ? "PRODUCTION" + : "TEST", + }->Identity.anyTypeToJson, + ) - let tokenObj = switch gPayToken { - | OtherTokenOptional(optToken) => optToken - | _ => Some(SessionsType.defaultToken) - } + gPayClient.isReadyToPay(payRequest) + ->then(res => { + let dict = res->getDictFromJson + let isReadyToPay = getBool(dict, "result", false) + let msg = [("isReadyToPay", isReadyToPay->JSON.Encode.bool)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }) + ->catch(err => { + logger.setLogInfo( + ~value=err->Identity.anyTypeToJson->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + resolve() + }) + ->ignore - let gpayobj = switch tokenObj { - | Some(val) => val - | _ => SessionsType.defaultToken - } + let handleGooglePayMessages = (event: Types.event) => { + let evJson = event.data->anyTypeToJson + let gpayClicked = + evJson + ->getOptionalJsonFromJson("GpayClicked") + ->getBoolFromJson(false) + + if gpayClicked { + setTimeout(() => { + gPayClient.loadPaymentData(paymentDataRequest->anyTypeToJson) + ->then( + json => { + let msg = [("gpayResponse", json->anyTypeToJson)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }, + ) + ->catch( + err => { + logger.setLogInfo( + ~value=err->anyTypeToJson->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) - let baseRequest = { - "apiVersion": 2, - "apiVersionMinor": 0, - } - let paymentDataRequest = GooglePayType.assign2( - Dict.make()->JSON.Encode.object, - baseRequest->anyTypeToJson, - ) - - let payRequest = GooglePayType.assign( - Dict.make()->JSON.Encode.object, - baseRequest->anyTypeToJson, - { - "allowedPaymentMethods": gpayobj.allowed_payment_methods->arrayJsonToCamelCase, - }->anyTypeToJson, - ) - paymentDataRequest.allowedPaymentMethods = - gpayobj.allowed_payment_methods->arrayJsonToCamelCase - paymentDataRequest.transactionInfo = gpayobj.transaction_info->transformKeys(CamelCase) - paymentDataRequest.merchantInfo = gpayobj.merchant_info->transformKeys(CamelCase) - try { - let gPayClient = GooglePayType.google( - { - "environment": publishableKey->String.startsWith("pk_prd_") - ? "PRODUCTION" - : "TEST", - }->anyTypeToJson, - ) + let msg = [("gpayError", err->anyTypeToJson)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + resolve() + }, + ) + ->ignore + }, 0)->ignore + } + } + addSmartEventListener("message", handleGooglePayMessages, "onGooglePayMessages") + } catch { + | _ => Console.log("Error loading Gpay") + } + } - gPayClient.isReadyToPay(payRequest) - ->then(res => { - let dict = res->getDictFromJson - let isReadyToPay = getBool(dict, "result", false) - let msg = [("isReadyToPay", isReadyToPay->JSON.Encode.bool)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() + json->resolve }) - ->catch(err => { - logger.setLogInfo( - ~value=err->anyTypeToJson->JSON.stringify, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - ~logType=DEBUG, - (), - ) - resolve() + ->then(json => { + let msg = [("sessions", json)]->Dict.fromArray + mountedIframeRef->Window.iframePostMessage(msg) + json->resolve }) ->ignore - - let handleGooglePayMessages = (event: Types.event) => { - let evJson = event.data->anyTypeToJson - let gpayClicked = - evJson - ->getOptionalJsonFromJson("GpayClicked") - ->getBoolFromJson(false) - - if gpayClicked { - setTimeout(() => { - gPayClient.loadPaymentData(paymentDataRequest->anyTypeToJson) - ->then( - json => { - let msg = [("gpayResponse", json->anyTypeToJson)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }, - ) - ->catch( - err => { - logger.setLogInfo( - ~value=err->anyTypeToJson->JSON.stringify, - ~eventName=GOOGLE_PAY_FLOW, - ~paymentMethod="GOOGLE_PAY", - ~logType=DEBUG, - (), - ) - - let msg = [("gpayError", err->anyTypeToJson)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - resolve() - }, - ) - ->ignore - }, 0)->ignore - } - } - addSmartEventListener("message", handleGooglePayMessages, "onGooglePayMessages") - } catch { - | _ => Console.log("Error loading Gpay") } } - - json->resolve - }) - ->then(json => { - let msg = [("sessions", json)]->Dict.fromArray - mountedIframeRef->Window.iframePostMessage(msg) - json->resolve + let msg = [("sendSessionTokensResponse", true->JSON.Encode.bool)]->Dict.fromArray + addSmartEventListener("message", handleSessionTokensLoaded, "onSessionTokensLoaded") + preMountLoaderIframeDiv->Window.iframePostMessage(msg) + } + preMountLoaderMountedPromise + ->then(_ => { + fetchPaymentsList(mountedIframeRef) + if ( + newOptions + ->getDictFromJson + ->getBool("displaySavedPaymentMethods", true) + ) { + fetchCustomerPaymentMethods(mountedIframeRef, false) + } + fetchSessionTokens(mountedIframeRef) + mountedIframeRef->Window.iframePostMessage(message) + resolve() }) ->ignore - if ( - newOptions - ->getDictFromJson - ->getBool("displaySavedPaymentMethods", true) - ) { - fetchCustomerDetails(mountedIframeRef) - } - fetchPaymentsList(mountedIframeRef) - mountedIframeRef->Window.iframePostMessage(message) } let paymentElement = LoaderPaymentElement.make( diff --git a/src/orca-loader/Hyper.res b/src/orca-loader/Hyper.res index 1a8a955b8..b0388e068 100644 --- a/src/orca-loader/Hyper.res +++ b/src/orca-loader/Hyper.res @@ -85,7 +85,8 @@ let make = (publishableKey, options: option, analyticsInfo: optionOption.flatMap(JSON.Decode.object)->Option.getOr(Dict.make()) - let sessionID = analyticsInfoDict->getString("sessionID", "") + let sessionID = + analyticsInfoDict->getString("sessionID", "hyp_" ++ Utils.generateRandomString(8)) let sdkTimestamp = analyticsInfoDict->getString("timeStamp", Date.now()->Belt.Float.toString) let logger = OrcaLogger.make( ~sessionId=sessionID, diff --git a/src/orca-loader/PaymentSessionMethods.res b/src/orca-loader/PaymentSessionMethods.res index 8e5ab84a0..bdf2693ac 100644 --- a/src/orca-loader/PaymentSessionMethods.res +++ b/src/orca-loader/PaymentSessionMethods.res @@ -8,7 +8,7 @@ let getCustomerSavedPaymentMethods = ( ~switchToCustomPod, ) => { open Promise - PaymentHelpers.fetchCustomerDetails( + PaymentHelpers.fetchCustomerPaymentMethodList( ~clientSecret, ~publishableKey, ~endpoint, diff --git a/webpack.common.js b/webpack.common.js index c6602f654..f4d6b43b9 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -5,8 +5,7 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CopyPlugin = require("copy-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin"); -const BundleAnalyzerPlugin = - require("webpack-bundle-analyzer").BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const { sentryWebpackPlugin } = require("@sentry/webpack-plugin"); const sdkEnv = process.env.sdkEnv; @@ -18,8 +17,7 @@ let repoVersion = require("./package.json").version; let majorVersion = "v" + repoVersion.split(".")[0]; let repoName = require("./package.json").name; -let repoPublicPath = - sdkEnv === "local" ? "" : `/${repoVersion}/${majorVersion}`; +let repoPublicPath = sdkEnv === "local" ? "" : `/${repoVersion}/${majorVersion}`; let sdkUrl; @@ -40,12 +38,12 @@ let backendEndPoint; if (envBackendUrl === undefined) { backendEndPoint = sdkEnv === "prod" - ? "https://api.hyperswitch.io" + ? "https://checkout.hyperswitch.io/api" : sdkEnv === "sandbox" - ? "https://sandbox.hyperswitch.io" + ? "https://beta.hyperswitch.io/api" : sdkEnv === "integ" ? "https://integ-api.hyperswitch.io" - : "https://sandbox.hyperswitch.io"; + : "https://beta.hyperswitch.io/api"; } else { backendEndPoint = envBackendUrl; } @@ -54,12 +52,12 @@ let confirmEndPoint; if (envBackendUrl === undefined) { confirmEndPoint = sdkEnv === "prod" - ? "https://api.hyperswitch.io" + ? "https://checkout.hyperswitch.io/api" : sdkEnv === "sandbox" - ? "https://sandbox.hyperswitch.io" + ? "https://beta.hyperswitch.io/api" : sdkEnv === "integ" ? "https://integ-api.hyperswitch.io" - : "https://sandbox.hyperswitch.io"; + : "https://beta.hyperswitch.io/api"; } else { confirmEndPoint = envBackendUrl; } diff --git a/webpack.dev.js b/webpack.dev.js index 8801754f3..ce80a9ad4 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -6,12 +6,12 @@ const sdkEnv = process.env.sdkEnv; let backendEndPoint = sdkEnv === "prod" - ? "https://api.hyperswitch.io/payments" + ? "https://checkout.hyperswitch.io/api/payments" : sdkEnv === "sandbox" - ? "https://sandbox.hyperswitch.io/payments" + ? "https://beta.hyperswitch.io/api/payments" : sdkEnv === "integ" ? "https://integ-api.hyperswitch.io/payments" - : "https://sandbox.hyperswitch.io/payments"; + : "https://beta.hyperswitch.io/api/payments"; let devServer = { contentBase: path.join(__dirname, "dist"), @@ -19,7 +19,7 @@ let devServer = { port: 9050, historyApiFallback: true, proxy: { - "/payments": { + "/api/payments": { target: backendEndPoint, changeOrigin: true, secure: true,