Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added Payment Session Headless #209

Merged
merged 4 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
714 changes: 388 additions & 326 deletions src/Utilities/PaymentHelpers.res

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/Utilities/Utils.res
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,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
Expand Down
3 changes: 3 additions & 0 deletions src/Window.res
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,6 @@ let isSandbox = hostname === "beta.hyperswitch.io"
let isInteg = hostname === "dev.hyperswitch.io"

let isProd = hostname === "checkout.hyperswitch.io"

type location = {replace: (. string) => unit}
@val @scope("window") external location: location = "location"
29 changes: 29 additions & 0 deletions src/orca-loader/Hyper.res
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,34 @@ let make = (publishableKey, options: option<Js.Json.t>, analyticsInfo: option<Js
Window.paymentRequest(methodData, details, optionsForPaymentRequest)
}

let initPaymentSession = paymentSessionOptions => {
open Promise

prafulkoppalkar marked this conversation as resolved.
Show resolved Hide resolved
let clientSecretId =
paymentSessionOptions
->Js.Json.decodeObject
->Belt.Option.flatMap(x => x->Js.Dict.get("clientSecret"))
->Belt.Option.flatMap(Js.Json.decodeString)
->Belt.Option.getWithDefault("")
clientSecret := clientSecretId
Js.Promise.make((~resolve, ~reject as _) => {
logger.setClientSecret(clientSecretId)
resolve(. Js.Json.null)
})
->then(_ => {
logger.setLogInfo(~value=Window.href, ~eventName=PAYMENT_SESSION_INITIATED, ())
resolve()
})
->ignore

PaymentSession.make(
paymentSessionOptions,
~clientSecret={clientSecretId},
~publishableKey,
~logger=Some(logger),
)
}

let returnObject = {
confirmOneClickPayment,
confirmPayment,
Expand All @@ -507,6 +535,7 @@ let make = (publishableKey, options: option<Js.Json.t>, analyticsInfo: option<Js
confirmCardPayment: confirmCardPaymentFn,
retrievePaymentIntent: retrievePaymentIntentFn,
paymentRequest,
initPaymentSession,
}
Window.setHyper(Window.window, returnObject)
returnObject
Expand Down
1 change: 1 addition & 0 deletions src/orca-loader/HyperLoader.res
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ let loadStripe = (str, option) => {

@val external window: {..} = "window"
window["Hyper"] = Hyper.make
window["Hyper"]["init"] = Hyper.make

let isWordpress = window["wp"] !== Js.Json.null
if !isWordpress {
Expand Down
5 changes: 1 addition & 4 deletions src/orca-loader/LoaderPaymentElement.res
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ open OrcaUtils

external eventToJson: Types.eventData => Js.Json.t = "%identity"

type location = {replace: (. string) => unit}
@val @scope("window") external location: location = "location"

@val @scope(("navigator", "clipboard"))
external writeText: string => Js.Promise.t<'a> = "writeText"

Expand Down Expand Up @@ -188,7 +185,7 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
switch eventDataObject->getOptionalJsonFromJson("openurl") {
| Some(val) => {
let url = val->getStringfromjson("")
location.replace(. url)
Window.location.replace(. url)
}
| None => ()
}
Expand Down
26 changes: 26 additions & 0 deletions src/orca-loader/PaymentSession.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
open Types

let make = (options, ~clientSecret, ~publishableKey, ~logger: option<OrcaLogger.loggerMake>) => {
let logger = logger->Belt.Option.getWithDefault(OrcaLogger.defaultLoggerConfig)
let switchToCustomPod =
GlobalVars.isInteg &&
options
->Js.Json.decodeObject
->Belt.Option.flatMap(x => x->Js.Dict.get("switchToCustomPod"))
->Belt.Option.flatMap(Js.Json.decodeBoolean)
->Belt.Option.getWithDefault(false)
let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey, ())

let defaultInitPaymentSession = {
getCustomerSavedPaymentMethods: _ =>
PaymentSessionMethods.getCustomerSavedPaymentMethods(
~clientSecret,
~publishableKey,
~endpoint,
~logger,
~switchToCustomPod,
),
}

defaultInitPaymentSession
}
180 changes: 180 additions & 0 deletions src/orca-loader/PaymentSessionMethods.res
Original file line number Diff line number Diff line change
@@ -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]->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 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
})
}
30 changes: 30 additions & 0 deletions src/orca-loader/Types.res
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ type element = {
create: (Js.Dict.key, Js.Json.t) => paymentElement,
}

type getCustomerSavedPaymentMethods = {
getCustomerDefaultSavedPaymentMethodData: unit => Js.Json.t,
confirmWithCustomerDefaultPaymentMethod: Js.Json.t => Promise.t<Js.Json.t>,
}

type initPaymentSession = {getCustomerSavedPaymentMethods: unit => Promise.t<Js.Json.t>}

type confirmParams = {return_url: string}

type confirmPaymentParams = {
Expand All @@ -56,6 +63,7 @@ type hyperInstance = {
retrievePaymentIntent: string => Js.Promise.t<Js.Json.t>,
widgets: Js.Json.t => element,
paymentRequest: Js.Json.t => Js.Json.t,
initPaymentSession: Js.Json.t => initPaymentSession,
}

let oneClickConfirmPaymentFn = (_, _) => {
Expand Down Expand Up @@ -115,6 +123,27 @@ let defaultElement = {
create,
}

let getCustomerDefaultSavedPaymentMethodData = () => {
Js.Json.null
}

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 = {
getCustomerSavedPaymentMethods: defaultGetCustomerSavedPaymentMethods,
}

let defaultHyperInstance = {
confirmOneClickPayment: oneClickConfirmPaymentFn,
confirmPayment: confirmPaymentFn,
Expand All @@ -123,6 +152,7 @@ let defaultHyperInstance = {
elements: _ev => defaultElement,
widgets: _ev => defaultElement,
paymentRequest: _ev => Js.Json.null,
initPaymentSession: _ev => defaultInitPaymentSession,
}

type eventType =
Expand Down
2 changes: 2 additions & 0 deletions src/orca-log-catcher/OrcaLogger.res
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type eventName =
| DISPLAY_VOUCHER
| PAYMENT_METHODS_RESPONSE
| LOADER_CHANGED
| PAYMENT_SESSION_INITIATED

let eventNameToStrMapper = eventName => {
switch eventName {
Expand Down Expand Up @@ -113,6 +114,7 @@ let eventNameToStrMapper = eventName => {
| DISPLAY_VOUCHER => "DISPLAY_VOUCHER"
| PAYMENT_METHODS_RESPONSE => "PAYMENT_METHODS_RESPONSE"
| LOADER_CHANGED => "LOADER_CHANGED"
| PAYMENT_SESSION_INITIATED => "PAYMENT_SESSION_INITIATED"
}
}

Expand Down
Loading