diff --git a/public/icons/orca.svg b/public/icons/orca.svg
index 5a78ee6d1..5cb955e11 100644
--- a/public/icons/orca.svg
+++ b/public/icons/orca.svg
@@ -2538,4 +2538,9 @@ License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL
+
+
+
+
+
diff --git a/src/App.res b/src/App.res
index f4d9644d4..0d2cbcc0a 100644
--- a/src/App.res
+++ b/src/App.res
@@ -44,7 +44,14 @@ let make = () => {
let sessionId = CardUtils.getQueryParamsDictforKey(url.search, "sessionId")
let publishableKey = CardUtils.getQueryParamsDictforKey(url.search, "publishableKey")
let endpoint = CardUtils.getQueryParamsDictforKey(url.search, "endpoint")
-
+ let ephemeralKey = CardUtils.getQueryParamsDictforKey(url.search, "ephemeralKey")
+ let hyperComponentName =
+ CardUtils.getQueryParamsDictforKey(
+ url.search,
+ "hyperComponentName",
+ )->Types.getHyperComponentNameFromStr
+
+
}
| "achBankTransfer"
| "bacsBankTransfer"
diff --git a/src/CardTheme.res b/src/CardTheme.res
index e7d3efeab..771a0a826 100644
--- a/src/CardTheme.res
+++ b/src/CardTheme.res
@@ -59,6 +59,7 @@ let defaultConfig = {
locale: "auto",
fonts: [],
clientSecret: "",
+ ephemeralKey: "",
loader: Auto,
}
type recoilConfig = {
@@ -382,7 +383,7 @@ let itemToObjMapper = (
logger,
) => {
unknownKeysWarning(
- ["appearance", "fonts", "locale", "clientSecret", "loader"],
+ ["appearance", "fonts", "locale", "clientSecret", "loader", "ephemeralKey"],
dict,
"elements",
~logger,
@@ -392,6 +393,7 @@ let itemToObjMapper = (
locale: getWarningString(dict, "locale", "auto", ~logger),
fonts: getFonts("fonts", dict, logger),
clientSecret: getWarningString(dict, "clientSecret", "", ~logger),
+ ephemeralKey: getWarningString(dict, "ephemeralKey", "", ~logger),
loader: getWarningString(dict, "loader", "auto", ~logger)->getShowLoader(logger),
}
}
diff --git a/src/CardUtils.res b/src/CardUtils.res
index e98bd8ca2..017b1cfdd 100644
--- a/src/CardUtils.res
+++ b/src/CardUtils.res
@@ -363,6 +363,7 @@ let getCardBrandIcon = (cardType, paymentType) => {
| ApplePayElement
| KlarnaElement
| ExpressCheckoutElement
+ | PaymentMethodsManagement
| NONE =>
}
@@ -625,3 +626,26 @@ let useCardDetails = (~cvcNumber, ~isCvcValidValue, ~isCVCValid) => {
(isCardDetailsEmpty, isCardDetailsValid, isCardDetailsInvalid)
}, (cvcNumber, isCvcValidValue, isCVCValid))
}
+
+let getWalletBrandIcon = (customerMethod: PaymentType.customerMethods) => {
+ switch customerMethod.paymentMethodType {
+ | Some("apple_pay") =>
+ | Some("google_pay") =>
+ | Some("paypal") =>
+ | _ =>
+ }
+}
+
+let getPaymentMethodBrand = (customerMethod: PaymentType.customerMethods) => {
+ switch customerMethod.paymentMethod {
+ | "wallet" => getWalletBrandIcon(customerMethod)
+ | _ =>
+ getCardBrandIcon(
+ switch customerMethod.card.scheme {
+ | Some(ele) => ele
+ | None => ""
+ }->getCardType,
+ ""->CardThemeType.getPaymentMode,
+ )
+ }
+}
diff --git a/src/Components/PaymentElementShimmer.res b/src/Components/PaymentElementShimmer.res
index e2a7eabd6..d054f5d91 100644
--- a/src/Components/PaymentElementShimmer.res
+++ b/src/Components/PaymentElementShimmer.res
@@ -16,6 +16,20 @@ module Shimmer = {
}
}
+module SavedPaymentShimmer = {
+ @react.component
+ let make = () => {
+
+
+
+ }
+}
+
@react.component
let make = () => {
diff --git a/src/Components/SavedMethodItem.res b/src/Components/SavedMethodItem.res
new file mode 100644
index 000000000..287e712b1
--- /dev/null
+++ b/src/Components/SavedMethodItem.res
@@ -0,0 +1,88 @@
+@react.component
+let make = (~brandIcon, ~paymentItem: PaymentType.customerMethods, ~handleDelete) => {
+ let {themeObj, localeString} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
+ let {hideExpiredPaymentMethods} = Recoil.useRecoilValueFromAtom(RecoilAtoms.optionAtom)
+ let isCard = paymentItem.paymentMethod === "card"
+ let expiryMonth = paymentItem.card.expiryMonth
+ let expiryYear = paymentItem.card.expiryYear
+ let expiryDate = Date.fromString(`${expiryYear}-${expiryMonth}`)
+ let currentDate = Date.make()
+ let pickerItemClass = "PickerItem--selected"
+ let isCardExpired = isCard && expiryDate < currentDate
+ let paymentMethodType = paymentItem.paymentMethodType->Option.getOr("debit")
+
+
+
+
+
+
+
+
+
brandIcon
+
+
+ {if isCard {
+
+
{React.string(paymentItem.card.nickname)}
+
+
{React.string(`****`)}
+
{React.string(paymentItem.card.last4Digits)}
+
+
+ } else {
+
{React.string(paymentMethodType->Utils.snakeToTitleCase)}
+ }}
+
+
+
+
+
+
+ {React.string(
+ `${expiryMonth} / ${expiryYear->CardUtils.formatExpiryToTwoDigit}`,
+ )}
+
+
+
+
+
paymentItem->handleDelete}
+ />
+
+
+
+
+
+ {`*${localeString.cardExpiredText}`->React.string}
+
+
+
+
+
+
+
+
+}
diff --git a/src/Components/SavedMethods.res b/src/Components/SavedMethods.res
index 7474006f8..f3e357bad 100644
--- a/src/Components/SavedMethods.res
+++ b/src/Components/SavedMethods.res
@@ -45,15 +45,6 @@ let make = (
let {paymentToken: paymentTokenVal, customerId} = paymentToken
- let getWalletBrandIcon = (obj: PaymentType.customerMethods) => {
- switch obj.paymentMethodType {
- | Some("apple_pay") =>
- | Some("google_pay") =>
- | Some("paypal") =>
- | _ =>
- }
- }
-
let isCustomerAcceptanceRequired = useIsCustomerAcceptanceRequired(
~displaySavedPaymentMethodsCheckbox,
~isSaveCardsChecked,
@@ -63,18 +54,7 @@ let make = (
let bottomElement = {
savedMethods
->Array.mapWithIndex((obj, i) => {
- let brandIcon = switch obj.paymentMethod {
- | "wallet" => getWalletBrandIcon(obj)
- | "bank_debit" =>
- | _ =>
- getCardBrandIcon(
- switch obj.card.scheme {
- | Some(ele) => ele
- | None => ""
- }->getCardType,
- ""->CardThemeType.getPaymentMode,
- )
- }
+ let brandIcon = obj->getPaymentMethodBrand
let isActive = paymentTokenVal == obj.paymentToken
Int.toString}
@@ -266,14 +246,7 @@ let make = (
fontWeight: "400",
marginTop: "25px",
}>
-
-
-
+
} else {
{bottomElement}
diff --git a/src/Components/SavedPaymentManagement.res b/src/Components/SavedPaymentManagement.res
new file mode 100644
index 000000000..f10467622
--- /dev/null
+++ b/src/Components/SavedPaymentManagement.res
@@ -0,0 +1,71 @@
+@react.component
+let make = (~savedMethods: array, ~setSavedMethods) => {
+ open CardUtils
+ open Utils
+
+ let {iframeId} = Recoil.useRecoilValueFromAtom(RecoilAtoms.keys)
+ let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom)
+ let switchToCustomPod = Recoil.useRecoilValueFromAtom(RecoilAtoms.switchToCustomPod)
+ let logger = Recoil.useRecoilValueFromAtom(RecoilAtoms.loggerAtom)
+
+ let removeSavedMethod = paymentMethodId => {
+ savedMethods->Array.filter(savedMethod => {
+ savedMethod.paymentMethodId !== paymentMethodId
+ })
+ }
+
+ let handleDelete = (paymentItem: PaymentType.customerMethods) => {
+ handlePostMessage([
+ ("fullscreen", true->JSON.Encode.bool),
+ ("param", "paymentloader"->JSON.Encode.string),
+ ("iframeId", iframeId->JSON.Encode.string),
+ ])
+ open Promise
+ PaymentHelpers.deletePaymentMethod(
+ ~ephemeralKey=config.ephemeralKey,
+ ~paymentMethodId=paymentItem.paymentMethodId,
+ ~logger,
+ ~switchToCustomPod,
+ )
+ ->then(res => {
+ let dict = res->getDictFromJson
+ let paymentMethodId = dict->getString("payment_method_id", "")
+ let isDeleted = dict->getBool("deleted", false)
+
+ if isDeleted {
+ logger.setLogInfo(
+ ~value="Successfully Deleted Saved Payment Method",
+ ~eventName=DELETE_SAVED_PAYMENT_METHOD,
+ (),
+ )
+ setSavedMethods(_ => paymentMethodId->removeSavedMethod)
+ } else {
+ logger.setLogError(
+ ~value=res->JSON.stringify,
+ ~eventName=DELETE_SAVED_PAYMENT_METHOD,
+ (),
+ )
+ }
+ handlePostMessage([("fullscreen", false->JSON.Encode.bool)])
+ resolve()
+ })
+ ->catch(err => {
+ let exceptionMessage = err->formatException->JSON.stringify
+ logger.setLogError(
+ ~value=`Error Deleting Saved Payment Method: ${exceptionMessage}`,
+ ~eventName=DELETE_SAVED_PAYMENT_METHOD,
+ (),
+ )
+ handlePostMessage([("fullscreen", false->JSON.Encode.bool)])
+ resolve()
+ })
+ ->ignore
+ }
+
+ savedMethods
+ ->Array.mapWithIndex((obj, i) => {
+ let brandIcon = obj->getPaymentMethodBrand
+ Int.toString} paymentItem=obj brandIcon handleDelete />
+ })
+ ->React.array
+}
diff --git a/src/Hooks/CommonHooks.res b/src/Hooks/CommonHooks.res
index e25667f9b..be864c298 100644
--- a/src/Hooks/CommonHooks.res
+++ b/src/Hooks/CommonHooks.res
@@ -10,6 +10,7 @@ type element = {
}
type keys = {
clientSecret: option,
+ ephemeralKey: option,
publishableKey: string,
iframeId: string,
parentURL: string,
@@ -94,6 +95,7 @@ let updateKeys = (dict, keyPair, setKeys) => {
}
let defaultkeys = {
clientSecret: None,
+ ephemeralKey: None,
publishableKey: "",
iframeId: "",
parentURL: "*",
diff --git a/src/LoaderController.res b/src/LoaderController.res
index 6824896d1..dccdc8b54 100644
--- a/src/LoaderController.res
+++ b/src/LoaderController.res
@@ -127,6 +127,7 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
locale: config.locale,
fonts: config.fonts,
clientSecret: config.clientSecret,
+ ephemeralKey: config.ephemeralKey,
loader: config.loader,
},
themeObj: appearance.variables,
@@ -257,9 +258,11 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
let paymentOptions = dict->getDictFromObj("paymentOptions")
let clientSecret = getWarningString(paymentOptions, "clientSecret", "", ~logger)
+ let ephemeralKey = getWarningString(paymentOptions, "ephemeralKey", "", ~logger)
setKeys(prev => {
...prev,
clientSecret: Some(clientSecret),
+ ephemeralKey: Some(ephemeralKey),
})
logger.setClientSecret(clientSecret)
@@ -304,9 +307,11 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
let paymentOptions = dict->getDictFromObj("paymentOptions")
let clientSecret = getWarningString(paymentOptions, "clientSecret", "", ~logger)
+ let ephemeralKey = getWarningString(paymentOptions, "ephemeralKey", "", ~logger)
setKeys(prev => {
...prev,
clientSecret: Some(clientSecret),
+ ephemeralKey: Some(ephemeralKey),
})
logger.setClientSecret(clientSecret)
@@ -446,7 +451,8 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
setPaymentMethodList(_ => updatedState)
}
if dict->getDictIsSome("customerPaymentMethods") {
- let customerPaymentMethods = dict->PaymentType.createCustomerObjArr
+ let customerPaymentMethods =
+ dict->PaymentType.createCustomerObjArr("customerPaymentMethods")
setOptionsPayment(prev => {
...prev,
customerPaymentMethods,
@@ -457,6 +463,51 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime
Date.now() -. launchTime
}
+ let evalMethodsList = () =>
+ switch paymentMethodList {
+ | Loaded(_) =>
+ logger.setLogInfo(
+ ~value="Loaded",
+ ~eventName=LOADER_CHANGED,
+ ~latency=finalLoadLatency,
+ (),
+ )
+ | LoadError(x) =>
+ logger.setLogError(
+ ~value="LoadError: " ++ x->JSON.stringify,
+ ~eventName=LOADER_CHANGED,
+ ~latency=finalLoadLatency,
+ (),
+ )
+ | _ => ()
+ }
+
+ switch optionsPayment.customerPaymentMethods {
+ | LoadingSavedCards => ()
+ | LoadedSavedCards(list, _) =>
+ list->Array.length > 0
+ ? logger.setLogInfo(
+ ~value="Loaded",
+ ~eventName=LOADER_CHANGED,
+ ~latency=finalLoadLatency,
+ (),
+ )
+ : evalMethodsList()
+ | NoResult(_) => evalMethodsList()
+ }
+ }
+ if dict->getDictIsSome("savedPaymentMethods") {
+ let savedPaymentMethods = dict->PaymentType.createCustomerObjArr("savedPaymentMethods")
+ setOptionsPayment(prev => {
+ ...prev,
+ savedPaymentMethods,
+ })
+ let finalLoadLatency = if launchTime <= 0.0 {
+ -1.0
+ } else {
+ Date.now() -. launchTime
+ }
+
let evalMethodsList = () =>
switch paymentMethodList {
| Loaded(_) =>
diff --git a/src/PaymentManagement.res b/src/PaymentManagement.res
new file mode 100644
index 000000000..e12768323
--- /dev/null
+++ b/src/PaymentManagement.res
@@ -0,0 +1,47 @@
+open PaymentType
+open RecoilAtoms
+
+@react.component
+let make = () => {
+ let {savedPaymentMethods, displaySavedPaymentMethods} = Recoil.useRecoilValueFromAtom(optionAtom)
+ let (savedMethods, setSavedMethods) = React.useState(_ => [])
+ let (isLoading, setIsLoading) = React.useState(_ => false)
+
+ React.useEffect(() => {
+ switch savedPaymentMethods {
+ | LoadedSavedCards(savedPaymentMethods, _) => {
+ let defaultPaymentMethod =
+ savedPaymentMethods->Array.find(savedCard => savedCard.defaultPaymentMethodSet)
+
+ let savedCardsWithoutDefaultPaymentMethod = savedPaymentMethods->Array.filter(savedCard => {
+ !savedCard.defaultPaymentMethodSet
+ })
+
+ let finalSavedPaymentMethods = switch defaultPaymentMethod {
+ | Some(defaultPaymentMethod) =>
+ [defaultPaymentMethod]->Array.concat(savedCardsWithoutDefaultPaymentMethod)
+ | None => savedCardsWithoutDefaultPaymentMethod
+ }
+
+ setSavedMethods(_ => finalSavedPaymentMethods)
+ setIsLoading(_ => false)
+ }
+ | LoadingSavedCards => setIsLoading(_ => true)
+ | NoResult(_) => setIsLoading(_ => false)
+ }
+
+ None
+ }, (savedPaymentMethods, displaySavedPaymentMethods))
+
+ <>
+
+
+
+
+
+
+
+ >
+}
+
+let default = make
diff --git a/src/PaymentManagementLazy.res b/src/PaymentManagementLazy.res
new file mode 100644
index 000000000..0f893d208
--- /dev/null
+++ b/src/PaymentManagementLazy.res
@@ -0,0 +1,4 @@
+open LazyUtils
+
+type props = {}
+let make: props => React.element = reactLazy(() => import_("./PaymentManagement.bs.js"))
diff --git a/src/Payments/PreMountLoader.res b/src/Payments/PreMountLoader.res
index c22273856..305ebb686 100644
--- a/src/Payments/PreMountLoader.res
+++ b/src/Payments/PreMountLoader.res
@@ -1,11 +1,21 @@
@react.component
-let make = (~sessionId, ~publishableKey, ~clientSecret, ~endpoint) => {
+let make = (
+ ~sessionId,
+ ~publishableKey,
+ ~clientSecret,
+ ~endpoint,
+ ~ephemeralKey,
+ ~hyperComponentName: Types.hyperComponentName,
+) => {
open Utils
let (paymentMethodsResponseSent, setPaymentMethodsResponseSent) = React.useState(_ => false)
let (
customerPaymentMethodsResponseSent,
setCustomerPaymentMethodsResponseSent,
) = React.useState(_ => false)
+ let (savedPaymentMethodsResponseSent, setSavedPaymentMethodsResponseSent) = React.useState(_ =>
+ false
+ )
let (sessionTokensResponseSent, setSessionTokensResponseSent) = React.useState(_ => false)
let logger = OrcaLogger.make(
~sessionId,
@@ -15,36 +25,67 @@ let make = (~sessionId, ~publishableKey, ~clientSecret, ~endpoint) => {
(),
)
- let paymentMethodsResponse = React.useMemo0(() =>
- PaymentHelpers.fetchPaymentMethodList(
- ~clientSecret,
- ~publishableKey,
- ~logger,
- ~switchToCustomPod=false,
- ~endpoint,
- )
- )
+ let (
+ paymentMethodsResponse,
+ customerPaymentMethodsResponse,
+ sessionTokensResponse,
+ savedPaymentMethodsResponse,
+ ) = React.useMemo0(() => {
+ let paymentMethodsResponse = switch hyperComponentName {
+ | Elements =>
+ PaymentHelpers.fetchPaymentMethodList(
+ ~clientSecret,
+ ~publishableKey,
+ ~logger,
+ ~switchToCustomPod=false,
+ ~endpoint,
+ )
+ | _ => JSON.Encode.null->Promise.resolve
+ }
- let customerPaymentMethodsResponse = React.useMemo0(() =>
- PaymentHelpers.fetchCustomerPaymentMethodList(
- ~clientSecret,
- ~publishableKey,
- ~optLogger=Some(logger),
- ~switchToCustomPod=false,
- ~endpoint,
- )
- )
+ let customerPaymentMethodsResponse = switch hyperComponentName {
+ | Elements =>
+ PaymentHelpers.fetchCustomerPaymentMethodList(
+ ~clientSecret,
+ ~publishableKey,
+ ~optLogger=Some(logger),
+ ~switchToCustomPod=false,
+ ~endpoint,
+ )
+ | _ => JSON.Encode.null->Promise.resolve
+ }
- let sessionTokensResponse = React.useMemo0(() =>
- PaymentHelpers.fetchSessions(
- ~clientSecret,
- ~publishableKey,
- ~optLogger=Some(logger),
- ~switchToCustomPod=false,
- ~endpoint,
- (),
+ let sessionTokensResponse = switch hyperComponentName {
+ | Elements =>
+ PaymentHelpers.fetchSessions(
+ ~clientSecret,
+ ~publishableKey,
+ ~optLogger=Some(logger),
+ ~switchToCustomPod=false,
+ ~endpoint,
+ (),
+ )
+ | _ => JSON.Encode.null->Promise.resolve
+ }
+
+ let savedPaymentMethodsResponse = switch hyperComponentName {
+ | PaymentMethodsManagementElements =>
+ PaymentHelpers.fetchSavedPaymentMethodList(
+ ~ephemeralKey,
+ ~optLogger=Some(logger),
+ ~switchToCustomPod=false,
+ ~endpoint,
+ )
+ | _ => JSON.Encode.null->Promise.resolve
+ }
+
+ (
+ paymentMethodsResponse,
+ customerPaymentMethodsResponse,
+ sessionTokensResponse,
+ savedPaymentMethodsResponse,
)
- )
+ })
let sendPromiseData = (promise, key) => {
open Promise
@@ -55,6 +96,7 @@ let make = (~sessionId, ~publishableKey, ~clientSecret, ~endpoint) => {
| "payment_methods" => setPaymentMethodsResponseSent(_ => true)
| "session_tokens" => setSessionTokensResponseSent(_ => true)
| "customer_payment_methods" => setCustomerPaymentMethodsResponseSent(_ => true)
+ | "saved_payment_methods" => setSavedPaymentMethodsResponseSent(_ => true)
| _ => ()
}
resolve()
@@ -79,6 +121,8 @@ let make = (~sessionId, ~publishableKey, ~clientSecret, ~endpoint) => {
customerPaymentMethodsResponse->sendPromiseData("customer_payment_methods")
} else if dict->Dict.get("sendSessionTokensResponse")->Option.isSome {
sessionTokensResponse->sendPromiseData("session_tokens")
+ } else if dict->Dict.get("sendSavedPaymentMethodsResponse")->Belt.Option.isSome {
+ savedPaymentMethodsResponse->sendPromiseData("saved_payment_methods")
}
}
@@ -92,15 +136,23 @@ let make = (~sessionId, ~publishableKey, ~clientSecret, ~endpoint) => {
)
})
- React.useEffect3(() => {
+ React.useEffect4(() => {
if (
- paymentMethodsResponseSent && customerPaymentMethodsResponseSent && sessionTokensResponseSent
+ paymentMethodsResponseSent &&
+ customerPaymentMethodsResponseSent &&
+ sessionTokensResponseSent &&
+ savedPaymentMethodsResponseSent
) {
handlePostMessage([("preMountLoaderIframeUnMount", true->JSON.Encode.bool)])
Window.removeEventListener("message", handle)
}
None
- }, (paymentMethodsResponseSent, customerPaymentMethodsResponseSent, sessionTokensResponseSent))
+ }, (
+ paymentMethodsResponseSent,
+ customerPaymentMethodsResponseSent,
+ sessionTokensResponseSent,
+ savedPaymentMethodsResponseSent,
+ ))
React.null
}
diff --git a/src/RenderPaymentMethods.res b/src/RenderPaymentMethods.res
index 1207cbe1d..d02c2bbfe 100644
--- a/src/RenderPaymentMethods.res
+++ b/src/RenderPaymentMethods.res
@@ -142,6 +142,13 @@ let make = (
id="card-cvc"
isFocus
/>
+ | PaymentMethodsManagement =>
+
+
+ }>
+
+
| PaymentMethodCollectElement
| NONE => React.null
}}
diff --git a/src/Types/CardThemeType.res b/src/Types/CardThemeType.res
index 0ed4a76fd..ed526c19f 100644
--- a/src/Types/CardThemeType.res
+++ b/src/Types/CardThemeType.res
@@ -17,6 +17,7 @@ type mode =
| ApplePayElement
| KlarnaElement
| ExpressCheckoutElement
+ | PaymentMethodsManagement
| NONE
type label = Above | Floating | Never
type themeClass = {
@@ -86,6 +87,7 @@ type fonts = {
type configClass = {
appearance: appearance,
locale: string,
+ ephemeralKey: string,
clientSecret: string,
fonts: array,
loader: showLoader,
@@ -104,6 +106,7 @@ let getPaymentMode = val => {
| "paymentMethodCollect" => PaymentMethodCollectElement
| "klarna" => KlarnaElement
| "expressCheckout" => ExpressCheckoutElement
+ | "paymentMethodsManagement" => PaymentMethodsManagement
| _ => NONE
}
}
@@ -121,6 +124,7 @@ let getPaymentModeToStrMapper = val => {
| PaymentMethodCollectElement => "PaymentMethodCollectElement"
| KlarnaElement => "KlarnaElement"
| ExpressCheckoutElement => "ExpressCheckoutElement"
+ | PaymentMethodsManagement => "PaymentMethodsManagement"
| NONE => "None"
}
}
diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res
index 0f682e839..899f06583 100644
--- a/src/Types/PaymentType.res
+++ b/src/Types/PaymentType.res
@@ -152,6 +152,7 @@ type options = {
layout: layoutType,
business: business,
customerPaymentMethods: savedCardsLoadState,
+ savedPaymentMethods: savedCardsLoadState,
paymentMethodOrder: option>,
displaySavedPaymentMethodsCheckbox: bool,
displaySavedPaymentMethods: bool,
@@ -289,6 +290,7 @@ let defaultOptions = {
defaultValues: defaultDefaultValues,
business: defaultBusiness,
customerPaymentMethods: LoadingSavedCards,
+ savedPaymentMethods: LoadingSavedCards,
layout: ObjectLayout(defaultLayout),
paymentMethodOrder: None,
fields: defaultFields,
@@ -889,10 +891,10 @@ let itemToCustomerObjMapper = customerDict => {
(customerPaymentMethods, isGuestCustomer)
}
-let createCustomerObjArr = dict => {
+let createCustomerObjArr = (dict, key) => {
let customerDict =
dict
- ->Dict.get("customerPaymentMethods")
+ ->Dict.get(key)
->Option.flatMap(JSON.Decode.object)
->Option.getOr(Dict.make())
let (customerPaymentMethods, isGuestCustomer) = customerDict->itemToCustomerObjMapper
@@ -1013,6 +1015,7 @@ let itemToObjMapper = (dict, logger) => {
business: getBusiness(dict, "business", logger),
layout: getLayout(dict, "layout", logger),
customerPaymentMethods: getCustomerMethods(dict, "customerPaymentMethods"),
+ savedPaymentMethods: getCustomerMethods(dict, "customerPaymentMethods"),
paymentMethodOrder: getOptionalStrArray(dict, "paymentMethodOrder"),
fields: getFields(dict, "fields", logger),
branding: getWarningString(dict, "branding", "auto", ~logger)->getShowType(
diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res
index 5352d7a1b..9a3427104 100644
--- a/src/Utilities/PaymentHelpers.res
+++ b/src/Utilities/PaymentHelpers.res
@@ -1901,7 +1901,9 @@ let callAuthExchange = (
setOptionValue(
prev => {
...prev,
- customerPaymentMethods: createCustomerObjArr(customerListResponse),
+ customerPaymentMethods: customerListResponse->createCustomerObjArr(
+ "customerPaymentMethods",
+ ),
},
)
JSON.Encode.null->resolve
@@ -1930,3 +1932,150 @@ let callAuthExchange = (
JSON.Encode.null->resolve
})
}
+
+let fetchSavedPaymentMethodList = (
+ ~ephemeralKey,
+ ~endpoint,
+ ~optLogger,
+ ~switchToCustomPod,
+ ~isPaymentSession=false,
+) => {
+ open Promise
+ let headers = [("Content-Type", "application/json"), ("api-key", ephemeralKey)]
+ let uri = `${endpoint}/customers/payment_methods`
+ logApi(
+ ~optLogger,
+ ~url=uri,
+ ~apiLogType=Request,
+ ~eventName=SAVED_PAYMENT_METHODS_CALL_INIT,
+ ~logType=INFO,
+ ~logCategory=API,
+ ~isPaymentSession,
+ (),
+ )
+ fetchApi(
+ uri,
+ ~method=#GET,
+ ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()),
+ (),
+ )
+ ->then(res => {
+ let statusCode = res->Fetch.Response.status->Int.toString
+ if statusCode->String.charAt(0) !== "2" {
+ res
+ ->Fetch.Response.json
+ ->then(data => {
+ logApi(
+ ~optLogger,
+ ~url=uri,
+ ~data,
+ ~statusCode,
+ ~apiLogType=Err,
+ ~eventName=CUSTOMER_PAYMENT_METHODS_CALL,
+ ~logType=ERROR,
+ ~logCategory=API,
+ ~isPaymentSession,
+ (),
+ )
+ JSON.Encode.null->resolve
+ })
+ } else {
+ logApi(
+ ~optLogger,
+ ~url=uri,
+ ~statusCode,
+ ~apiLogType=Response,
+ ~eventName=CUSTOMER_PAYMENT_METHODS_CALL,
+ ~logType=INFO,
+ ~logCategory=API,
+ ~isPaymentSession,
+ (),
+ )
+ res->Fetch.Response.json
+ }
+ })
+ ->catch(err => {
+ let exceptionMessage = err->formatException
+ logApi(
+ ~optLogger,
+ ~url=uri,
+ ~apiLogType=NoResponse,
+ ~eventName=CUSTOMER_PAYMENT_METHODS_CALL,
+ ~logType=ERROR,
+ ~logCategory=API,
+ ~data=exceptionMessage,
+ ~isPaymentSession,
+ (),
+ )
+ JSON.Encode.null->resolve
+ })
+}
+
+let deletePaymentMethod = (~ephemeralKey, ~paymentMethodId, ~logger, ~switchToCustomPod) => {
+ open Promise
+ let endpoint = ApiEndpoint.getApiEndPoint()
+ let headers = [("Content-Type", "application/json"), ("api-key", ephemeralKey)]
+ let uri = `${endpoint}/payment_methods/${paymentMethodId}`
+ logApi(
+ ~optLogger=Some(logger),
+ ~url=uri,
+ ~apiLogType=Request,
+ ~eventName=DELETE_PAYMENT_METHODS_CALL_INIT,
+ ~logType=INFO,
+ ~logCategory=API,
+ (),
+ )
+ fetchApi(
+ uri,
+ ~method=#DELETE,
+ ~headers=headers->ApiEndpoint.addCustomPodHeader(~switchToCustomPod, ()),
+ (),
+ )
+ ->then(resp => {
+ let statusCode = resp->Fetch.Response.status->Int.toString
+ if statusCode->String.charAt(0) !== "2" {
+ resp
+ ->Fetch.Response.json
+ ->then(data => {
+ logApi(
+ ~optLogger=Some(logger),
+ ~url=uri,
+ ~data,
+ ~statusCode,
+ ~apiLogType=Err,
+ ~eventName=SAVED_PAYMENT_METHODS_CALL,
+ ~logType=ERROR,
+ ~logCategory=API,
+ (),
+ )
+ JSON.Encode.null->resolve
+ })
+ } else {
+ logApi(
+ ~optLogger=Some(logger),
+ ~url=uri,
+ ~statusCode,
+ ~apiLogType=Response,
+ ~eventName=SAVED_PAYMENT_METHODS_CALL,
+ ~logType=INFO,
+ ~logCategory=API,
+ (),
+ )
+ Fetch.Response.json(resp)
+ }
+ })
+ ->catch(err => {
+ let exceptionMessage = err->formatException
+ logApi(
+ ~optLogger=Some(logger),
+ ~url=uri,
+ ~apiLogType=NoResponse,
+ ~eventName=SAVED_PAYMENT_METHODS_CALL,
+ ~logType=ERROR,
+ ~logCategory=API,
+ ~data=exceptionMessage,
+ (),
+ )
+ JSON.Encode.null->resolve
+ })
+}
diff --git a/src/Utilities/Utils.res b/src/Utilities/Utils.res
index c3e79a6c2..663020fa2 100644
--- a/src/Utilities/Utils.res
+++ b/src/Utilities/Utils.res
@@ -1241,7 +1241,9 @@ let expressCheckoutComponents = ["googlePay", "payPal", "applePay", "klarna", "e
let spmComponents = ["paymentMethodCollect"]->Array.concat(expressCheckoutComponents)
let componentsForPaymentElementCreate =
- ["payment", "paymentMethodCollect"]->Array.concat(expressCheckoutComponents)
+ ["payment", "paymentMethodCollect", "paymentMethodsManagement"]->Array.concat(
+ expressCheckoutComponents,
+ )
let getIsExpressCheckoutComponent = componentType => {
expressCheckoutComponents->Array.includes(componentType)
diff --git a/src/orca-loader/Elements.res b/src/orca-loader/Elements.res
index 261075ea0..c69129e25 100644
--- a/src/orca-loader/Elements.res
+++ b/src/orca-loader/Elements.res
@@ -27,6 +27,7 @@ let make = (
let logger = logger->Option.getOr(OrcaLogger.defaultLoggerConfig)
let savedPaymentElement = Dict.make()
let localOptions = options->JSON.Decode.object->Option.getOr(Dict.make())
+
let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey, ())
let redirect = ref("if_required")
@@ -278,6 +279,7 @@ let make = (
| "applePay"
| "klarna"
| "expressCheckout"
+ | "paymentMethodsManagement"
| "payment" => ()
| str => manageErrorWarning(UNKNOWN_KEY, ~dynamicStr=`${str} type in create`, ~logger, ())
}
diff --git a/src/orca-loader/Hyper.res b/src/orca-loader/Hyper.res
index 8fff0138a..58e66940e 100644
--- a/src/orca-loader/Hyper.res
+++ b/src/orca-loader/Hyper.res
@@ -206,6 +206,7 @@ let make = (publishableKey, options: option, analyticsInfo: option {
iframeRef.contents->Array.push(ref)->ignore
}
@@ -428,6 +429,56 @@ let make = (publishableKey, options: option, analyticsInfo: optiongetString("customBackendUrl", ""),
)
}
+
+ let paymentMethodsManagementElements = paymentMethodsManagementElementsOptions => {
+ open Promise
+ let paymentMethodsManagementElementsOptionsDict =
+ paymentMethodsManagementElementsOptions->JSON.Decode.object
+ paymentMethodsManagementElementsOptionsDict
+ ->Option.forEach(x => x->Dict.set("launchTime", Date.now()->JSON.Encode.float))
+ ->ignore
+
+ let ephemeralKeyId =
+ paymentMethodsManagementElementsOptionsDict
+ ->Option.flatMap(x => x->Dict.get("ephemeralKey"))
+ ->Option.flatMap(JSON.Decode.string)
+ ->Option.getOr("")
+
+ let paymentMethodsManagementElementsOptions =
+ paymentMethodsManagementElementsOptionsDict->Option.mapOr(
+ paymentMethodsManagementElementsOptions,
+ JSON.Encode.object,
+ )
+ ephemeralKey := ephemeralKeyId
+ Promise.make((resolve, _) => {
+ logger.setEphemeralKey(ephemeralKeyId)
+ resolve(JSON.Encode.null)
+ })
+ ->then(_ => {
+ logger.setLogInfo(
+ ~value=Window.hrefWithoutSearch,
+ ~eventName=PAYMENT_MANAGEMENT_ELEMENTS_CALLED,
+ (),
+ )
+ resolve()
+ })
+ ->ignore
+
+ PaymentMethodsManagementElements.make(
+ paymentMethodsManagementElementsOptions,
+ setIframeRef,
+ ~sdkSessionId=sessionID,
+ ~publishableKey,
+ ~ephemeralKey={ephemeralKeyId},
+ ~logger=Some(logger),
+ ~analyticsMetadata,
+ ~customBackendUrl=options
+ ->Option.getOr(JSON.Encode.null)
+ ->getDictFromJson
+ ->getString("customBackendUrl", ""),
+ )
+ }
+
let confirmCardPaymentFn = (
clientSecretId: string,
data: option,
@@ -563,6 +614,7 @@ let make = (publishableKey, options: option, analyticsInfo: option, analyticsInfo: option Promise.t<'a> = "writeText"
-let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) => {
+let make = (
+ componentType,
+ options,
+ setIframeRef,
+ iframeRef,
+ mountPostMessage,
+ ~isPaymentManagementElement=false,
+) => {
try {
let mountId = ref("")
let setPaymentIframeRef = ref => {
setIframeRef(ref)
}
+ let (elementIframeWrapperDivId, elementIframeId) = if isPaymentManagementElement {
+ ("management-element", "payment-methods-management-element")
+ } else {
+ ("element", "payment-element")
+ }
+
let sdkHandleOneClickConfirmPayment =
options->getDecodedBoolFromJson(
callbackFuncForExtractingValFromDict("sdkHandleOneClickConfirmPayment"),
@@ -155,7 +168,7 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
iframeHeight->Option.getOr(JSON.Encode.null)->Utils.getFloatFromJson(200.0)
if iframeId === localSelectorString {
let elem = Window.querySelector(
- `#orca-payment-element-iframeRef-${localSelectorString}`,
+ `#orca-${elementIframeId}-iframeRef-${localSelectorString}`,
)
switch elem->Nullable.toOption {
| Some(ele) =>
@@ -205,7 +218,9 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
| None => ()
}
if id == localSelectorString {
- let elem = Window.querySelector(`#orca-element-${localSelectorString}`)
+ let elem = Window.querySelector(
+ `#orca-${elementIframeWrapperDivId}-${localSelectorString}`,
+ )
switch elem->Nullable.toOption {
| Some(ele) => ele->Window.className(currentClass.contents)
| None => ()
@@ -236,7 +251,7 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
| Some(ele) =>
ele->Window.innerHTML("")
let mainElement = Window.querySelector(
- `#orca-payment-element-iframeRef-${localSelectorString}`,
+ `#orca-${elementIframeId}-iframeRef-${localSelectorString}`,
)
let iframeURL =
fullscreenParam.contents != ""
@@ -291,7 +306,7 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
if iframeMounted->Option.isSome {
mountPostMessage(
- Window.querySelector(`#orca-payment-element-iframeRef-${localSelectorString}`),
+ Window.querySelector(`#orca-${elementIframeId}-iframeRef-${localSelectorString}`),
localSelectorString,
sdkHandleOneClickConfirmPayment,
)
@@ -305,11 +320,11 @@ let make = (componentType, options, setIframeRef, iframeRef, mountPostMessage) =
componentType->Utils.isOtherElements ? "height: 2rem;" : "height: 0;"
switch oElement->Nullable.toOption {
| Some(elem) => {
- let iframeDiv = `