From 5d07d17ad504e0114e6ba176dfafd875d9291114 Mon Sep 17 00:00:00 2001 From: Hari Challa Date: Tue, 24 Mar 2026 12:58:22 +0530 Subject: [PATCH 1/4] feat: Error Display 1st commit --- src/Components/PaymentErrorBanner.res | 36 +++++++++++++++++++++++++++ src/PaymentElement.res | 7 ++++++ src/Payments/CardPayment.res | 8 ++++++ src/Types/PaymentConfirmTypes.res | 13 ++++++++++ src/Utilities/PaymentHelpers.res | 16 ++++++++++++ src/Utilities/RecoilAtoms.res | 1 + 6 files changed, 81 insertions(+) create mode 100644 src/Components/PaymentErrorBanner.res diff --git a/src/Components/PaymentErrorBanner.res b/src/Components/PaymentErrorBanner.res new file mode 100644 index 000000000..99cf40759 --- /dev/null +++ b/src/Components/PaymentErrorBanner.res @@ -0,0 +1,36 @@ +@react.component +let make = () => { + let errorMessage = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentFailedErrorMessage) + + String.length > 0}> +
+ + + + + + + {React.string(errorMessage)} + +
+
+} diff --git a/src/PaymentElement.res b/src/PaymentElement.res index d89aa9624..a0f69170c 100644 --- a/src/PaymentElement.res +++ b/src/PaymentElement.res @@ -37,6 +37,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod RecoilAtoms.showPaymentMethodsScreen, ) let (paymentToken, setPaymentToken) = Recoil.useRecoilState(RecoilAtoms.paymentTokenAtom) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(RecoilAtoms.paymentFailedErrorMessage) let (paymentMethodListValue, setPaymentMethodListValue) = Recoil.useRecoilState( paymentMethodListValue, ) @@ -260,6 +261,11 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod None }, (selectedOption, cardOptions, dropDownOptions, showAllPaymentMethods, layoutClass)) + React.useEffect(() => { + setPaymentFailedErrorMessage(_ => "") + None + }, [selectedOption]) + let isSelectedOptionValid = React.useMemo(() => { selectedOption !== "" && paymentOptions->Array.includes(selectedOption) }, (paymentOptions, selectedOption)) @@ -606,6 +612,7 @@ let make = (~cardProps, ~expiryProps, ~cvcProps, ~paymentType: CardThemeType.mod }} +
diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index 481241ac7..f433da56c 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -86,6 +86,9 @@ let make = ( ) let setComplete = Recoil.useSetRecoilState(RecoilAtoms.fieldsComplete) let blockedBinsList = Recoil.useRecoilValueFromAtom(RecoilAtoms.blockedBins) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState( + RecoilAtoms.paymentFailedErrorMessage, + ) let (isSaveCardsChecked, setIsSaveCardsChecked) = React.useState(_ => savedPaymentMethodsCheckboxCheckedByDefault ) @@ -106,6 +109,11 @@ let make = ( None }, (cardBrand, clickToPayConfig.availableCardBrands)) + React.useEffect(() => { + setPaymentFailedErrorMessage(_ => "") + None + }, (cardNumber, cardExpiry, cvcNumber)) + let combinedCardNetworks = React.useMemo1(() => { let cardPaymentMethod = paymentMethodListValue.payment_methods diff --git a/src/Types/PaymentConfirmTypes.res b/src/Types/PaymentConfirmTypes.res index 0fa37776c..6fdbf1312 100644 --- a/src/Types/PaymentConfirmTypes.res +++ b/src/Types/PaymentConfirmTypes.res @@ -59,6 +59,7 @@ type intent = { payment_method_type: string, manualRetryAllowed: bool, connectorTransactionId: string, + userGuidanceMessage: string, } open Utils @@ -92,6 +93,7 @@ let defaultIntent = { payment_method_type: "", manualRetryAllowed: false, connectorTransactionId: "", + userGuidanceMessage: "", } let getAchCreditTransfer = (dict, str) => { @@ -186,6 +188,16 @@ let getNextAction = (dict, str) => { }) ->Option.getOr(defaultNextAction) } +let getUserGuidanceMessage = dict => { + dict + ->Dict.get("error_details") + ->Option.flatMap(JSON.Decode.object) + ->Option.flatMap(errorDetails => errorDetails->Dict.get("unified_details")) + ->Option.flatMap(JSON.Decode.object) + ->Option.flatMap(unifiedDetails => getOptionString(unifiedDetails, "user_guidance_message")) + ->Option.getOr("") +} + let itemToObjMapper = dict => { { nextAction: getNextAction(dict, "next_action"), @@ -196,5 +208,6 @@ let itemToObjMapper = dict => { payment_method_type: getString(dict, "payment_method_type", ""), manualRetryAllowed: getBool(dict, "manual_retry_allowed", false), connectorTransactionId: getString(dict, "connector_transaction_id", ""), + userGuidanceMessage: getUserGuidanceMessage(dict), } } diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 213e2b185..42e1fd299 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -333,6 +333,7 @@ let rec intentCall = ( ~iframeId, ~fetchMethod, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment, ~counter, @@ -344,6 +345,9 @@ let rec intentCall = ( ) => { open Promise let isConfirm = uri->String.includes("/confirm") + if isConfirm { + setPaymentFailedErrorMessage(_ => "") + } let isCompleteAuthorize = uri->String.includes("/complete_authorize") let isPostSessionTokens = uri->String.includes("/post_session_tokens") @@ -492,6 +496,7 @@ let rec intentCall = ( ~iframeId, ~fetchMethod=#GET, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment, ~counter=counter + 1, @@ -894,6 +899,7 @@ let rec intentCall = ( } if intent.status === "failed" { setIsManualRetryEnabled(_ => intent.manualRetryAllowed) + setPaymentFailedErrorMessage(_ => intent.userGuidanceMessage) } handleProcessingStatus(paymentType, sdkHandleOneClickConfirmPayment) } else if !isPaymentSession { @@ -965,6 +971,7 @@ let rec intentCall = ( ~iframeId, ~fetchMethod=#GET, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment, ~counter=counter + 1, @@ -1005,6 +1012,7 @@ let usePaymentSync = (optLogger: option, paymentTyp let customPodUri = Recoil.useRecoilValueFromAtom(customPodUri) let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) (~handleUserError=false, ~confirmParam: ConfirmType.confirmParams, ~iframeId="") => { switch keys.clientSecret { | Some(clientSecret) => @@ -1039,6 +1047,7 @@ let usePaymentSync = (optLogger: option, paymentTyp ~iframeId, ~fetchMethod=#GET, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment=keys.sdkHandleOneClickConfirmPayment, ~counter=0, @@ -1086,6 +1095,7 @@ let useCompleteAuthorizeHandler = () => { let customPodUri = Recoil.useRecoilValueFromAtom(customPodUri) let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) let isCallbackUsedVal = Recoil.useRecoilValueFromAtom(isCompleteCallbackUsed) let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) let keys = Recoil.useRecoilValueFromAtom(keys) @@ -1155,6 +1165,7 @@ let useCompleteAuthorizeHandler = () => { ~iframeId, ~fetchMethod=#POST, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment, ~counter=0, @@ -1237,6 +1248,7 @@ let usePaymentIntent = (optLogger, paymentType) => { let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -1339,6 +1351,7 @@ let usePaymentIntent = (optLogger, paymentType) => { ~iframeId, ~fetchMethod=#POST, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment=keys.sdkHandleOneClickConfirmPayment, ~counter=0, @@ -1713,6 +1726,7 @@ let paymentIntentForPaymentSession = ( ~iframeId="", ~fetchMethod=#POST, ~setIsManualRetryEnabled={_ => ()}, + ~setPaymentFailedErrorMessage={_ => ()}, ~customPodUri, ~sdkHandleOneClickConfirmPayment=false, ~counter=0, @@ -1941,6 +1955,7 @@ let usePostSessionTokens = ( let redirectionFlags = Recoil.useRecoilValueFromAtom(RecoilAtoms.redirectionFlagsAtom) let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) + let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -2035,6 +2050,7 @@ let usePostSessionTokens = ( ~iframeId, ~fetchMethod=#POST, ~setIsManualRetryEnabled, + ~setPaymentFailedErrorMessage, ~customPodUri, ~sdkHandleOneClickConfirmPayment=keys.sdkHandleOneClickConfirmPayment, ~counter=0, diff --git a/src/Utilities/RecoilAtoms.res b/src/Utilities/RecoilAtoms.res index c2ab07a50..1fe527ab1 100644 --- a/src/Utilities/RecoilAtoms.res +++ b/src/Utilities/RecoilAtoms.res @@ -63,6 +63,7 @@ let userGiftCardNumber = Recoil.atom("userGiftCardNumber", defaultFieldValues) let userGiftCardPin = Recoil.atom("userGiftCardPin", defaultFieldValues) let fieldsComplete = Recoil.atom("fieldsComplete", false) let isManualRetryEnabled = Recoil.atom("isManualRetryEnabled", false) +let paymentFailedErrorMessage = Recoil.atom("paymentFailedErrorMessage", "") let userCurrency = Recoil.atom("userCurrency", "") let cryptoCurrencyNetworks = Recoil.atom("cryptoCurrencyNetworks", "") let isShowOrPayUsing = Recoil.atom("isShowOrPayUsing", false) From f6ecb6dd148dcb195231acbc7c68d2a7628fbd3c Mon Sep 17 00:00:00 2001 From: Hari Challa Date: Wed, 25 Mar 2026 07:28:29 +0530 Subject: [PATCH 2/4] feat: added locale support and opt in payment element prop --- src/Components/PaymentErrorBanner.res | 21 ++++++++++++++++++++- src/PaymentElement.res | 5 ++++- src/Payments/CardPayment.res | 8 -------- src/Types/PaymentType.res | 4 ++++ src/Utilities/PaymentHelpers.res | 10 +++++++++- src/Utilities/PaymentHelpersV2.res | 4 ++++ 6 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/Components/PaymentErrorBanner.res b/src/Components/PaymentErrorBanner.res index 99cf40759..4777928aa 100644 --- a/src/Components/PaymentErrorBanner.res +++ b/src/Components/PaymentErrorBanner.res @@ -1,10 +1,29 @@ +@val @scope("document") +external addDocumentEventListener: (string, _ => unit) => unit = "addEventListener" + +@val @scope("document") +external removeDocumentEventListener: (string, _ => unit) => unit = "removeEventListener" + @react.component let make = () => { let errorMessage = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentFailedErrorMessage) + let setErrorMessage = Recoil.useSetRecoilState(RecoilAtoms.paymentFailedErrorMessage) + + React.useEffect(() => { + if errorMessage->String.length > 0 { + let handler = _event => { + setErrorMessage(_ => "") + } + addDocumentEventListener("input", handler) + Some(() => removeDocumentEventListener("input", handler)) + } else { + None + } + }, [errorMessage]) String.length > 0}>
}} - + + +
diff --git a/src/Payments/CardPayment.res b/src/Payments/CardPayment.res index f433da56c..481241ac7 100644 --- a/src/Payments/CardPayment.res +++ b/src/Payments/CardPayment.res @@ -86,9 +86,6 @@ let make = ( ) let setComplete = Recoil.useSetRecoilState(RecoilAtoms.fieldsComplete) let blockedBinsList = Recoil.useRecoilValueFromAtom(RecoilAtoms.blockedBins) - let setPaymentFailedErrorMessage = Recoil.useSetRecoilState( - RecoilAtoms.paymentFailedErrorMessage, - ) let (isSaveCardsChecked, setIsSaveCardsChecked) = React.useState(_ => savedPaymentMethodsCheckboxCheckedByDefault ) @@ -109,11 +106,6 @@ let make = ( None }, (cardBrand, clickToPayConfig.availableCardBrands)) - React.useEffect(() => { - setPaymentFailedErrorMessage(_ => "") - None - }, (cardNumber, cardExpiry, cvcNumber)) - let combinedCardNetworks = React.useMemo1(() => { let cardPaymentMethod = paymentMethodListValue.payment_methods diff --git a/src/Types/PaymentType.res b/src/Types/PaymentType.res index 4dc2afd0c..41a5f7413 100644 --- a/src/Types/PaymentType.res +++ b/src/Types/PaymentType.res @@ -229,6 +229,7 @@ type options = { customMessageForCardTerms: string, showShortSurchargeMessage: bool, paymentMethodsConfig: paymentMethodsConfig, + displayPaymentFailureMessage: bool, } type payerDetails = { @@ -405,6 +406,7 @@ let defaultOptions = { customMessageForCardTerms: "", showShortSurchargeMessage: false, paymentMethodsConfig: [], + displayPaymentFailureMessage: false, } let getMessageDisplayMode = (str, key) => { @@ -1233,6 +1235,7 @@ let allowedPaymentElementOptions = [ "customMessageForCardTerms", "showShortSurchargeMessage", "paymentMethodsConfig", + "displayPaymentFailureMessage", ] let fieldsToExcludeFromMasking = ["layout", "wallets", "paymentMethodsConfig", "terms"] @@ -1327,6 +1330,7 @@ let itemToObjMapper = (dict, logger: HyperLoggerTypes.loggerMake) => { customMessageForCardTerms: getString(dict, "customMessageForCardTerms", ""), showShortSurchargeMessage: getBool(dict, "showShortSurchargeMessage", false), paymentMethodsConfig: getPaymentMethodsConfig(dict, "paymentMethodsConfig", logger), + displayPaymentFailureMessage: getBool(dict, "displayPaymentFailureMessage", false), } } diff --git a/src/Utilities/PaymentHelpers.res b/src/Utilities/PaymentHelpers.res index 42e1fd299..9dd00385a 100644 --- a/src/Utilities/PaymentHelpers.res +++ b/src/Utilities/PaymentHelpers.res @@ -1013,11 +1013,12 @@ let usePaymentSync = (optLogger: option, paymentTyp let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) (~handleUserError=false, ~confirmParam: ConfirmType.confirmParams, ~iframeId="") => { switch keys.clientSecret { | Some(clientSecret) => let paymentIntentID = clientSecret->Utils.getPaymentId - let headers = [("Content-Type", "application/json")] + let headers = [("Content-Type", "application/json"), ("Accept-Language", config.locale)] switch keys.sdkAuthorization->Utils.getNonEmptyOption { | Some(_) => () @@ -1099,6 +1100,7 @@ let useCompleteAuthorizeHandler = () => { let isCallbackUsedVal = Recoil.useRecoilValueFromAtom(isCompleteCallbackUsed) let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) let keys = Recoil.useRecoilValueFromAtom(keys) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) ( ~clientSecret: option, @@ -1126,6 +1128,8 @@ let useCompleteAuthorizeHandler = () => { ] } + finalHeaders->Array.push(("Accept-Language", config.locale)) + let sdkAuth = switch ( keys.sdkAuthorization->Utils.getNonEmptyOption, sdkAuthorization->Utils.getNonEmptyOption, @@ -1249,6 +1253,7 @@ let usePaymentIntent = (optLogger, paymentType) => { let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -1264,6 +1269,7 @@ let usePaymentIntent = (optLogger, paymentType) => { let headers = { let baseHeaders = [ ("X-Client-Source", paymentTypeFromUrl->CardThemeType.getPaymentModeToStrMapper), + ("Accept-Language", config.locale), ] switch keys.sdkAuthorization->Utils.getNonEmptyOption { | Some(sdkAuth) => baseHeaders->Array.push(("Authorization", sdkAuth)) @@ -1956,6 +1962,7 @@ let usePostSessionTokens = ( let setIsManualRetryEnabled = Recoil.useSetRecoilState(isManualRetryEnabled) let setPaymentFailedErrorMessage = Recoil.useSetRecoilState(paymentFailedErrorMessage) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -1972,6 +1979,7 @@ let usePostSessionTokens = ( let headers = [ ("Content-Type", "application/json"), ("X-Client-Source", paymentTypeFromUrl->CardThemeType.getPaymentModeToStrMapper), + ("Accept-Language", config.locale), ] let body = [ diff --git a/src/Utilities/PaymentHelpersV2.res b/src/Utilities/PaymentHelpersV2.res index 26fe96dac..e821ab860 100644 --- a/src/Utilities/PaymentHelpersV2.res +++ b/src/Utilities/PaymentHelpersV2.res @@ -392,6 +392,7 @@ let useSaveCard = (optLogger: option, paymentType: let customPodUri = Recoil.useRecoilValueFromAtom(customPodUri) let isCallbackUsedVal = Recoil.useRecoilValueFromAtom(RecoilAtoms.isCompleteCallbackUsed) let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -404,6 +405,7 @@ let useSaveCard = (optLogger: option, paymentType: ("Content-Type", "application/json"), ("Authorization", `publishable-key=${keys.publishableKey},client-secret=${pmClientSecret}`), ("x-profile-id", keys.profileId), + ("Accept-Language", config.locale), ] let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey=confirmParam.publishableKey) let uri = `${endpoint}/v1/payment-method-sessions/${pmSessionId}/confirm` @@ -455,6 +457,7 @@ let useUpdateCard = (optLogger: option, paymentType let customPodUri = Recoil.useRecoilValueFromAtom(customPodUri) let isCallbackUsedVal = Recoil.useRecoilValueFromAtom(RecoilAtoms.isCompleteCallbackUsed) let redirectionFlags = Recoil.useRecoilValueFromAtom(redirectionFlagsAtom) + let {config} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) ( ~handleUserError=false, ~bodyArr: array<(string, JSON.t)>, @@ -467,6 +470,7 @@ let useUpdateCard = (optLogger: option, paymentType ("Content-Type", "application/json"), ("Authorization", `publishable-key=${keys.publishableKey},client-secret=${pmClientSecret}`), ("x-profile-id", keys.profileId), + ("Accept-Language", config.locale), ] let endpoint = ApiEndpoint.getApiEndPoint(~publishableKey=confirmParam.publishableKey) let uri = `${endpoint}/v1/payment-method-sessions/${pmSessionId}/update-saved-payment-method` From 5ca6176123f3b204d043938332588c44ce4a1ee9 Mon Sep 17 00:00:00 2001 From: Hari Challa Date: Sun, 29 Mar 2026 16:14:11 +0530 Subject: [PATCH 3/4] chore: Added theme support and localisation support --- src/Components/PaymentErrorBanner.res | 75 +++++++++++++++++---------- src/LoaderController.res | 10 +++- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/src/Components/PaymentErrorBanner.res b/src/Components/PaymentErrorBanner.res index 4777928aa..69ddc381f 100644 --- a/src/Components/PaymentErrorBanner.res +++ b/src/Components/PaymentErrorBanner.res @@ -8,6 +8,7 @@ external removeDocumentEventListener: (string, _ => unit) => unit = "removeEvent let make = () => { let errorMessage = Recoil.useRecoilValueFromAtom(RecoilAtoms.paymentFailedErrorMessage) let setErrorMessage = Recoil.useSetRecoilState(RecoilAtoms.paymentFailedErrorMessage) + let {themeObj} = Recoil.useRecoilValueFromAtom(RecoilAtoms.configAtom) React.useEffect(() => { if errorMessage->String.length > 0 { @@ -15,41 +16,61 @@ let make = () => { setErrorMessage(_ => "") } addDocumentEventListener("input", handler) - Some(() => removeDocumentEventListener("input", handler)) + addDocumentEventListener("click", handler) + Some( + () => { + removeDocumentEventListener("input", handler) + removeDocumentEventListener("click", handler) + }, + ) } else { None } }, [errorMessage]) String.length > 0}> -
- - - - - - +
- {React.string(errorMessage)} - + + + + + + + {React.string(errorMessage)} + +
} diff --git a/src/LoaderController.res b/src/LoaderController.res index d0fc69ea8..b46a090d3 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -134,11 +134,17 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime optionsLocaleString == "" ? config.locale : optionsLocaleString, ) let constantString = await CardTheme.getConstantStringsObject() - let _ = await S3Utils.initializeCountryData(~locale=config.locale, ~logger) + let resolvedLocale = + optionsLocaleString != "" + ? optionsLocaleString + : config.locale === "auto" + ? Window.Navigator.language + : config.locale + let _ = await S3Utils.initializeCountryData(~locale=resolvedLocale, ~logger) setConfig(_ => { config: { appearance, - locale: config.locale === "auto" ? Window.Navigator.language : config.locale, + locale: resolvedLocale, fonts: config.fonts, clientSecret: config.clientSecret, pmClientSecret: config.pmClientSecret, From dcf0cd104e9a185fe851a70770dbc392efa3af89 Mon Sep 17 00:00:00 2001 From: Hari Challa Date: Sun, 29 Mar 2026 19:01:58 +0530 Subject: [PATCH 4/4] chore: theme support for banner --- src/Components/PaymentErrorBanner.res | 10 +++++----- src/LoaderController.res | 9 ++++++++- src/LocaleStrings/LocaleStringHelper.res | 25 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/Components/PaymentErrorBanner.res b/src/Components/PaymentErrorBanner.res index 69ddc381f..1fb88de6c 100644 --- a/src/Components/PaymentErrorBanner.res +++ b/src/Components/PaymentErrorBanner.res @@ -30,7 +30,7 @@ let make = () => { String.length > 0}>
-
{ gap: themeObj.spacingUnit, padding: themeObj.spacingTab, borderRadius: themeObj.borderRadius, - backgroundColor: "#FEF0F0", - border: `1px solid ${themeObj.colorDanger}20`, + backgroundColor: `${themeObj.colorDanger}15`, + border: `1px solid ${themeObj.colorDanger}40`, width: "100%", boxSizing: "border-box", fontFamily: themeObj.fontFamily, @@ -50,7 +50,7 @@ let make = () => { height="20" viewBox="0 0 24 24" fill="none" - stroke={themeObj.colorText} + stroke={themeObj.colorDanger} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" @@ -64,7 +64,7 @@ let make = () => { diff --git a/src/LoaderController.res b/src/LoaderController.res index b46a090d3..3e0978c50 100644 --- a/src/LoaderController.res +++ b/src/LoaderController.res @@ -134,12 +134,19 @@ let make = (~children, ~paymentMode, ~setIntegrateErrorError, ~logger, ~initTime optionsLocaleString == "" ? config.locale : optionsLocaleString, ) let constantString = await CardTheme.getConstantStringsObject() - let resolvedLocale = + let rawLocale = optionsLocaleString != "" ? optionsLocaleString : config.locale === "auto" ? Window.Navigator.language : config.locale + // Normalize locale to canonical form using the SDK's locale mapping + // Known locales (e.g., "en-GB", "fr-BE", "zh-Hant") are preserved as-is + // Unknown variants (e.g., "en-US") are mapped to their base language ("en") + let resolvedLocale = + rawLocale + ->LocaleStringHelper.mapLocalStringToTypeLocale + ->LocaleStringHelper.localeTypeToString let _ = await S3Utils.initializeCountryData(~locale=resolvedLocale, ~logger) setConfig(_ => { config: { diff --git a/src/LocaleStrings/LocaleStringHelper.res b/src/LocaleStrings/LocaleStringHelper.res index 699e2a1a7..b78339d7c 100644 --- a/src/LocaleStrings/LocaleStringHelper.res +++ b/src/LocaleStrings/LocaleStringHelper.res @@ -1,4 +1,29 @@ open LocaleStringTypes + +// Converts a locale type to the canonical string used by the backend translations table +let localeTypeToString = locale => { + switch locale { + | EN => "en" + | EN_GB => "en-GB" + | HE => "he" + | FR => "fr" + | FR_BE => "fr-BE" + | AR => "ar" + | JA => "ja" + | DE => "de" + | ES => "es" + | CA => "ca" + | PT => "pt" + | IT => "it" + | PL => "pl" + | NL => "nl" + | SV => "sv" + | RU => "ru" + | ZH => "zh" + | ZH_HANT => "zh-Hant" + } +} + let mapLocalStringToTypeLocale = val => { // First try the exact match let exactMatch = switch val->String.toLowerCase {