From 7f8dccc0a0c176447610bc85e5f350658ce333f9 Mon Sep 17 00:00:00 2001 From: Arush Date: Sat, 9 Mar 2024 13:29:46 +0530 Subject: [PATCH] fix: added Support for future APIs and added error handling fix #207 --- src/Utilities/PaymentHelpers.res | 35 ++++- src/Utilities/Utils.res | 13 ++ src/orca-loader/PaymentSession.res | 150 ++---------------- src/orca-loader/PaymentSessionMethods.res | 180 ++++++++++++++++++++++ src/orca-loader/Types.res | 26 +++- 5 files changed, 250 insertions(+), 154 deletions(-) create mode 100644 src/orca-loader/PaymentSessionMethods.res diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 32200f7fe..d297b9135 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -117,6 +117,7 @@ let rec intentCall = ( ~sdkHandleOneClickConfirmPayment, ~counter, ~isPaymentSession=false, + ~paymentSessionRedirect="if_redirect", (), ) => { open Promise @@ -200,7 +201,11 @@ let rec intentCall = ( if handleUserError { handleOpenUrl(url.href) } else { - resolve(. data) + let failedSubmitResponse = getFailedSubmitResponse( + ~errorType=errorObj.error.type_, + ~message=errorObj.error.message, + ) + resolve(. failedSubmitResponse) } }, )->then(resolve) @@ -228,7 +233,11 @@ let rec intentCall = ( if handleUserError { handleOpenUrl(url.href) } else { - resolve(. exceptionMessage) + let failedSubmitResponse = getFailedSubmitResponse( + ~errorType="server_error", + ~message="Something went wrong", + ) + resolve(. failedSubmitResponse) } } else { let paymentIntentID = @@ -294,8 +303,10 @@ let rec intentCall = ( | (Paypal, false) => if !isPaymentSession { postSubmitResponse(~jsonData=data, ~url=url.href) - } else { + } else if paymentSessionRedirect === "always" { handleOpenUrl(url.href) + } else { + resolve(. data) } | _ => handleOpenUrl(url.href) } @@ -411,7 +422,11 @@ let rec intentCall = ( ) handleOpenUrl(url.href) } else { - resolve(. data) + let failedSubmitResponse = getFailedSubmitResponse( + ~errorType="confirm_payment_failed", + ~message="Payment failed. Try again!", + ) + resolve(. failedSubmitResponse) } } } else if intent.status == "processing" { @@ -465,7 +480,11 @@ let rec intentCall = ( ~message="Payment failed. Try again!", ) } else { - resolve(. data) + let failedSubmitResponse = getFailedSubmitResponse( + ~errorType="confirm_payment_failed", + ~message="Payment failed. Try again!", + ) + resolve(. failedSubmitResponse) } }, )->then(resolve) @@ -496,7 +515,11 @@ let rec intentCall = ( if handleUserError { handleOpenUrl(url.href) } else { - resolve(. exceptionMessage) + let failedSubmitResponse = getFailedSubmitResponse( + ~errorType="server_error", + ~message="Something went wrong", + ) + resolve(. failedSubmitResponse) } } else { let paymentIntentID = diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res index 73e257bee..909d68e59 100644 --- a/src/Utilities/Utils.res +++ b/src/Utilities/Utils.res @@ -273,6 +273,19 @@ let postSubmitResponse = (~jsonData, ~url) => { ]) } +let getFailedSubmitResponse = (~errorType, ~message) => { + [ + ( + "error", + [("type", errorType->Js.Json.string), ("message", message->Js.Json.string)] + ->Js.Dict.fromArray + ->Js.Json.object_, + ), + ] + ->Js.Dict.fromArray + ->Js.Json.object_ +} + let toCamelCase = str => { if str->Js.String2.includes(":") { str diff --git a/src/orca-loader/PaymentSession.res b/src/orca-loader/PaymentSession.res index 0fe768625..87d485baf 100644 --- a/src/orca-loader/PaymentSession.res +++ b/src/orca-loader/PaymentSession.res @@ -1,7 +1,6 @@ open Types let make = (options, ~clientSecret, ~publishableKey, ~logger: option) => { - open Promise let logger = logger->Belt.Option.getWithDefault(OrcaLogger.defaultLoggerConfig) let switchToCustomPod = GlobalVars.isInteg && @@ -12,145 +11,16 @@ let make = (options, ~clientSecret, ~publishableKey, ~logger: optionBelt.Option.getWithDefault(false) let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey, ()) - let defaultPaymentMethodList = ref(Js.Json.null) - - let customerDetailsPromise = PaymentHelpers.useCustomerDetails( - ~clientSecret, - ~publishableKey, - ~endpoint, - ~switchToCustomPod, - ~optLogger=Some(logger), - ) - - let getDefaultPaymentMethod = () => { - customerDetailsPromise - ->then(customerDetails => { - let customerPaymentMethods = - customerDetails - ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => x->Js.Dict.get("customer_payment_methods")) - ->Belt.Option.flatMap(Js.Json.decodeArray) - ->Belt.Option.getWithDefault([]) - ->Js.Array2.filter(customerPaymentMethod => { - customerPaymentMethod - ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => x->Js.Dict.get("default_payment_method_set")) - ->Belt.Option.flatMap(Js.Json.decodeBoolean) - ->Belt.Option.getWithDefault(false) - }) - let isGuestCustomer = - customerDetails - ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => x->Js.Dict.get("is_guest_customer")) - ->Belt.Option.getWithDefault(Js.Json.null) - let updatedCustomerDetails = - [ - ("customer_payment_methods", customerPaymentMethods->Js.Json.array), - ("is_guest_customer", isGuestCustomer), - ] - ->Js.Dict.fromArray - ->Js.Json.object_ - defaultPaymentMethodList := updatedCustomerDetails - updatedCustomerDetails->resolve - }) - ->catch(_err => { - let dict = - [("customer_payment_methods", []->Js.Json.array)]->Js.Dict.fromArray->Js.Json.object_ - let msg = [("customerPaymentMethods", dict)]->Js.Dict.fromArray - resolve(msg->Js.Json.object_) - }) - } - - let confirmWithDefault = payload => { - let customerPaymentMethod = - defaultPaymentMethodList.contents - ->Utils.getDictFromJson - ->Js.Dict.get("customer_payment_methods") - ->Belt.Option.flatMap(Js.Json.decodeArray) - ->Belt.Option.getWithDefault([]) - ->Belt.Array.get(0) - ->Belt.Option.flatMap(Js.Json.decodeObject) - ->Belt.Option.getWithDefault(Js.Dict.empty()) - let paymentToken = customerPaymentMethod->Utils.getJsonFromDict("payment_token", Js.Json.null) - let paymentMethod = customerPaymentMethod->Utils.getJsonFromDict("payment_method", Js.Json.null) - let paymentMethodType = - customerPaymentMethod->Utils.getJsonFromDict("payment_method_type", Js.Json.null) - - let confirmParams = - payload - ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => x->Js.Dict.get("confirmParams")) - ->Belt.Option.getWithDefault(Js.Json.null) - - let returnUrl = - confirmParams - ->Js.Json.decodeObject - ->Belt.Option.flatMap(x => x->Js.Dict.get("return_url")) - ->Belt.Option.flatMap(Js.Json.decodeString) - ->Belt.Option.getWithDefault("") - - let confirmParam: ConfirmType.confirmParams = { - return_url: returnUrl, - publishableKey, - } - - let paymentIntentID = Js.String2.split(clientSecret, "_secret_")[0] - 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 - ->Js.Json.decodeString - ->Belt.Option.getWithDefault("") { - | "apple_pay" => Applepay - | "google_pay" => Gpay - | "debit" - | "credit" - | "" => - Card - | _ => Other - } - - let broswerInfo = BrowserSpec.broswerInfo() - - let body = [ - ("client_secret", clientSecret->Js.Json.string), - ("payment_method", paymentMethod), - ("payment_token", paymentToken), - ("payment_method_type", paymentMethodType), - ] - - let bodyStr = - body->Js.Array2.concat(broswerInfo)->Js.Dict.fromArray->Js.Json.object_->Js.Json.stringify - - PaymentHelpers.intentCall( - ~fetchApi=Utils.fetchApi, - ~uri, - ~headers, - ~bodyStr, - ~confirmParam: ConfirmType.confirmParams, - ~clientSecret, - ~optLogger=Some(logger), - ~handleUserError=false, - ~paymentType, - ~iframeId="", - ~fetchMethod=Fetch.Post, - ~setIsManualRetryEnabled={(. _) => ()}, - ~switchToCustomPod=false, - ~sdkHandleOneClickConfirmPayment=false, - ~counter=0, - ~isPaymentSession=true, - (), - ) + let defaultInitPaymentSession = { + getCustomerSavedPaymentMethods: _ => + PaymentSessionMethods.getCustomerSavedPaymentMethods( + ~clientSecret, + ~publishableKey, + ~endpoint, + ~logger, + ~switchToCustomPod, + ), } - let returnObject = { - getDefaultPaymentMethod, - confirmWithDefault, - } - returnObject + defaultInitPaymentSession } diff --git a/src/orca-loader/PaymentSessionMethods.res b/src/orca-loader/PaymentSessionMethods.res new file mode 100644 index 000000000..c33ced2ca --- /dev/null +++ b/src/orca-loader/PaymentSessionMethods.res @@ -0,0 +1,180 @@ +open Types + +external customerSavedPaymentMethodsToJson: getCustomerSavedPaymentMethods => Js.Json.t = + "%identity" + +let getCustomerSavedPaymentMethods = ( + ~clientSecret, + ~publishableKey, + ~endpoint, + ~logger, + ~switchToCustomPod, +) => { + open Promise + PaymentHelpers.useCustomerDetails( + ~clientSecret, + ~publishableKey, + ~endpoint, + ~switchToCustomPod, + ~optLogger=Some(logger), + ) + ->then(customerDetails => { + let customerPaymentMethods = + customerDetails + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("customer_payment_methods")) + ->Belt.Option.flatMap(Js.Json.decodeArray) + ->Belt.Option.getWithDefault([]) + ->Js.Array2.filter(customerPaymentMethod => { + customerPaymentMethod + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("default_payment_method_set")) + ->Belt.Option.flatMap(Js.Json.decodeBoolean) + ->Belt.Option.getWithDefault(false) + }) + + switch customerPaymentMethods->Belt.Array.get(0) { + | Some(customerDefaultPaymentMethod) => + let getCustomerDefaultSavedPaymentMethodData = () => { + customerDefaultPaymentMethod + } + + let confirmWithCustomerDefaultPaymentMethod = payload => { + let customerPaymentMethod = + customerDefaultPaymentMethod + ->Js.Json.decodeObject + ->Belt.Option.getWithDefault(Js.Dict.empty()) + let paymentToken = + customerPaymentMethod->Utils.getJsonFromDict("payment_token", Js.Json.null) + let paymentMethod = + customerPaymentMethod->Utils.getJsonFromDict("payment_method", Js.Json.null) + let paymentMethodType = + customerPaymentMethod->Utils.getJsonFromDict("payment_method_type", Js.Json.null) + + let confirmParams = + payload + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("confirmParams")) + ->Belt.Option.getWithDefault(Js.Json.null) + + let redirect = + payload + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("redirect")) + ->Belt.Option.flatMap(Js.Json.decodeString) + ->Belt.Option.getWithDefault("if_required") + + let returnUrl = + confirmParams + ->Js.Json.decodeObject + ->Belt.Option.flatMap(x => x->Js.Dict.get("return_url")) + ->Belt.Option.flatMap(Js.Json.decodeString) + ->Belt.Option.getWithDefault("") + + let confirmParam: ConfirmType.confirmParams = { + return_url: returnUrl, + publishableKey, + } + + let paymentIntentID = Js.String2.split(clientSecret, "_secret_")[0] + 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 + ->Js.Json.decodeString + ->Belt.Option.getWithDefault("") { + | "apple_pay" => Applepay + | "google_pay" => Gpay + | "debit" + | "credit" + | "" => + Card + | _ => Other + } + + let broswerInfo = BrowserSpec.broswerInfo() + + let body = [ + ("client_secret", clientSecret->Js.Json.string), + ("payment_method", paymentMethod), + ("payment_token", paymentToken), + ("payment_method_type", paymentMethodType), + ] + + let bodyStr = + body->Js.Array2.concat(broswerInfo)->Js.Dict.fromArray->Js.Json.object_->Js.Json.stringify + + PaymentHelpers.intentCall( + ~fetchApi=Utils.fetchApi, + ~uri, + ~headers, + ~bodyStr, + ~confirmParam: ConfirmType.confirmParams, + ~clientSecret, + ~optLogger=Some(logger), + ~handleUserError=false, + ~paymentType, + ~iframeId="", + ~fetchMethod=Fetch.Post, + ~setIsManualRetryEnabled={(. _) => ()}, + ~switchToCustomPod=false, + ~sdkHandleOneClickConfirmPayment=false, + ~counter=0, + ~isPaymentSession=true, + ~paymentSessionRedirect=redirect, + (), + ) + } + + { + getCustomerDefaultSavedPaymentMethodData, + confirmWithCustomerDefaultPaymentMethod, + } + ->customerSavedPaymentMethodsToJson + ->resolve + | None => { + let updatedCustomerDetails = + [ + ( + "error", + [ + ("type", "no_data"->Js.Json.string), + ( + "message", + "There is no customer default saved payment method data"->Js.Json.string, + ), + ] + ->Js.Dict.fromArray + ->Js.Json.object_, + ), + ] + ->Js.Dict.fromArray + ->Js.Json.object_ + updatedCustomerDetails->resolve + } + } + }) + ->catch(err => { + let exceptionMessage = err->Utils.formatException->Js.Json.stringify + let updatedCustomerDetails = + [ + ( + "error", + [("type", "server_error"->Js.Json.string), ("message", exceptionMessage->Js.Json.string)] + ->Js.Dict.fromArray + ->Js.Json.object_, + ), + ] + ->Js.Dict.fromArray + ->Js.Json.object_ + updatedCustomerDetails->resolve + }) +} diff --git a/src/orca-loader/Types.res b/src/orca-loader/Types.res index 06e162574..bc19dcb67 100644 --- a/src/orca-loader/Types.res +++ b/src/orca-loader/Types.res @@ -39,11 +39,13 @@ type element = { create: (Js.Dict.key, Js.Json.t) => paymentElement, } -type initPaymentSession = { - getDefaultPaymentMethod: unit => Promise.t, - confirmWithDefault: Js.Json.t => Promise.t, +type getCustomerSavedPaymentMethods = { + getCustomerDefaultSavedPaymentMethodData: unit => Js.Json.t, + confirmWithCustomerDefaultPaymentMethod: Js.Json.t => Promise.t, } +type initPaymentSession = {getCustomerSavedPaymentMethods: unit => Promise.t} + type confirmParams = {return_url: string} type confirmPaymentParams = { @@ -121,17 +123,25 @@ let defaultElement = { create, } -let getDefaultPaymentMethod = () => { - Js.Promise.resolve(Js.Dict.empty()->Js.Json.object_) +let getCustomerDefaultSavedPaymentMethodData = () => { + Js.Json.null } -let confirmWithDefault = _confirmParams => { +let confirmWithCustomerDefaultPaymentMethod = _confirmParams => { Js.Promise.resolve(Js.Dict.empty()->Js.Json.object_) } +let defaultGetCustomerSavedPaymentMethods = () => { + // TODO: After rescript migration to v11, add this without TAG using enums + // Js.Promise.resolve({ + // getCustomerDefaultSavedPaymentMethodData, + // confirmWithCustomerDefaultPaymentMethod, + // }) + Js.Promise.resolve(Js.Json.null) +} + let defaultInitPaymentSession: initPaymentSession = { - getDefaultPaymentMethod, - confirmWithDefault, + getCustomerSavedPaymentMethods: defaultGetCustomerSavedPaymentMethods, } let defaultHyperInstance = {