From fa71e609e38fbfd03c8cbc76c6702bd709c1818b Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Fri, 20 Mar 2026 21:41:12 +0530 Subject: [PATCH 1/4] feat: support customMethodNames in payment sheet --- android | 2 +- ios | 2 +- src/hooks/AllApiDataModifier.res | 20 +++++++++++++-- src/types/SdkTypes.res | 42 ++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/android b/android index b9c8e5b4..6b8118a7 160000 --- a/android +++ b/android @@ -1 +1 @@ -Subproject commit b9c8e5b4d3ad9b22d9e02bf400f29e9d898af0cd +Subproject commit 6b8118a797659d45f5050166dfa3e4f89569c435 diff --git a/ios b/ios index f042fb48..ae78d87f 160000 --- a/ios +++ b/ios @@ -1 +1 @@ -Subproject commit f042fb484714ec089468f6327aad927e316b0e18 +Subproject commit ae78d87f6786b6502c90f5cd29e8b897ac6c1f39 diff --git a/src/hooks/AllApiDataModifier.res b/src/hooks/AllApiDataModifier.res index 2e617141..1af4c7f9 100644 --- a/src/hooks/AllApiDataModifier.res +++ b/src/hooks/AllApiDataModifier.res @@ -69,6 +69,16 @@ let useAccountPaymentMethodModifier = () => { ([], []) } + let customMethodNames = nativeProp.configuration.customMethodNames + + // Returns the merchant-defined alias name for a payment method, or the default display name. + let resolveDisplayName = (paymentMethodType, defaultName) => { + customMethodNames + ->Array.find((alias: SdkTypes.alias) => alias.paymentMethodName === paymentMethodType) + ->Option.map((alias: SdkTypes.alias) => alias.aliasName) + ->Option.getOr(defaultName) + } + switch accountPaymentMethodData { | Some(accountPaymentMethodData) => accountPaymentMethodData.payment_methods->Array.reduce( @@ -150,14 +160,20 @@ let useAccountPaymentMethodModifier = () => { />, ) : tabArr->Array.push({ - name: paymentMethodData.payment_method_type->CommonUtils.getDisplayName, + name: resolveDisplayName( + paymentMethodData.payment_method_type, + paymentMethodData.payment_method_type->CommonUtils.getDisplayName, + ), componentHoc: (~isScreenFocus, ~setConfirmButtonData) => , }) | TabSheet | WidgetTabSheet => tabArr->Array.push({ - name: paymentMethodData.payment_method_type->CommonUtils.getDisplayName, + name: resolveDisplayName( + paymentMethodData.payment_method_type, + paymentMethodData.payment_method_type->CommonUtils.getDisplayName, + ), componentHoc: (~isScreenFocus, ~setConfirmButtonData) => , }) diff --git a/src/types/SdkTypes.res b/src/types/SdkTypes.res index 7edb6d10..b8eb17e2 100644 --- a/src/types/SdkTypes.res +++ b/src/types/SdkTypes.res @@ -214,6 +214,12 @@ type placeholder = { cvv: string, } +type alias = { + paymentMethodName: string, + aliasName: string, +} +type customMethodNames = array + type configurationType = { allowsDelayedPaymentMethods: bool, appearance: appearance, @@ -234,6 +240,7 @@ type configurationType = { enablePartialLoading: bool, displayMergedSavedMethods: bool, disableBranding: bool, + customMethodNames: customMethodNames, } type sdkState = @@ -836,6 +843,41 @@ let parseConfigurationDict = (configObj, from) => { }, displayMergedSavedMethods: getBool(configObj, "displayMergedSavedMethods", false), disableBranding: getBool(configObj, "disableBranding", false), + customMethodNames: { + // Android passes customMethodNames as a JSON string; iOS passes it as a native array. + // Handle both cases gracefully. + let rawValue = configObj->Dict.get("customMethodNames") + let jsonArr = switch rawValue { + | Some(v) => + switch JSON.Decode.array(v) { + | Some(arr) => arr + | None => + // Try to parse as a JSON string (Android bundle path) + switch JSON.Decode.string(v) { + | Some(str) => + let parsedValue = switch try { + Some(JSON.parseExn(str)) + } catch { + | _ => None + } { + | value => value + } + switch parsedValue->Option.flatMap(JSON.Decode.array) { + | Some(arr) => arr + | _ => [] + } + | None => [] + } + } + | None => [] + } + jsonArr + ->Belt.Array.keepMap(JSON.Decode.object) + ->Array.map(json => { + paymentMethodName: getString(json, "paymentMethodName", ""), + aliasName: getString(json, "aliasName", ""), + }) + }, } configuration } From cbb0db90f5312201d2b76c21b3b4fd209589e63f Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Sun, 29 Mar 2026 01:28:02 +0530 Subject: [PATCH 2/4] fix: decouple icon asset key from display name in customMethodNames Add iconName field to hoc type to preserve the stable payment method type key for icon asset lookup, independent of any merchant-defined aliasName. Also fix resolveDisplayName to match case-insensitively, apply alias resolution to the Saved tab, and add nativeProp to the useMemo dependency array to prevent stale closure on config changes. --- src/components/common/CustomAccordionView.res | 3 +- src/components/common/CustomTabView.res | 2 +- src/hooks/AllApiDataModifier.res | 31 +++++++++++-------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/common/CustomAccordionView.res b/src/components/common/CustomAccordionView.res index db966894..a3944687 100644 --- a/src/components/common/CustomAccordionView.res +++ b/src/components/common/CustomAccordionView.res @@ -21,7 +21,7 @@ module SectionHeader = { {section.title === "loading" ? : Option.getOr(section.title)} width=18. height=18. fill={isExpanded ? primaryColor : iconColor} />} {section.title === "loading" @@ -90,6 +90,7 @@ let make = ( let allSections = hocComponentArr->Array.mapWithIndex((hoc, index) => { AccordionView.key: index, title: hoc.name, + icon: hoc.iconName, isExpanded: expandedSections->Array.includes(index), componentHoc: hoc.componentHoc, }) diff --git a/src/components/common/CustomTabView.res b/src/components/common/CustomTabView.res index e7c022a2..f86ca652 100644 --- a/src/components/common/CustomTabView.res +++ b/src/components/common/CustomTabView.res @@ -33,7 +33,7 @@ let make = ( isLoading ? : { ) let samsungPayStatus = SamsungPay.useSamsungPayValidityHook() - React.useMemo3(() => { + React.useMemo4(() => { + let customMethodNames = nativeProp.configuration.customMethodNames + + // Returns the merchant-defined alias name for a payment method, or the default display name. + let resolveDisplayName = (paymentMethodType, defaultName) => { + customMethodNames + ->Array.find((alias: SdkTypes.alias) => alias.paymentMethodName->String.toLowerCase === paymentMethodType->String.toLowerCase) + ->Option.map((alias: SdkTypes.alias) => alias.aliasName) + ->Option.getOr(defaultName) + } + let (initialTabArr, initialElementArr) = if nativeProp.configuration.displayMergedSavedMethods { customerPaymentMethodData ->Option.map(customerPaymentMethods => { @@ -35,7 +46,8 @@ let useAccountPaymentMethodModifier = () => { customerPaymentMethods->Array.length > 0 ? [ { - name: "Saved", + name: resolveDisplayName("saved", "Saved"), + iconName: "saved", componentHoc: (~isScreenFocus, ~setConfirmButtonData) => { ([], []) } - let customMethodNames = nativeProp.configuration.customMethodNames - - // Returns the merchant-defined alias name for a payment method, or the default display name. - let resolveDisplayName = (paymentMethodType, defaultName) => { - customMethodNames - ->Array.find((alias: SdkTypes.alias) => alias.paymentMethodName === paymentMethodType) - ->Option.map((alias: SdkTypes.alias) => alias.aliasName) - ->Option.getOr(defaultName) - } - switch accountPaymentMethodData { | Some(accountPaymentMethodData) => accountPaymentMethodData.payment_methods->Array.reduce( @@ -164,6 +166,7 @@ let useAccountPaymentMethodModifier = () => { paymentMethodData.payment_method_type, paymentMethodData.payment_method_type->CommonUtils.getDisplayName, ), + iconName: paymentMethodData.payment_method_type, componentHoc: (~isScreenFocus, ~setConfirmButtonData) => , }) @@ -174,6 +177,7 @@ let useAccountPaymentMethodModifier = () => { paymentMethodData.payment_method_type, paymentMethodData.payment_method_type->CommonUtils.getDisplayName, ), + iconName: paymentMethodData.payment_method_type, componentHoc: (~isScreenFocus, ~setConfirmButtonData) => , }) @@ -195,6 +199,7 @@ let useAccountPaymentMethodModifier = () => { | None => let loadingTabElement = { name: "loading", + iconName: "loading", /* sentinel — short-circuited before Icon render */ componentHoc: (~isScreenFocus as _, ~setConfirmButtonData as _) => <> @@ -232,7 +237,7 @@ let useAccountPaymentMethodModifier = () => { | _ => ([], [], []) } } - }, (accountPaymentMethodData, customerPaymentMethodData, sessionTokenData)) + }, (accountPaymentMethodData, customerPaymentMethodData, sessionTokenData, nativeProp)) } let useAddWebPaymentButton = () => { From e006d85675e141c5e5641fc2ea76f3d10ee9a2a8 Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Sun, 29 Mar 2026 04:45:01 +0530 Subject: [PATCH 3/4] fix: iOS customMethodNames serialization + filter empty aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iOS: bump submodule to 627a5ef — replaces [[String: String]]? with [PaymentMethodAlias]? (DictionaryConverter-conforming struct) so the field survives toDictionary() reflection-based serialization. Previously silently dropped because Dictionary is not Hashable and not DictionaryConverter. - SdkTypes.res: filter out alias entries where paymentMethodName or aliasName is empty string — prevents invisible tab labels or no-op match entries from malformed merchant config. - SdkTypes.res: Belt.Array.keepMap -> Array.filterMap for RescriptCore consistency. --- ios | 2 +- src/types/SdkTypes.res | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ios b/ios index ae78d87f..627a5ef7 160000 --- a/ios +++ b/ios @@ -1 +1 @@ -Subproject commit ae78d87f6786b6502c90f5cd29e8b897ac6c1f39 +Subproject commit 627a5ef751f8a67acb12346e523294e707357f7f diff --git a/src/types/SdkTypes.res b/src/types/SdkTypes.res index b8eb17e2..d797a0a0 100644 --- a/src/types/SdkTypes.res +++ b/src/types/SdkTypes.res @@ -872,11 +872,12 @@ let parseConfigurationDict = (configObj, from) => { | None => [] } jsonArr - ->Belt.Array.keepMap(JSON.Decode.object) + ->Array.filterMap(JSON.Decode.object) ->Array.map(json => { paymentMethodName: getString(json, "paymentMethodName", ""), aliasName: getString(json, "aliasName", ""), }) + ->Array.filter(alias => alias.paymentMethodName !== "" && alias.aliasName !== "") }, } configuration From fd58bf65dd609ec86ddb0fb169ccbff449e33340 Mon Sep 17 00:00:00 2001 From: Arush Kapoor Date: Sun, 29 Mar 2026 05:03:30 +0530 Subject: [PATCH 4/4] fix: use iconName for isScrollBarOnlyCards check in CustomTabView The check used name == "Card" which breaks when a merchant aliases the credit payment method via customMethodNames. Switch to iconName == "credit" which is the stable payment_method_type key, unaffected by aliasing. --- src/components/common/CustomTabView.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/CustomTabView.res b/src/components/common/CustomTabView.res index f86ca652..7908c1ed 100644 --- a/src/components/common/CustomTabView.res +++ b/src/components/common/CustomTabView.res @@ -68,7 +68,7 @@ let make = ( let isScrollBarOnlyCards = hocComponentArr->Array.length == 1 && switch hocComponentArr->Array.get(0) { - | Some({name}) => name == "Card" + | Some({iconName}) => iconName == "credit" | None => true }