From ea6a6d9235fb6f1fe7dddfc1f58168e3d071d38f Mon Sep 17 00:00:00 2001 From: Arush Date: Mon, 24 Jun 2024 05:28:51 +0530 Subject: [PATCH] feat: headless applepay and googlepay --- public/icons/orca.svg | 63 +-- src/Components/SavedMethods.res | 96 +++- src/PaymentElement.res | 17 +- src/Payments/ApplePay.res | 120 +---- src/Payments/GPay.res | 112 +---- src/Types/PaymentType.res | 21 +- src/Utilities/ApplePayHelpers.res | 218 +++++++++ src/Utilities/DynamicFieldsUtils.res | 59 ++- src/Utilities/GooglePayHelpers.res | 146 ++++++ src/Utilities/PaymentBody.res | 75 +-- src/Utilities/PaymentHelpers.res | 85 ++++ src/Utilities/PaymentUtils.res | 53 +++ src/Utilities/Utils.res | 12 + src/orca-loader/Elements.res | 79 +--- src/orca-loader/PaymentSessionMethods.res | 541 ++++++++++++++++------ 15 files changed, 1194 insertions(+), 503 deletions(-) create mode 100644 src/Utilities/ApplePayHelpers.res create mode 100644 src/Utilities/GooglePayHelpers.res diff --git a/public/icons/orca.svg b/public/icons/orca.svg index 122a18f8c..5a78ee6d1 100644 --- a/public/icons/orca.svg +++ b/public/icons/orca.svg @@ -2204,55 +2204,22 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL fill="#434343" /> - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - { open CardUtils open Utils @@ -20,9 +21,21 @@ let make = ( loggerState.setLogError(~value=message, ~eventName=INVALID_FORMAT, ()) } let (isSaveCardsChecked, setIsSaveCardsChecked) = React.useState(_ => false) - let {displaySavedPaymentMethodsCheckbox} = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) + let {displaySavedPaymentMethodsCheckbox, readOnly} = Recoil.useRecoilValueFromAtom( + RecoilAtoms.optionAtom, + ) let isGuestCustomer = useIsGuestCustomer() + let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + let url = RescriptReactRouter.useUrl() + let componentName = CardUtils.getQueryParamsDictforKey(url.search, "componentName") + + let dict = sessions->Utils.getDictFromJson + let sessionObj = React.useMemo(() => SessionsType.itemToObjMapper(dict, Others), [dict]) + + let isApplePayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isApplePayReady) + let isGPayReady = Recoil.useRecoilValueFromAtom(RecoilAtoms.isGooglePayReady) + let intent = PaymentHelpers.usePaymentIntent(Some(loggerState), Card) let savedCardlength = savedMethods->Array.length let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) @@ -46,6 +59,13 @@ let make = ( let bottomElement = { savedMethods + ->Array.filter(savedMethod => { + switch savedMethod.paymentMethodType { + | Some("apple_pay") => isApplePayReady + | Some("google_pay") => isGPayReady + | _ => true + } + }) ->Array.mapWithIndex((obj, i) => { let brandIcon = switch obj.paymentMethod { | "wallet" => getWalletBrandIcon(obj) @@ -101,6 +121,10 @@ let make = ( useHandlePostMessages(~complete, ~empty, ~paymentType, ~savedMethod=true) + GooglePayHelpers.useHandleGooglePayResponse(~connectors=[], ~intent) + + ApplePayHelpers.useHandleApplePayResponse(~connectors=[], ~intent) + let submitCallback = React.useCallback((ev: Window.event) => { let json = ev.data->JSON.parseExn let confirm = json->getDictFromJson->ConfirmType.itemToObjMapper @@ -137,16 +161,66 @@ let make = ( (!isCardPaymentMethod || isCardPaymentMethodValid) && confirm.confirmTimestamp >= confirm.readyTimestamp ) { - intent( - ~bodyArr=savedPaymentMethodBody - ->getJsonFromArrayOfJson - ->flattenObject(true) - ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) - ->getArrayOfTupleFromDict, - ~confirmParam=confirm.confirmParams, - ~handleUserError=false, - (), - ) + switch customerMethod.paymentMethodType { + | Some("google_pay") => { + let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) + switch gPayToken { + | OtherTokenOptional(optToken) => + GooglePayHelpers.handleGooglePayClicked( + ~sessionObj=optToken, + ~componentName, + ~iframeId, + ~readOnly, + ) + | _ => + // TODO - To be replaced with proper error message + intent( + ~bodyArr=savedPaymentMethodBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict, + ~confirmParam=confirm.confirmParams, + ~handleUserError=false, + (), + ) + } + } + | Some("apple_pay") => { + let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject) + let applePayToken = SessionsType.getPaymentSessionObj( + applePaySessionObj.sessionsToken, + ApplePay, + ) + switch applePayToken { + | ApplePayTokenOptional(optToken) => + ApplePayHelpers.handleApplePayButtonClicked(~sessionObj=optToken, ~componentName) + | _ => + // TODO - To be replaced with proper error message + intent( + ~bodyArr=savedPaymentMethodBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict, + ~confirmParam=confirm.confirmParams, + ~handleUserError=false, + (), + ) + } + } + | _ => + intent( + ~bodyArr=savedPaymentMethodBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict, + ~confirmParam=confirm.confirmParams, + ~handleUserError=false, + (), + ) + } } else { if isUnknownPaymentMethod || confirm.confirmTimestamp < confirm.readyTimestamp { setUserError(localeString.selectPaymentMethodText) diff --git a/src/PaymentElement.res b/src/PaymentElement.res index c43555f6e..648932116 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -53,12 +53,13 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod } | (_, LoadingSavedCards) => () | (_, LoadedSavedCards(savedPaymentMethods, isGuestCustomer)) => { + let displayDefaultSavedPaymentIcon = optionAtomValue.displayDefaultSavedPaymentIcon let sortSavedPaymentMethods = (a, b) => { let defaultCompareVal = compareLogic( Date.fromString(a.lastUsedAt), Date.fromString(b.lastUsedAt), ) - if optionAtomValue.displayDefaultSavedPaymentIcon { + if displayDefaultSavedPaymentIcon { if a.defaultPaymentMethodSet { -1. } else if b.defaultPaymentMethodSet { @@ -74,7 +75,15 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod let finalSavedPaymentMethods = savedPaymentMethods->Array.copy finalSavedPaymentMethods->Array.sort(sortSavedPaymentMethods) - setSavedMethods(_ => finalSavedPaymentMethods) + let paymentOrder = paymentMethodOrder->getOptionalArr->removeDuplicate + + let sortSavedMethodsBasedOnPriority = + finalSavedPaymentMethods->PaymentUtils.sortCustomerMethodsBasedOnPriority( + paymentOrder, + ~displayDefaultSavedPaymentIcon, + ) + + setSavedMethods(_ => sortSavedMethodsBasedOnPriority) setLoadSavedCards(_ => finalSavedPaymentMethods->Array.length == 0 ? NoResult(isGuestCustomer) @@ -89,7 +98,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod } None - }, (customerPaymentMethods, displaySavedPaymentMethods)) + }, (customerPaymentMethods, displaySavedPaymentMethods, optionAtomValue)) React.useEffect(() => { let defaultSelectedPaymentMethod = optionAtomValue.displayDefaultSavedPaymentIcon @@ -335,7 +344,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod - + Array.length > 0 || walletOptions->Array.length > 0) && diff --git a/src/Payments/ApplePay.res b/src/Payments/ApplePay.res index b677684ff..93763960e 100644 --- a/src/Payments/ApplePay.res +++ b/src/Payments/ApplePay.res @@ -19,7 +19,6 @@ let make = (~sessionObj: option) => { let isApplePaySDKFlow = sessionObj->Option.isSome let areOneClickWalletsRendered = Recoil.useSetRecoilState(RecoilAtoms.areOneClickWalletsRendered) let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) - let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) let applePayPaymentMethodType = React.useMemo(() => { switch PaymentMethodsRecord.getPaymentMethodTypeFromList( @@ -49,27 +48,6 @@ let make = (~sessionObj: option) => { let isGuestCustomer = UtilityHooks.useIsGuestCustomer() - PaymentUtils.useStatesJson(setStatesJson) - - let processPayment = (bodyArr, ~isThirdPartyFlow=false, ()) => { - let requestBody = PaymentUtils.appendedCustomerAcceptance( - ~isGuestCustomer, - ~paymentType=paymentMethodListValue.payment_type, - ~body=bodyArr, - ) - - intent( - ~bodyArr=requestBody, - ~confirmParam={ - return_url: options.wallets.walletReturnUrl, - publishableKey, - }, - ~handleUserError=true, - ~isThirdPartyFlow, - (), - ) - } - let syncPayment = () => { sync( ~confirmParam={ @@ -242,21 +220,28 @@ let make = (~sessionObj: option) => { if isDelayedSessionToken { setShowApplePayLoader(_ => true) let bodyDict = PaymentBody.applePayThirdPartySdkBody(~connectors) - processPayment(bodyDict, ~isThirdPartyFlow=true, ()) - } else { - let paymentRequest = ApplePayTypes.getPaymentRequestFromSession( - ~sessionObj, - ~componentName, + ApplePayHelpers.processPayment( + ~bodyArr=bodyDict, + ~isThirdPartyFlow=true, + ~isGuestCustomer, + ~paymentMethodListValue, + ~intent, + ~options, + ~publishableKey, ) - let message = [ - ("applePayButtonClicked", true->JSON.Encode.bool), - ("applePayPaymentRequest", paymentRequest), - ] - handlePostMessage(message) + } else { + ApplePayHelpers.handleApplePayButtonClicked(~sessionObj, ~componentName) } } else { let bodyDict = PaymentBody.applePayRedirectBody(~connectors) - processPayment(bodyDict, ()) + ApplePayHelpers.processPayment( + ~bodyArr=bodyDict, + ~isGuestCustomer, + ~paymentMethodListValue, + ~intent, + ~options, + ~publishableKey, + ) } } else { setApplePayClicked(_ => false) @@ -266,71 +251,14 @@ let make = (~sessionObj: option) => { ->ignore } - let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( - ~paymentMethodListValue, - ~paymentMethod="wallet", - ~paymentMethodType="apple_pay", + ApplePayHelpers.useHandleApplePayResponse( + ~connectors, + ~intent, + ~setApplePayClicked, + ~syncPayment, + ~isInvokeSDKFlow, ) - React.useEffect(() => { - let handleApplePayMessages = (ev: Window.event) => { - let json = try { - ev.data->JSON.parseExn - } catch { - | _ => Dict.make()->JSON.Encode.object - } - - try { - let dict = json->getDictFromJson - if dict->Dict.get("applePayProcessPayment")->Option.isSome { - let token = - dict->Dict.get("applePayProcessPayment")->Option.getOr(Dict.make()->JSON.Encode.object) - - let billingContact = - dict - ->getDictFromDict("applePayBillingContact") - ->ApplePayTypes.billingContactItemToObjMapper - - let shippingContact = - dict - ->getDictFromDict("applePayShippingContact") - ->ApplePayTypes.shippingContactItemToObjMapper - - let requiredFieldsBody = DynamicFieldsUtils.getApplePayRequiredFields( - ~billingContact, - ~shippingContact, - ~paymentMethodTypes, - ~statesList=stateJson, - ) - - let bodyDict = PaymentBody.applePayBody(~token, ~connectors) - - let applePayBody = - bodyDict - ->getJsonFromArrayOfJson - ->flattenObject(true) - ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) - ->getArrayOfTupleFromDict - - processPayment(applePayBody, ()) - } else if dict->Dict.get("showApplePayButton")->Option.isSome { - setApplePayClicked(_ => false) - } else if dict->Dict.get("applePaySyncPayment")->Option.isSome { - syncPayment() - } - } catch { - | _ => logInfo(Console.log("Error in parsing Apple Pay Data")) - } - } - Window.addEventListener("message", handleApplePayMessages) - Some( - () => { - handlePostMessage([("applePaySessionAbort", true->JSON.Encode.bool)]) - Window.removeEventListener("message", handleApplePayMessages) - }, - ) - }, (isInvokeSDKFlow, processPayment, stateJson)) - React.useEffect(() => { if ( (isInvokeSDKFlow || paymentExperience == PaymentMethodsRecord.RedirectToURL) && diff --git a/src/Payments/GPay.res b/src/Payments/GPay.res index 997a4cfc7..cd33c5557 100644 --- a/src/Payments/GPay.res +++ b/src/Payments/GPay.res @@ -59,89 +59,7 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti ->Option.getOr(false) }, [thirdPartySessionObj]) - let processPayment = (body: array<(string, JSON.t)>, ~isThirdPartyFlow=false, ()) => { - intent( - ~bodyArr=body, - ~confirmParam={ - return_url: options.wallets.walletReturnUrl, - publishableKey, - }, - ~handleUserError=true, - ~isThirdPartyFlow, - (), - ) - } - - let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( - ~paymentMethodListValue, - ~paymentMethod="wallet", - ~paymentMethodType="google_pay", - ) - - let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) - - PaymentUtils.useStatesJson(setStatesJson) - - React.useEffect(() => { - let handle = (ev: Window.event) => { - let json = try { - ev.data->JSON.parseExn - } catch { - | _ => Dict.make()->JSON.Encode.object - } - let dict = json->getDictFromJson - if dict->Dict.get("gpayResponse")->Option.isSome { - let metadata = dict->getJsonObjectFromDict("gpayResponse") - let obj = metadata->getDictFromJson->itemToObjMapper - let gPayBody = PaymentUtils.appendedCustomerAcceptance( - ~isGuestCustomer, - ~paymentType=paymentMethodListValue.payment_type, - ~body=PaymentBody.gpayBody(~payObj=obj, ~connectors), - ) - - let billingContact = - obj.paymentMethodData.info - ->getDictFromJson - ->getJsonObjectFromDict("billingAddress") - ->getDictFromJson - ->billingContactItemToObjMapper - - let shippingContact = - metadata - ->getDictFromJson - ->getJsonObjectFromDict("shippingAddress") - ->getDictFromJson - ->billingContactItemToObjMapper - - let email = - metadata - ->getDictFromJson - ->getString("email", "") - - let requiredFieldsBody = DynamicFieldsUtils.getGooglePayRequiredFields( - ~billingContact, - ~shippingContact, - ~paymentMethodTypes, - ~statesList=stateJson, - ~email, - ) - - let body = { - gPayBody - ->getJsonFromArrayOfJson - ->flattenObject(true) - ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) - ->getArrayOfTupleFromDict - } - processPayment(body, ()) - } - if dict->Dict.get("gpayError")->Option.isSome { - handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) - } - } - Window.addEventListener("message", handle) - Some(() => {Window.removeEventListener("message", handle)}) - }, (paymentMethodTypes, stateJson)) + GooglePayHelpers.useHandleGooglePayResponse(~connectors, ~intent) let (_, buttonType, _) = options.wallets.style.type_ let (_, heightType, _, _) = options.wallets.style.height @@ -183,24 +101,24 @@ let make = (~sessionObj: option, ~thirdPartySessionObj: opti ("iframeId", iframeId->JSON.Encode.string), ]) let bodyDict = PaymentBody.gPayThirdPartySdkBody(~connectors) - processPayment(bodyDict, ~isThirdPartyFlow=true, ()) + GooglePayHelpers.processPayment( + ~body=bodyDict, + ~isThirdPartyFlow=true, + ~intent, + ~options, + ~publishableKey, + ) } else { - let paymentDataRequest = getPaymentDataFromSession(~sessionObj, ~componentName) - handlePostMessage([ - ("fullscreen", true->JSON.Encode.bool), - ("param", "paymentloader"->JSON.Encode.string), - ("iframeId", iframeId->JSON.Encode.string), - ]) - if !options.readOnly { - handlePostMessage([ - ("GpayClicked", true->JSON.Encode.bool), - ("GpayPaymentDataRequest", paymentDataRequest->Identity.anyTypeToJson), - ]) - } + GooglePayHelpers.handleGooglePayClicked( + ~sessionObj, + ~componentName, + ~iframeId, + ~readOnly=options.readOnly, + ) } } else { let bodyDict = PaymentBody.gpayRedirectBody(~connectors) - processPayment(bodyDict, ()) + GooglePayHelpers.processPayment(~body=bodyDict, ~intent, ~options, ~publishableKey) } } resolve() diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index d41977b60..5f91520e3 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -849,13 +849,7 @@ let getPaymentMethodType = dict => { dict->Dict.get("payment_method_type")->Option.flatMap(JSON.Decode.string) } -let createCustomerObjArr = dict => { - let customerDict = - dict - ->Dict.get("customerPaymentMethods") - ->Option.flatMap(JSON.Decode.object) - ->Option.getOr(Dict.make()) - +let itemToCustomerObjMapper = customerDict => { let customerArr = customerDict ->Dict.get("customer_payment_methods") @@ -876,7 +870,7 @@ let createCustomerObjArr = dict => { paymentToken: getString(dict, "payment_token", ""), customerId: getString(dict, "customer_id", ""), paymentMethod: getString(dict, "payment_method", ""), - paymentMethodIssuer: Some(getString(dict, "payment_method_issuer", "")), + paymentMethodIssuer: getOptionString(dict, "payment_method_issuer"), card: getCardDetails(dict, "card"), paymentMethodType: getPaymentMethodType(dict), defaultPaymentMethodSet: getBool(dict, "default_payment_method_set", false), @@ -884,6 +878,17 @@ let createCustomerObjArr = dict => { lastUsedAt: getString(dict, "last_used_at", ""), } }) + + (customerPaymentMethods, isGuestCustomer) +} + +let createCustomerObjArr = dict => { + let customerDict = + dict + ->Dict.get("customerPaymentMethods") + ->Option.flatMap(JSON.Decode.object) + ->Option.getOr(Dict.make()) + let (customerPaymentMethods, isGuestCustomer) = customerDict->itemToCustomerObjMapper LoadedSavedCards(customerPaymentMethods, isGuestCustomer) } diff --git a/src/Utilities/ApplePayHelpers.res b/src/Utilities/ApplePayHelpers.res new file mode 100644 index 000000000..24f482439 --- /dev/null +++ b/src/Utilities/ApplePayHelpers.res @@ -0,0 +1,218 @@ +open ApplePayTypes +open Utils + +let processPayment = ( + ~bodyArr, + ~isThirdPartyFlow=false, + ~isGuestCustomer, + ~paymentMethodListValue=PaymentMethodsRecord.defaultList, + ~intent: PaymentHelpers.paymentIntent, + ~options: PaymentType.options, + ~publishableKey, +) => { + let requestBody = PaymentUtils.appendedCustomerAcceptance( + ~isGuestCustomer, + ~paymentType=paymentMethodListValue.payment_type, + ~body=bodyArr, + ) + + intent( + ~bodyArr=requestBody, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=true, + ~isThirdPartyFlow, + (), + ) +} + +let getApplePayFromResponse = ( + ~token, + ~billingContactDict, + ~shippingContactDict, + ~requiredFields=[], + ~stateJson, + ~connectors, + ~isPaymentSession=false, +) => { + let billingContact = billingContactDict->ApplePayTypes.billingContactItemToObjMapper + + let shippingContact = shippingContactDict->ApplePayTypes.shippingContactItemToObjMapper + + let requiredFieldsBody = if isPaymentSession { + DynamicFieldsUtils.getApplePayRequiredFields( + ~billingContact, + ~shippingContact, + ~statesList=stateJson, + ) + } else { + DynamicFieldsUtils.getApplePayRequiredFields( + ~billingContact, + ~shippingContact, + ~requiredFields, + ~statesList=stateJson, + ) + } + + let bodyDict = PaymentBody.applePayBody(~token, ~connectors) + + bodyDict + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict +} + +let startApplePaySession = ( + ~paymentRequest, + ~applePaySessionRef, + ~applePayPresent, + ~logger: OrcaLogger.loggerMake, + ~applePayEvent: option=None, + ~callBackFunc, + ~resolvePromise: option unit>=None, +) => { + 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->Js.Nullable.return + + ssn.onvalidatemerchant = _event => { + let merchantSession = + applePayPresent + ->Belt.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) + } + + ssn.onpaymentauthorized = event => { + ssn.completePayment({"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson) + applePaySessionRef := Nullable.null + let value = "Payment Data Filled: New Payment Method" + logger.setLogInfo(~value, ~eventName=PAYMENT_DATA_FILLED, ~paymentMethod="APPLE_PAY", ()) + + let payment = event.payment + payment->callBackFunc + } + ssn.oncancel = _ev => { + applePaySessionRef := Nullable.null + logInfo(Console.log("Apple Pay Payment Cancelled")) + logger.setLogInfo( + ~value="Apple Pay Payment Cancelled", + ~eventName=APPLE_PAY_FLOW, + ~paymentMethod="APPLE_PAY", + (), + ) + switch (applePayEvent, resolvePromise) { + | (Some(applePayEvent), _) => { + let msg = [("showApplePayButton", true->JSON.Encode.bool)]->Dict.fromArray + applePayEvent.source->Window.sendPostMessage(msg) + } + | (_, Some(resolvePromise)) => + handleFailureResponse( + ~message="ApplePay Session Cancelled", + ~errorType="apple_pay", + )->resolvePromise + | _ => () + } + } + ssn.begin() +} + +let useHandleApplePayResponse = ( + ~connectors, + ~intent, + ~setApplePayClicked=_ => (), + ~syncPayment=() => (), + ~isInvokeSDKFlow=true, +) => { + let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) + let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) + + let isGuestCustomer = UtilityHooks.useIsGuestCustomer() + + PaymentUtils.useStatesJson(setStatesJson) + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="apple_pay", + ) + + React.useEffect(() => { + let handleApplePayMessages = (ev: Window.event) => { + let json = try { + ev.data->JSON.parseExn + } catch { + | _ => Dict.make()->JSON.Encode.object + } + + try { + let dict = json->getDictFromJson + if dict->Dict.get("applePayProcessPayment")->Option.isSome { + let token = + dict->Dict.get("applePayProcessPayment")->Option.getOr(Dict.make()->JSON.Encode.object) + + let billingContactDict = dict->getDictFromDict("applePayBillingContact") + let shippingContactDict = dict->getDictFromDict("applePayShippingContact") + + let applePayBody = getApplePayFromResponse( + ~token, + ~billingContactDict, + ~shippingContactDict, + ~requiredFields=paymentMethodTypes.required_fields, + ~stateJson, + ~connectors, + ) + + processPayment( + ~bodyArr=applePayBody, + ~isThirdPartyFlow=false, + ~isGuestCustomer, + ~paymentMethodListValue, + ~intent, + ~options, + ~publishableKey, + ) + } else if dict->Dict.get("showApplePayButton")->Option.isSome { + setApplePayClicked(_ => false) + } else if dict->Dict.get("applePaySyncPayment")->Option.isSome { + syncPayment() + } + } catch { + | _ => logInfo(Console.log("Error in parsing Apple Pay Data")) + } + } + Window.addEventListener("message", handleApplePayMessages) + Some( + () => { + handlePostMessage([("applePaySessionAbort", true->JSON.Encode.bool)]) + Window.removeEventListener("message", handleApplePayMessages) + }, + ) + }, (isInvokeSDKFlow, processPayment, stateJson)) +} + +let handleApplePayButtonClicked = (~sessionObj, ~componentName) => { + let paymentRequest = ApplePayTypes.getPaymentRequestFromSession(~sessionObj, ~componentName) + let message = [ + ("applePayButtonClicked", true->JSON.Encode.bool), + ("applePayPaymentRequest", paymentRequest), + ] + handlePostMessage(message) +} diff --git a/src/Utilities/DynamicFieldsUtils.res b/src/Utilities/DynamicFieldsUtils.res index 640f73f80..52650ceb9 100644 --- a/src/Utilities/DynamicFieldsUtils.res +++ b/src/Utilities/DynamicFieldsUtils.res @@ -825,13 +825,64 @@ let getNameFromFirstAndLastName = (~firstName, ~lastName, ~requiredFieldsArr) => }->String.trim } +let defaultRequiredFieldsArray: array = [ + { + required_field: "email", + display_name: "email", + field_type: Email, + value: "", + }, + { + required_field: "payment_method_data.billing.address.state", + display_name: "state", + field_type: AddressState, + value: "", + }, + { + required_field: "payment_method_data.billing.address.first_name", + display_name: "billing_first_name", + field_type: BillingName, + value: "", + }, + { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: AddressCity, + value: "", + }, + { + required_field: "payment_method_data.billing.address.country", + display_name: "country", + field_type: AddressCountry(["ALL"]), + value: "", + }, + { + required_field: "payment_method_data.billing.address.line1", + display_name: "line", + field_type: AddressLine1, + value: "", + }, + { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: AddressPincode, + value: "", + }, + { + required_field: "payment_method_data.billing.address.last_name", + display_name: "billing_last_name", + field_type: BillingName, + value: "", + }, +] + let getApplePayRequiredFields = ( ~billingContact: ApplePayTypes.billingContact, ~shippingContact: ApplePayTypes.shippingContact, - ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~requiredFields=defaultRequiredFieldsArray, ~statesList, ) => { - paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + requiredFields->Array.reduce(Dict.make(), (acc, item) => { let requiredFieldsArr = item.required_field->String.split(".") let getName = (firstName, lastName) => { @@ -897,11 +948,11 @@ let getApplePayRequiredFields = ( let getGooglePayRequiredFields = ( ~billingContact: GooglePayType.billingContact, ~shippingContact: GooglePayType.billingContact, - ~paymentMethodTypes: PaymentMethodsRecord.paymentMethodTypes, + ~requiredFields=defaultRequiredFieldsArray, ~statesList, ~email, ) => { - paymentMethodTypes.required_fields->Array.reduce(Dict.make(), (acc, item) => { + requiredFields->Array.reduce(Dict.make(), (acc, item) => { let requiredFieldsArr = item.required_field->String.split(".") let fieldVal = switch item.field_type { diff --git a/src/Utilities/GooglePayHelpers.res b/src/Utilities/GooglePayHelpers.res new file mode 100644 index 000000000..64b3c24da --- /dev/null +++ b/src/Utilities/GooglePayHelpers.res @@ -0,0 +1,146 @@ +open Utils + +let getGooglePayBodyFromResponse = ( + ~gPayResponse, + ~isGuestCustomer, + ~paymentMethodListValue=PaymentMethodsRecord.defaultList, + ~connectors, + ~requiredFields=[], + ~stateJson, + ~isPaymentSession=false, +) => { + let obj = gPayResponse->getDictFromJson->GooglePayType.itemToObjMapper + let gPayBody = PaymentUtils.appendedCustomerAcceptance( + ~isGuestCustomer, + ~paymentType=paymentMethodListValue.payment_type, + ~body=PaymentBody.gpayBody(~payObj=obj, ~connectors), + ) + + let billingContact = + obj.paymentMethodData.info + ->getDictFromJson + ->getJsonObjectFromDict("billingAddress") + ->getDictFromJson + ->GooglePayType.billingContactItemToObjMapper + + let shippingContact = + gPayResponse + ->getDictFromJson + ->getJsonObjectFromDict("shippingAddress") + ->getDictFromJson + ->GooglePayType.billingContactItemToObjMapper + + let email = + gPayResponse + ->getDictFromJson + ->getString("email", "") + + let requiredFieldsBody = if isPaymentSession { + DynamicFieldsUtils.getGooglePayRequiredFields( + ~billingContact, + ~shippingContact, + ~statesList=stateJson, + ~email, + ) + } else { + DynamicFieldsUtils.getGooglePayRequiredFields( + ~billingContact, + ~shippingContact, + ~requiredFields, + ~statesList=stateJson, + ~email, + ) + } + + gPayBody + ->getJsonFromArrayOfJson + ->flattenObject(true) + ->mergeTwoFlattenedJsonDicts(requiredFieldsBody) + ->getArrayOfTupleFromDict +} + +let processPayment = ( + ~body: array<(string, JSON.t)>, + ~isThirdPartyFlow=false, + ~intent: PaymentHelpers.paymentIntent, + ~options: PaymentType.options, + ~publishableKey, +) => { + intent( + ~bodyArr=body, + ~confirmParam={ + return_url: options.wallets.walletReturnUrl, + publishableKey, + }, + ~handleUserError=true, + ~isThirdPartyFlow, + (), + ) +} + +let useHandleGooglePayResponse = (~connectors, ~intent) => { + let options = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom) + let {publishableKey} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys) + + let paymentMethodListValue = Recoil.useRecoilValueFromAtom(PaymentUtils.paymentMethodListValue) + let isGuestCustomer = UtilityHooks.useIsGuestCustomer() + + let (stateJson, setStatesJson) = React.useState(_ => JSON.Encode.null) + + PaymentUtils.useStatesJson(setStatesJson) + + let paymentMethodTypes = DynamicFieldsUtils.usePaymentMethodTypeFromList( + ~paymentMethodListValue, + ~paymentMethod="wallet", + ~paymentMethodType="google_pay", + ) + + React.useEffect(() => { + let handle = (ev: Window.event) => { + let json = try { + ev.data->JSON.parseExn + } catch { + | _ => Dict.make()->JSON.Encode.object + } + let dict = json->getDictFromJson + if dict->Dict.get("gpayResponse")->Option.isSome { + let metadata = dict->getJsonObjectFromDict("gpayResponse") + let body = getGooglePayBodyFromResponse( + ~gPayResponse=metadata, + ~isGuestCustomer, + ~paymentMethodListValue, + ~connectors, + ~requiredFields=paymentMethodTypes.required_fields, + ~stateJson, + ) + processPayment( + ~body, + ~isThirdPartyFlow=false, + ~intent, + ~options: PaymentType.options, + ~publishableKey, + ) + } + if dict->Dict.get("gpayError")->Option.isSome { + handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) + } + } + Window.addEventListener("message", handle) + Some(() => {Window.removeEventListener("message", handle)}) + }, (paymentMethodTypes, stateJson)) +} + +let handleGooglePayClicked = (~sessionObj, ~componentName, ~iframeId, ~readOnly) => { + let paymentDataRequest = GooglePayType.getPaymentDataFromSession(~sessionObj, ~componentName) + handlePostMessage([ + ("fullscreen", true->JSON.Encode.bool), + ("param", "paymentloader"->JSON.Encode.string), + ("iframeId", iframeId->JSON.Encode.string), + ]) + if !readOnly { + handlePostMessage([ + ("GpayClicked", true->JSON.Encode.bool), + ("GpayPaymentDataRequest", paymentDataRequest->Identity.anyTypeToJson), + ]) + } +} diff --git a/src/Utilities/PaymentBody.res b/src/Utilities/PaymentBody.res index 9e3d6fad3..e8c10b1e5 100644 --- a/src/Utilities/PaymentBody.res +++ b/src/Utilities/PaymentBody.res @@ -424,33 +424,42 @@ let paypalSdkBody = (~token, ~connectors) => [ ), ] -let gpayBody = (~payObj: GooglePayType.paymentData, ~connectors: array) => [ - ("payment_method", "wallet"->JSON.Encode.string), - ("payment_method_type", "google_pay"->JSON.Encode.string), - ("connector", connectors->Utils.getArrofJsonString->JSON.Encode.array), - ( - "payment_method_data", - [ - ( - "wallet", - [ - ( - "google_pay", - [ - ("type", payObj.paymentMethodData.\"type"->JSON.Encode.string), - ("description", payObj.paymentMethodData.description->JSON.Encode.string), - ("info", payObj.paymentMethodData.info->Utils.transformKeys(Utils.SnakeCase)), - ( - "tokenization_data", - payObj.paymentMethodData.tokenizationData->Utils.transformKeys(Utils.SnakeCase), - ), - ]->Utils.getJsonFromArrayOfJson, - ), - ]->Utils.getJsonFromArrayOfJson, - ), - ]->Utils.getJsonFromArrayOfJson, - ), -] +let gpayBody = (~payObj: GooglePayType.paymentData, ~connectors: array) => { + let gPayBody = [ + ("payment_method", "wallet"->JSON.Encode.string), + ("payment_method_type", "google_pay"->JSON.Encode.string), + ( + "payment_method_data", + [ + ( + "wallet", + [ + ( + "google_pay", + [ + ("type", payObj.paymentMethodData.\"type"->JSON.Encode.string), + ("description", payObj.paymentMethodData.description->JSON.Encode.string), + ("info", payObj.paymentMethodData.info->Utils.transformKeys(Utils.SnakeCase)), + ( + "tokenization_data", + payObj.paymentMethodData.tokenizationData->Utils.transformKeys(Utils.SnakeCase), + ), + ]->Utils.getJsonFromArrayOfJson, + ), + ]->Utils.getJsonFromArrayOfJson, + ), + ]->Utils.getJsonFromArrayOfJson, + ), + ] + + if connectors->Array.length > 0 { + gPayBody + ->Array.push(("connector", connectors->Utils.getArrofJsonString->JSON.Encode.array)) + ->ignore + } + + gPayBody +} let gpayRedirectBody = (~connectors: array) => [ ("payment_method", "wallet"->JSON.Encode.string), @@ -495,8 +504,8 @@ let applePayBody = (~token, ~connectors) => { ->JSON.stringify ->btoa dict->Dict.set("paymentData", paymentDataString->JSON.Encode.string) - [ - ("connector", connectors->Utils.getArrofJsonString->JSON.Encode.array), + + let applePayBody = [ ("payment_method", "wallet"->JSON.Encode.string), ("payment_method_type", "apple_pay"->JSON.Encode.string), ( @@ -513,6 +522,14 @@ let applePayBody = (~token, ~connectors) => { ->JSON.Encode.object, ), ] + + if connectors->Array.length > 0 { + applePayBody + ->Array.push(("connector", connectors->Utils.getArrofJsonString->JSON.Encode.array)) + ->ignore + } + + applePayBody } let applePayRedirectBody = (~connectors) => [ diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 26f8f9c89..eb733e8e9 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -10,6 +10,17 @@ type url = {searchParams: searchParams, href: string} open LoggerUtils type payment = Card | BankTransfer | BankDebits | KlarnaRedirect | Gpay | Applepay | Paypal | Other +let getPaymentType = paymentMethodType => + switch paymentMethodType { + | "apple_pay" => Applepay + | "google_pay" => Gpay + | "debit" + | "credit" + | "" => + Card + | _ => Other + } + let closePaymentLoaderIfAny = () => handlePostMessage([("fullscreen", false->JSON.Encode.bool)]) type paymentIntent = ( @@ -1246,6 +1257,7 @@ let fetchSessions = ( ~optLogger, ~switchToCustomPod, ~endpoint, + ~isPaymentSession=false, (), ) => { open Promise @@ -1266,6 +1278,7 @@ let fetchSessions = ( ~eventName=SESSIONS_CALL_INIT, ~logType=INFO, ~logCategory=API, + ~isPaymentSession, (), ) fetchApi( @@ -1290,6 +1303,7 @@ let fetchSessions = ( ~eventName=SESSIONS_CALL, ~logType=ERROR, ~logCategory=API, + ~isPaymentSession, (), ) JSON.Encode.null->resolve @@ -1303,6 +1317,7 @@ let fetchSessions = ( ~eventName=SESSIONS_CALL, ~logType=INFO, ~logCategory=API, + ~isPaymentSession, (), ) Fetch.Response.json(resp) @@ -1318,6 +1333,7 @@ let fetchSessions = ( ~logType=ERROR, ~logCategory=API, ~data=exceptionMessage, + ~isPaymentSession, (), ) JSON.Encode.null->resolve @@ -1476,3 +1492,72 @@ let fetchCustomerPaymentMethodList = ( JSON.Encode.null->resolve }) } + +let paymentIntentForPaymentSession = ( + ~body, + ~paymentType, + ~payload, + ~publishableKey, + ~clientSecret, + ~logger, +) => { + let confirmParams = + payload + ->getDictFromJson + ->getDictFromDict("confirmParams") + + let redirect = confirmParams->getString("redirect", "if_required") + + let returnUrl = confirmParams->getString("return_url", "") + + let confirmParam: ConfirmType.confirmParams = { + return_url: returnUrl, + publishableKey, + redirect, + } + + let paymentIntentID = String.split(clientSecret, "_secret_")[0]->Option.getOr("") + + let endpoint = ApiEndpoint.getApiEndPoint( + ~publishableKey=confirmParam.publishableKey, + ~isConfirmCall=true, + (), + ) + let uri = `${endpoint}/payments/${paymentIntentID}/confirm` + let headers = [("Content-Type", "application/json"), ("api-key", confirmParam.publishableKey)] + + let broswerInfo = BrowserSpec.broswerInfo() + + let returnUrlArr = [("return_url", confirmParam.return_url->JSON.Encode.string)] + + let bodyStr = + body + ->Array.concatMany([ + broswerInfo, + [("client_secret", clientSecret->JSON.Encode.string)], + returnUrlArr, + ]) + ->getJsonFromArrayOfJson + ->JSON.stringify + + intentCall( + ~fetchApi, + ~uri, + ~headers, + ~bodyStr, + ~confirmParam: ConfirmType.confirmParams, + ~clientSecret, + ~optLogger=Some(logger), + ~handleUserError=false, + ~paymentType, + ~iframeId="", + ~fetchMethod=#POST, + ~setIsManualRetryEnabled={_ => ()}, + ~switchToCustomPod=false, + ~sdkHandleOneClickConfirmPayment=false, + ~counter=0, + ~isPaymentSession=true, + (), + ) +} + diff --git a/src/Utilities/PaymentUtils.res b/src/Utilities/PaymentUtils.res index aa98d9684..289af12bc 100644 --- a/src/Utilities/PaymentUtils.res +++ b/src/Utilities/PaymentUtils.res @@ -369,3 +369,56 @@ let useStatesJson = setStatesJson => { None }) } + +let getStateJson = () => { + open Promise + AddressPaymentInput.importStates("./../States.json")->then(res => { + res.states->resolve + }) +} + +let sortCustomerMethodsBasedOnPriority = ( + sortArr: array, + priorityArr: array, + ~displayDefaultSavedPaymentIcon=true, +) => { + let priorityArr = priorityArr->Array.length > 0 ? priorityArr : PaymentModeType.defaultOrder + + let getPaymentMethod = (customerMethod: PaymentType.customerMethods) => { + if customerMethod.paymentMethod === "card" { + customerMethod.paymentMethod + } else { + switch customerMethod.paymentMethodType { + | Some(paymentMethodType) => paymentMethodType + | _ => customerMethod.paymentMethod + } + } + } + + let getCustomerMethodPriority = (paymentMethod: string) => { + let priorityArrLength = priorityArr->Array.length + let index = priorityArr->Array.indexOf(paymentMethod) + + index === -1 ? priorityArrLength : index + } + + let handleCustomerMethodsSort = ( + firstCustomerMethod: PaymentType.customerMethods, + secondCustomerMethod: PaymentType.customerMethods, + ) => { + let firstPaymentMethod = firstCustomerMethod->getPaymentMethod + let secondPaymentMethod = secondCustomerMethod->getPaymentMethod + + if ( + displayDefaultSavedPaymentIcon && + (firstCustomerMethod.defaultPaymentMethodSet || secondCustomerMethod.defaultPaymentMethodSet) + ) { + firstCustomerMethod.defaultPaymentMethodSet ? -1 : 1 + } else { + firstPaymentMethod->getCustomerMethodPriority - secondPaymentMethod->getCustomerMethodPriority + } + } + + sortArr->Belt.SortArray.stableSortBy(handleCustomerMethodsSort) +} + diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 8336197e7..be60d6c42 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -1317,3 +1317,15 @@ let toSpacedUpperCase = (~str, ~delimiter) => ->String.toUpperCase ->String.split(delimiter) ->Array.joinWith(" ") + +let handleFailureResponse = (~message, ~errorType) => + [ + ( + "error", + [ + ("type", errorType->JSON.Encode.string), + ("message", message->JSON.Encode.string), + ]->getJsonFromArrayOfJson, + ), + ]->getJsonFromArrayOfJson + diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res index 2865b6c4e..4d5d07da9 100644 --- a/src/orca-loader/Elements.res +++ b/src/orca-loader/Elements.res @@ -761,21 +761,6 @@ let make = ( componentType->getIsComponentTypeForPaymentElementCreate && applePayPresent->Option.isSome ) { - //do operations here - let processPayment = ( - payment: ApplePayTypes.paymentResult, - applePayEvent: Types.event, - ) => { - //let body = PaymentBody.applePayBody(~token) - let msg = - [ - ("applePayProcessPayment", payment.token), - ("applePayBillingContact", payment.billingContact), - ("applePayShippingContact", payment.shippingContact), - ]->Dict.fromArray - applePayEvent.source->Window.sendPostMessage(msg) - } - let handleApplePayMessages = (applePayEvent: Types.event) => { let json = applePayEvent.data->Identity.anyTypeToJson let dict = json->getDictFromJson @@ -801,58 +786,24 @@ let make = ( (), ) - 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->Js.Nullable.return - - ssn.onvalidatemerchant = _event => { - let merchantSession = - applePayPresent - ->Belt.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) - } - - ssn.onpaymentauthorized = event => { - ssn.completePayment( - {"status": ssn.\"STATUS_SUCCESS"}->Identity.anyTypeToJson, - ) - applePaySessionRef := Nullable.null - processPayment(event.payment, applePayEvent) - let value = "Payment Data Filled: New Payment Method" - logger.setLogInfo( - ~value, - ~eventName=PAYMENT_DATA_FILLED, - ~paymentMethod="APPLE_PAY", - (), - ) - } - ssn.oncancel = _ev => { + let callBackFunc = payment => { let msg = - [("showApplePayButton", true->JSON.Encode.bool)]->Dict.fromArray + [ + ("applePayProcessPayment", payment.token), + ("applePayBillingContact", payment.billingContact), + ("applePayShippingContact", payment.shippingContact), + ]->Dict.fromArray applePayEvent.source->Window.sendPostMessage(msg) - applePaySessionRef := Nullable.null - logInfo(Console.log("Apple Pay Payment Cancelled")) - logger.setLogInfo( - ~value="Apple Pay Payment Cancelled", - ~eventName=APPLE_PAY_FLOW, - ~paymentMethod="APPLE_PAY", - (), - ) } - ssn.begin() + + ApplePayHelpers.startApplePaySession( + ~paymentRequest, + ~applePaySessionRef, + ~applePayPresent, + ~logger, + ~applePayEvent=Some(applePayEvent), + ~callBackFunc, + ) } } else { () diff --git a/src/orca-loader/PaymentSessionMethods.res b/src/orca-loader/PaymentSessionMethods.res index bd6c2d983..828484ba6 100644 --- a/src/orca-loader/PaymentSessionMethods.res +++ b/src/orca-loader/PaymentSessionMethods.res @@ -8,6 +8,16 @@ let getCustomerSavedPaymentMethods = ( open Promise open Types open Utils + open ApplePayTypes + open GooglePayType + let applePaySessionRef = ref(Nullable.null) + + let gPayClient = google( + { + "environment": publishableKey->String.startsWith("pk_prd_") ? "PRODUCTION" : "TEST", + }->Identity.anyTypeToJson, + ) + PaymentHelpers.fetchCustomerPaymentMethodList( ~clientSecret, ~publishableKey, @@ -17,182 +27,429 @@ let getCustomerSavedPaymentMethods = ( ~isPaymentSession=true, ) ->then(customerDetails => { - let customerDetailsArray = - customerDetails - ->JSON.Decode.object - ->Option.flatMap(x => x->Dict.get("customer_payment_methods")) - ->Option.flatMap(JSON.Decode.array) - ->Option.getOr([]) - - let customerPaymentMethods = customerDetailsArray->Array.filter(customerPaymentMethod => { - customerPaymentMethod - ->JSON.Decode.object - ->Option.flatMap(x => x->Dict.get("default_payment_method_set")) - ->Option.flatMap(JSON.Decode.bool) - ->Option.getOr(false) - }) - - let getLastUsedAtDate = val => - Date.fromString(val->getDictFromJson->getString("last_used_at", "")) - - let paymentNotExist = ( - ~message="There is no default saved payment method data for this customer", - ) => - [ - ( - "error", - [ - ("type", "no_data"->JSON.Encode.string), - ("message", message->JSON.Encode.string), - ]->getJsonFromArrayOfJson, - ), - ]->getJsonFromArrayOfJson - - let getCustomerDefaultSavedPaymentMethodData = () => - switch customerPaymentMethods->Array.get(0) { - | Some(customerDefaultPaymentMethod) => customerDefaultPaymentMethod - | None => paymentNotExist() - } + let customerDetailsDict = customerDetails->JSON.Decode.object->Option.getOr(Dict.make()) + let (customerPaymentMethods, isGuestCustomer) = + customerDetailsDict->PaymentType.itemToCustomerObjMapper - let getCustomerLastUsedPaymentMethodData = () => { - let customerPaymentMethodsCopy = customerDetailsArray->Array.copy - customerPaymentMethodsCopy->Array.sort((a, b) => - compareLogic(a->getLastUsedAtDate, b->getLastUsedAtDate) + customerPaymentMethods->Array.sort((a, b) => compareLogic(a.lastUsedAt, b.lastUsedAt)) + + let customerPaymentMethodsRef = ref(customerPaymentMethods) + let applePayTokenRef = ref(JSON.Encode.null) + let googlePayTokenRef = ref(JSON.Encode.null) + + let isApplePayPresent = + customerPaymentMethods + ->Array.find(customerPaymentMethod => + customerPaymentMethod.paymentMethodType === Some("apple_pay") + ) + ->Option.isSome + + let isGooglePayPresent = + customerPaymentMethods + ->Array.find(customerPaymentMethod => + customerPaymentMethod.paymentMethodType === Some("google_pay") ) + ->Option.isSome - switch customerPaymentMethodsCopy->Array.get(0) { - | Some(customerLastPaymentUsed) => customerLastPaymentUsed - | None => paymentNotExist(~message="No recent payments found for this customer.") + let canMakePayments = try { + switch sessionForApplePay->Nullable.toOption { + | Some(session) => session.canMakePayments() + | _ => false } + } catch { + | _ => false } - let confirmCallForParticularPaymentObject = (~paymentMethodObject, ~payload) => { - let customerPaymentMethod = paymentMethodObject->getDictFromJson - - let paymentToken = customerPaymentMethod->getJsonFromDict("payment_token", JSON.Encode.null) + let customerDefaultPaymentMethod = + customerPaymentMethods + ->Array.filter(customerPaymentMethod => { + customerPaymentMethod.defaultPaymentMethodSet + }) + ->Array.get(0) - let paymentMethod = customerPaymentMethod->getJsonFromDict("payment_method", JSON.Encode.null) + let getCustomerDefaultSavedPaymentMethodData = () => { + switch customerDefaultPaymentMethod { + | Some(defaultPaymentMethod) => defaultPaymentMethod->Identity.anyTypeToJson + | None => + handleFailureResponse( + ~message="There is no default saved payment method data for this customer.", + ~errorType="no_data", + ) + } + } - let paymentMethodType = - customerPaymentMethod->getJsonFromDict("payment_method_type", JSON.Encode.null) + let getCustomerLastUsedPaymentMethodData = () => { + switch customerPaymentMethodsRef.contents->Array.get(0) { + | Some(lastUsedPaymentMethod) => lastUsedPaymentMethod->Identity.anyTypeToJson + | None => + handleFailureResponse( + ~message="No recent payments found for this customer.", + ~errorType="no_data", + ) + } + } - let confirmParams = - payload - ->getDictFromJson - ->getDictFromDict("confirmParams") + let confirmWithCustomerDefaultPaymentMethod = payload => { + switch customerDefaultPaymentMethod { + | Some(defaultPaymentMethod) => { + let paymentToken = defaultPaymentMethod.paymentToken + let paymentMethod = defaultPaymentMethod.paymentMethod + let paymentMethodType = defaultPaymentMethod.paymentMethodType->Option.getOr("") + let paymentType = paymentMethodType->PaymentHelpers.getPaymentType - let redirect = confirmParams->getString("redirect", "if_required") + let body = [ + ("payment_method", paymentMethod->JSON.Encode.string), + ("payment_token", paymentToken->JSON.Encode.string), + ] - let returnUrl = confirmParams->getString("return_url", "") + if paymentMethodType !== "" { + body->Array.push(("payment_method_type", paymentMethodType->JSON.Encode.string))->ignore + } - let confirmParam: ConfirmType.confirmParams = { - return_url: returnUrl, - publishableKey, - redirect, + PaymentHelpers.paymentIntentForPaymentSession( + ~body, + ~paymentType, + ~payload, + ~publishableKey, + ~clientSecret, + ~logger, + ) + } + | None => + handleFailureResponse( + ~message="There is no default saved payment method data for this customer.", + ~errorType="no_data", + )->resolve } + } - let paymentIntentID = String.split(clientSecret, "_secret_")[0]->Option.getOr("") + let handleApplePayConfirmPayment = ( + lastUsedPaymentMethod: PaymentType.customerMethods, + payload, + resolvePromise, + ) => { + let processPayment = (payment: ApplePayTypes.paymentResult) => { + let token = payment.token - let endpoint = ApiEndpoint.getApiEndPoint( - ~publishableKey=confirmParam.publishableKey, - ~isConfirmCall=true, - (), - ) - let uri = `${endpoint}/payments/${paymentIntentID}/confirm` - let headers = [("Content-Type", "application/json"), ("api-key", confirmParam.publishableKey)] - - let paymentType: PaymentHelpers.payment = switch paymentMethodType - ->JSON.Decode.string - ->Option.getOr("") { - | "apple_pay" => Applepay - | "google_pay" => Gpay - | "debit" - | "credit" - | "" => - Card - | _ => Other + let billingContactDict = payment.billingContact->Utils.getDictFromJson + let shippingContactDict = payment.shippingContact->Utils.getDictFromJson + + let completeApplePayPayment = stateJson => { + let applePayBody = ApplePayHelpers.getApplePayFromResponse( + ~token, + ~billingContactDict, + ~shippingContactDict, + ~stateJson, + ~connectors=[], + ~isPaymentSession=true, + ) + + let requestBody = PaymentUtils.appendedCustomerAcceptance( + ~isGuestCustomer, + ~paymentType=NONE, + ~body=applePayBody, + ) + + let paymentMethodType = lastUsedPaymentMethod.paymentMethodType->Option.getOr("") + let paymentType = paymentMethodType->PaymentHelpers.getPaymentType + + PaymentHelpers.paymentIntentForPaymentSession( + ~body=requestBody, + ~paymentType, + ~payload, + ~publishableKey, + ~clientSecret, + ~logger, + )->then(val => { + val->resolvePromise + resolve() + }) + } + + PaymentUtils.getStateJson() + ->then(stateJson => { + logger.setLogInfo(~value="States Loaded", ~eventName=APPLE_PAY_FLOW, ~paymentMethod="APPLE_PAY", ()) + stateJson->completeApplePayPayment + }) + ->catch(err => { + let value = "Error Loading States : " ++ err->Identity.anyTypeToJson->JSON.stringify + logger.setLogInfo(~value, ~eventName=APPLE_PAY_FLOW, ~paymentMethod="APPLE_PAY", ()) + completeApplePayPayment(JSON.Encode.null) + }) + ->ignore } - let broswerInfo = BrowserSpec.broswerInfo() + ApplePayHelpers.startApplePaySession( + ~paymentRequest=applePayTokenRef.contents, + ~applePaySessionRef, + ~applePayPresent=Some(applePayTokenRef.contents), + ~logger, + ~callBackFunc=processPayment, + ) + } + + let handleGooglePayConfirmPayment = ( + lastUsedPaymentMethod: PaymentType.customerMethods, + payload, + ) => { + let paymentDataRequest = googlePayTokenRef.contents + + gPayClient.loadPaymentData(paymentDataRequest) + ->then(json => { + let metadata = json->Identity.anyTypeToJson - let body = [ - ("client_secret", clientSecret->JSON.Encode.string), - ("payment_method", paymentMethod), - ("payment_token", paymentToken), - ("payment_method_type", paymentMethodType), - ] + let value = "Payment Data Filled: New Payment Method" + logger.setLogInfo(~value, ~eventName=PAYMENT_DATA_FILLED, ~paymentMethod="GOOGLE_PAY", ()) - let bodyStr = body->Array.concat(broswerInfo)->getJsonFromArrayOfJson->JSON.stringify + let completeGooglePayPayment = stateJson => { + let body = GooglePayHelpers.getGooglePayBodyFromResponse( + ~gPayResponse=metadata, + ~isGuestCustomer, + ~connectors=[], + ~stateJson, + ~isPaymentSession=true, + ) - PaymentHelpers.intentCall( - ~fetchApi, - ~uri, - ~headers, - ~bodyStr, - ~confirmParam: ConfirmType.confirmParams, + let paymentMethodType = lastUsedPaymentMethod.paymentMethodType->Option.getOr("") + let paymentType = paymentMethodType->PaymentHelpers.getPaymentType + + PaymentHelpers.paymentIntentForPaymentSession( + ~body, + ~paymentType, + ~payload, + ~publishableKey, + ~clientSecret, + ~logger, + ) + } + + PaymentUtils.getStateJson() + ->then( + stateJson => { + logger.setLogInfo(~value="States Loaded", ~eventName=GOOGLE_PAY_FLOW, ~paymentMethod="GOOGLE_PAY", ()) + stateJson->completeGooglePayPayment + }, + ) + ->catch( + err => { + let value = "Error Loading States : " ++ err->Identity.anyTypeToJson->JSON.stringify + logger.setLogInfo(~value, ~eventName=GOOGLE_PAY_FLOW, ~paymentMethod="GOOGLE_PAY", ()) + completeGooglePayPayment(JSON.Encode.null) + }, + ) + }) + ->catch(err => { + logger.setLogInfo( + ~value=err->Identity.anyTypeToJson->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + + handleFailureResponse( + ~message=err->Identity.anyTypeToJson->JSON.stringify, + ~errorType="google_pay", + )->resolve + }) + } + + let confirmWithLastUsedPaymentMethod = payload => { + switch customerPaymentMethodsRef.contents->Array.get(0) { + | Some(lastUsedPaymentMethod) => + if lastUsedPaymentMethod.paymentMethodType === Some("apple_pay") { + Promise.make((resolve, _) => { + handleApplePayConfirmPayment(lastUsedPaymentMethod, payload, resolve) + }) + } else if lastUsedPaymentMethod.paymentMethodType === Some("google_pay") { + handleGooglePayConfirmPayment(lastUsedPaymentMethod, payload) + } else { + let paymentToken = lastUsedPaymentMethod.paymentToken + let paymentMethod = lastUsedPaymentMethod.paymentMethod + let paymentMethodType = lastUsedPaymentMethod.paymentMethodType->Option.getOr("") + let paymentType = paymentMethodType->PaymentHelpers.getPaymentType + + let body = [ + ("payment_method", paymentMethod->JSON.Encode.string), + ("payment_token", paymentToken->JSON.Encode.string), + ] + + if paymentMethodType !== "" { + body->Array.push(("payment_method_type", paymentMethodType->JSON.Encode.string))->ignore + } + + PaymentHelpers.paymentIntentForPaymentSession( + ~body, + ~paymentType, + ~payload, + ~publishableKey, + ~clientSecret, + ~logger, + ) + } + | None => + handleFailureResponse( + ~message="No recent payments found for this customer.", + ~errorType="no_data", + )->resolve + } + } + + let updateCustomerPaymentMethodsRef = (~isFilterApplePay=false, ~isFilterGooglePay=false) => { + let filterArray = [] + if isFilterApplePay { + filterArray->Array.push("apple_pay") + } + if isFilterGooglePay { + filterArray->Array.push("google_pay") + } + let updatedCustomerDetails = + customerPaymentMethodsRef.contents->Array.filter(customerPaymentMethod => { + filterArray + ->Array.includes(customerPaymentMethod.paymentMethodType->Option.getOr("")) + ->not + }) + + customerPaymentMethodsRef := updatedCustomerDetails + } + + if (isApplePayPresent && canMakePayments) || isGooglePayPresent { + PaymentHelpers.fetchSessions( ~clientSecret, + ~publishableKey, ~optLogger=Some(logger), - ~handleUserError=false, - ~paymentType, - ~iframeId="", - ~fetchMethod=#POST, - ~setIsManualRetryEnabled={_ => ()}, ~switchToCustomPod=false, - ~sdkHandleOneClickConfirmPayment=false, - ~counter=0, - ~isPaymentSession=true, + ~endpoint, (), ) - } + ->then(sessionDetails => { + let componentName = "headless" + let dict = sessionDetails->Utils.getDictFromJson + let sessionObj = SessionsType.itemToObjMapper(dict, Others) - let confirmWithCustomerDefaultPaymentMethod = payload => { - switch customerPaymentMethods->Array.get(0) { - | Some(customerDefaultPaymentMethod) => - confirmCallForParticularPaymentObject( - ~paymentMethodObject=customerDefaultPaymentMethod, - ~payload, + let applePaySessionObj = SessionsType.itemToObjMapper(dict, ApplePayObject) + let applePayToken = SessionsType.getPaymentSessionObj( + applePaySessionObj.sessionsToken, + ApplePay, ) - | None => paymentNotExist()->resolve - } - } - let confirmWithLastUsedPaymentMethod = payload => { - let customerPaymentMethodsCopy = customerDetailsArray->Array.copy - customerPaymentMethodsCopy->Array.sort((a, b) => - compareLogic(a->getLastUsedAtDate, b->getLastUsedAtDate) - ) + let gPayToken = SessionsType.getPaymentSessionObj(sessionObj.sessionsToken, Gpay) - switch customerPaymentMethodsCopy->Array.get(0) { - | Some(customerLastPaymentUsed) => - confirmCallForParticularPaymentObject( - ~paymentMethodObject=customerLastPaymentUsed, - ~payload, + let gPayTokenObj = switch gPayToken { + | OtherTokenOptional(optToken) => optToken + | _ => Some(SessionsType.defaultToken) + } + + let gPayobj = switch gPayTokenObj { + | Some(val) => val + | _ => SessionsType.defaultToken + } + + let payRequest = assign( + Dict.make()->JSON.Encode.object, + baseRequest->Identity.anyTypeToJson, + { + "allowedPaymentMethods": gPayobj.allowed_payment_methods->arrayJsonToCamelCase, + }->Identity.anyTypeToJson, ) - | None => paymentNotExist(~message="No recent payments found for this customer.")->resolve - } - } - { - getCustomerDefaultSavedPaymentMethodData, - getCustomerLastUsedPaymentMethodData, - confirmWithCustomerDefaultPaymentMethod, - confirmWithLastUsedPaymentMethod, + let isGooglePayReadyPromise = try { + gPayClient.isReadyToPay(payRequest) + ->then( + res => { + let dict = res->getDictFromJson + getBool(dict, "result", false)->resolve + }, + ) + ->catch( + err => { + logger.setLogInfo( + ~value=err->Identity.anyTypeToJson->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + false->resolve + }, + ) + } catch { + | exn => { + logger.setLogInfo( + ~value=exn->Identity.anyTypeToJson->JSON.stringify, + ~eventName=GOOGLE_PAY_FLOW, + ~paymentMethod="GOOGLE_PAY", + ~logType=DEBUG, + (), + ) + false->resolve + } + } + + isGooglePayReadyPromise + ->then( + isGooglePayReady => { + if isGooglePayReady { + let paymentDataRequest = getPaymentDataFromSession( + ~sessionObj=gPayTokenObj, + ~componentName, + ) + googlePayTokenRef := paymentDataRequest->Identity.anyTypeToJson + } else { + updateCustomerPaymentMethodsRef(~isFilterGooglePay=true) + } + resolve() + }, + ) + ->ignore + + switch applePayToken { + | ApplePayTokenOptional(optToken) => { + let paymentRequest = ApplePayTypes.getPaymentRequestFromSession( + ~sessionObj=optToken, + ~componentName, + ) + applePayTokenRef := paymentRequest + } + | _ => updateCustomerPaymentMethodsRef(~isFilterApplePay=true) + } + + { + getCustomerDefaultSavedPaymentMethodData, + getCustomerLastUsedPaymentMethodData, + confirmWithCustomerDefaultPaymentMethod, + confirmWithLastUsedPaymentMethod, + } + ->Identity.anyTypeToJson + ->resolve + }) + ->catch(err => { + updateCustomerPaymentMethodsRef(~isFilterApplePay=true, ~isFilterGooglePay=true) + + { + getCustomerDefaultSavedPaymentMethodData, + getCustomerLastUsedPaymentMethodData, + confirmWithCustomerDefaultPaymentMethod, + confirmWithLastUsedPaymentMethod, + } + ->Identity.anyTypeToJson + ->resolve + }) + } else { + updateCustomerPaymentMethodsRef(~isFilterApplePay=true, ~isFilterGooglePay=true) + + { + getCustomerDefaultSavedPaymentMethodData, + getCustomerLastUsedPaymentMethodData, + confirmWithCustomerDefaultPaymentMethod, + confirmWithLastUsedPaymentMethod, + } + ->Identity.anyTypeToJson + ->resolve } - ->Identity.anyTypeToJson - ->resolve }) ->catch(err => { let exceptionMessage = err->formatException->JSON.stringify - let updatedCustomerDetails = - [ - ( - "error", - [ - ("type", "server_error"->JSON.Encode.string), - ("message", exceptionMessage->JSON.Encode.string), - ]->getJsonFromArrayOfJson, - ), - ]->getJsonFromArrayOfJson - updatedCustomerDetails->resolve + handleFailureResponse(~message=exceptionMessage, ~errorType="server_error")->resolve }) }