Skip to content

feat: superposition sdk/config consumption#524

Open
Pradeep-kumar1202 wants to merge 5 commits into
mainfrom
superposition-sdk/config
Open

feat: superposition sdk/config consumption#524
Pradeep-kumar1202 wants to merge 5 commits into
mainfrom
superposition-sdk/config

Conversation

@Pradeep-kumar1202

@Pradeep-kumar1202 Pradeep-kumar1202 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Type of Change

  • Bugfix
  • Feature
  • Refactor
  • Chore
  • CI/CD
  • Docs

Summary

The PML no longer decides which payment methods to show or which fields to collect. SDK now fetches a config for the payment and uses that as the source for both — what's eligible, and what to render.

New Contract

When the SDK loads, it fetches configuration once:

GET {baseUrl}/v1/sdk/configs/web/sdk_config.json

Headers: api-key + client-secret

For each payment method and connector, the configuration defines:

  • Which payment methods and connectors are eligible
  • The exact fields to collect
  • Field metadata, including:
    • Render type
    • Display order
    • Labels and placeholders
    • Validation rules
    • Where values should be mapped in the confirm payload

What we do with it

Available everywhere — fetched once at startup and exposed through a context,so any screen reads the same config.

Eligibility

The configuration determines which payment method tiles are displayed.

  • If a payment method or connector is not present in the configuration, it is not rendered.
  • No fallback behavior exists.
  • No configuration → nothing renders.

Fields

For the selected payment method:

  1. Resolve eligible connectors from the configuration.
  2. Resolve the required fields.
  3. Render fields dynamically.

Field handling includes:

  • Grouping related fields together (card details, name, phone, etc.)
  • Rendering inputs according to their declared render type:
    • Card Number
    • Expiry
    • CVC
    • Card Network
    • Email
    • First Name / Last Name
    • Phone (+ Country Code)
    • Date / Date of Birth
    • Country
    • State
    • Dropdowns
    • Crypto Currency / Network
  • Applying labels, placeholders, and validation rules directly from the configuration

Rendering Implementation

  • Each renderer (Card, Name, Phone, Crypto, Date, Generic) selects fields based on render type and handles layout accordingly.
  • Name fields are received as a first-name/last-name pair but rendered as a single "Full Name / Card Holder Name" input and split back into first and last names during submission.
  • Standalone phone fields without a country code render independently.
  • The legacy standalone address element has been removed. Address fields are now handled through grouped or generic renderers.

Screenshots / Recordings


Affected Area & Impact

  • Client Core
  • Shared Codebase
  • Android
  • iOS

Android PR / status (if any):
iOS PR / status (if any):
**Shared Codebase PR / status (if any):**juspay/hyperswitch-sdk-utils#68

Testing

  • JS bundle built
  • Tested in Android app
  • Tested in iOS app

Notes:


Checklist

  • Tested in consuming Android app
  • Tested in consuming iOS app

Comment thread src/hooks/SuperpositionConfigHook.res Outdated
@@ -0,0 +1,35 @@

// will be checked where this logic should live
let useGetSuperpositionRawConfigs = () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to shared-code

Comment thread src/hooks/GetLocale.res Outdated
}

let lookupLocaleString = (localeObject, key) =>
switch localeObject->Obj.magic->Dict.get(key) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try removing the .magic and if not possible wrap it in try{} catch{}

let handleInputChange = (value: string) => {
let formattedValue = value //formatValue(value, phoneNumberConfig.fieldType)
phoneNumberInput.onChange(formattedValue)
phoneNumberInput.onChange(value)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for Phone-code and postal code for formatting there is a file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted as a TODO, Pick this up later

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch from 8066300 to 7aefc01 Compare June 15, 2026 11:06
Comment thread src/contexts/DynamicFieldsContext.res Outdated
| _ => ()
let isCountryDropdown =
(field.fieldRenderType === Dropdown || field.fieldRenderType === Country) &&
field.confirmRequestWritePath->String.endsWith(".country")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These many checks are not required


let isStateDropdown = (field: SuperpositionTypes.fieldConfig) =>
field.fieldRenderType === State ||
(field.fieldRenderType === Dropdown && field.confirmRequestWritePath->String.endsWith(".state"))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These many checks might not be required

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch from 0e1ae60 to 2490027 Compare June 16, 2026 17:14
@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch 3 times, most recently from 01ad827 to 262dc50 Compare June 16, 2026 19:16
Comment thread src/components/dynamic/CardElement.res Outdated
~config={validate: createFieldValidator(CardNetwork(enabledCardSchemes))},
cardNetworkPath,
~config={
validate: ?(hasNetwork && enabledCardSchemes->Array.length > 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not have a unified logic for check and render?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — extracted a useOptionalCardField(config, ~sentinel, ~validate) → (present, input, meta) hook that unifies the presence-check + real/sentinel path + conditional validator in one place.

  • The optional cvc and network fields now each resolve in a single call.
  • The render guard uses the returned present flag.

let cardExpiryMonthConfig = findField(SuperpositionTypes.CardExpiryMonth)
let cardExpiryYearConfig = findField(SuperpositionTypes.CardExpiryYear)
let cardCvcConfig = findField(SuperpositionTypes.Cvc)
let cardNetworkConfig = findField(SuperpositionTypes.CardNetwork)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't fields controlled by priority?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Previously, the CardElement logic relied on the order and priority of fields in the array (for example, assuming fields[0] was card number, fields[1] was expiry month, etc.). This approach was fragile because any change in field priority ordering it fell through to | _ => React.null
  • To make the rendering logic more robust and independent of field ordering, we have extended fieldRenderType with dedicated values such as CardExpiryMonth, CardExpiryYear, and CardNetwork. CardElement and other Special UX components now identify and consume fields based on fieldRenderType rather than array position or priority. Since fieldRenderType is a stable, explicit, and non-changing identifier, this ensures the components receive the correct fields regardless of ordering changes.

@sh-iv-am

Copy link
Copy Markdown
Collaborator

Remove all logic based on confirmRequestWritePath

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch from 262dc50 to 65dd4e1 Compare June 17, 2026 06:15
@Pradeep-kumar1202

Copy link
Copy Markdown
Contributor Author

Remove all logic based on confirmRequestWritePath

GenericTabElement.res

  • Deleted the isCountryDropdown, isStateDropdown, and isPhoneCodeDropdown helpers.
  • Switch guards are now plain | Country / | State.
  • Removed the phone-code arm entirely — PhoneCountryCode is routed to PhoneElement by FieldGrouper.classify, so that arm was dead.
  • language_preference now renders as a normal dropdown from its own dropdownOptions (dropped the includes("language_preference") label special-casing).

DynamicFieldsContext.res

  • Country-default seeding is now fieldRenderType === Country instead of ... && endsWith(".country").

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch from 65dd4e1 to 972e31a Compare June 17, 2026 06:19
maxLength=Some(7)
borderTopWidth=?{splitCardFields ? None : Some(borderWidth /. 2.)}
borderRightWidth=?{splitCardFields ? None : Some(borderWidth /. 2.)}
borderRightWidth=?{splitCardFields

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we changing this borderWidth

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Because CVC is now optional (driven by the resolved config). When CVC is present, the expiry field sits to its left in the same row and shares a half-width border (borderWidth /. 2.). When CVC is hidden (hasCvc = false), expiry becomes the right-most element, so it needs the full borderWidth on its right edge; otherwise, the row's outer border renders thin. Hence Some(hasCvc ? borderWidth /. 2. : borderWidth).

}}
</UIUtils.RenderIf>
</View>
<UIUtils.RenderIf condition={hasCvc}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly are we doing here?
Are we rendering CVC based on the superposition

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Yes — CVC is config-driven now. hasCvc = findField(Cvc)->Option.isSome, so it's true only when the resolved dynamic-fields (superposition) config includes the CVC field.
  • The card block used to assume all five sub-fields always exist; now the CVC and network fields are optional. The useField for CVC is still called unconditionally via a sentinel path (Rules of Hooks), and hasCvc only gates the visual render and its validator.

Comment thread src/hooks/SuperpositionConfigLoader.res Outdated
React.useEffect1(() => {
let cancelled = ref(false)

setResult(_ => defaultSdkConfigValue)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you setting back to the defaultSdkConfigValue?
on refetchKey changes it will be triggered, you are already using the initial state with the defaultSdkConfigValue

let (result, setResult) = React.useState(() => defaultSdkConfigValue)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may leads to broken UI

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Good catch — agreed. setResult(_ => defaultSdkConfigValue) at the top of the effect would blank the config on every refetch and cause a UI flash.
  • In the latest revision, i've removed this bespoke loader entirely. The config is now fetched the same way as PML (via NavigationRouter) and stored in AllApiDataContextNew as an option. During refetches, the previous config is retained until the new response arrives, so there's no reset-to-default state and no empty flash.
  • This file is deleted

Comment thread src/hooks/SuperpositionConfigLoader.res Outdated
}
Promise.resolve()
})
->Promise.catch(_ => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sh-iv-am should we add a retry or lets skip this time

Comment thread src/hooks/SuperpositionConfigHook.res Outdated
~appId=nativeProp.sdkParams.appId,
(),
)
if clientSecret->String.length > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we fetch sdkAuth without Client-Secret?
@sh-iv-am @Pradeep-kumar1202

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, can we send a mesage client-secret is needed if not handled in the sdkTypes

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch 2 times, most recently from c96dba3 to e3c58ca Compare June 17, 2026 14:09
Comment thread src/routes/NavigationRouter.res Outdated
if isValidConfig(parsed) {
setSdkConfigData(_ => Some(parsed))
} else {
handleSuccessFailure(~apiResStatus=PaymentConfirmTypes.defaultConfirmError, ())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we using defaultConfirmError here? Its a get call no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mark this as a TODO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if its a small change can we not do this now? Is there a big change needed for this?

Comment thread src/hooks/AllPaymentHooks.res Outdated
let apiLogWrapper = LoggerHook.useApiLogWrapper()
let baseUrl = GlobalHooks.useGetBaseUrl()()
() => {
let uri = `${baseUrl}/v1/sdk/configs/web/sdk_config.json`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is /web in the uri intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good Catch - it is not intensional it should be platform now it's modified

@Pradeep-kumar1202 Pradeep-kumar1202 force-pushed the superposition-sdk/config branch 2 times, most recently from 9d0af72 to ff6bb00 Compare June 18, 2026 05:51
manideepk90
manideepk90 previously approved these changes Jun 18, 2026
@manideepk90

Copy link
Copy Markdown
Contributor
  1. UpdateIntentHook
  2. Api in the Parallel with the accountPML, customerPML with the delayedPM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants