Conversation
src/Utilities/PaymentHelpers.res
Outdated
| ]->getJsonFromArrayOfJson | ||
| resolve(response) | ||
| } else if intent.nextAction.type_ === "wait_screen_information" { | ||
| let displayToTimestamp = intent.nextAction.display_to_timestamp->Option.getOr(0.0) |
There was a problem hiding this comment.
When display_to_timestamp is None, it defaults to 0.0. Since currentTime >= 0.0 is always true, the polling terminates immediately on the first check, defeating the purpose of the wait screen.
Suggested Fix:
let displayToTimestamp = intent.nextAction.display_to_timestamp->Option.getOr(
Date.now() *. 1000000.0 +. 300000000000.0 // 5 minutes from now in nanoseconds
)posted by PR reviewer bot
src/Utilities/PaymentHelpers.res
Outdated
| ) | ||
| ->ignore | ||
| } | ||
| resolve(data) |
There was a problem hiding this comment.
resolve(data) executes immediately after starting the async polling chain (line 909), not waiting for polling to complete. The promise resolves before the polling result is known.
Suggested Fix:
if !isPaymentSession {
let metaData =
[("paymentMethod", paymentMethod->JSON.Encode.string)]->getJsonFromArrayOfJson
messageParentWindow([
("fullscreen", true->JSON.Encode.bool),
("param", `paymentloader`->JSON.Encode.string),
("iframeId", iframeId->JSON.Encode.string),
("metadata", metaData),
])
pollRetrievePaymentIntent(
clientSecret,
~headers=headersDict,
~publishableKey=confirmParam.publishableKey,
~logger=optLogger->Option.getOr(LoggerUtils.defaultLoggerConfig),
~customPodUri,
~sdkAuthorization,
~delayInMs=pollConfig.delay_in_secs * 1000,
~endTimestamp=Some(displayToTimestamp),
)
->Promise.then(retrievedData => {
closePaymentLoaderIfAny()
postSubmitResponse(~jsonData=retrievedData, ~url=url.href)
resolve(retrievedData)
Promise.resolve()
})
->Promise.catch(_ => {
closePaymentLoaderIfAny()
postSubmitResponse(~jsonData=data, ~url=url.href)
resolve(data)
Promise.resolve()
})
->ignore
} else {
resolve(data)
}posted by PR reviewer bot
|
|
||
| let parsePaymentMethod = methodString => { | ||
| switch methodString { | ||
| | "pix_automatico_push" => PixAutomaticoPush |
There was a problem hiding this comment.
The loader checks for "pix_automatico_push", but PaymentMethodsRecord.res and DynamicFieldsUtils.res use "pix_automatico_push_transfer". The loader will never match and always show generic "Other" text for Pix Automatico Push payments.
Suggested Fix:
let parsePaymentMethod = methodString => {
switch methodString {
| "pix_automatico_push_transfer" => PixAutomaticoPush
| _ => Other
}
}posted by PR reviewer bot
| reference: string, | ||
| } | ||
|
|
||
| type pollConfig = { |
There was a problem hiding this comment.
The frequency field is parsed from the API response but never referenced in the polling logic. This is either dead code or a missing implementation.
Suggested Fix:
Either implement frequency-based polling limits or remove the field:
// If intended to limit poll count, add to pollRetrievePaymentIntent:
if pollConfig.frequency > 0 && currentAttempt >= pollConfig.frequency {
// Stop polling, return current status
}posted by PR reviewer bot
src/Utilities/PaymentHelpers.res
Outdated
|
|
||
| if status === "succeeded" || status === "failed" { | ||
| resolve(json) | ||
| switch endTimestamp { |
There was a problem hiding this comment.
The Some(timestamp) and None branches contain ~50 lines of nearly identical polling logic, violating DRY principle.
Suggested Fix:
let rec pollRetrievePaymentIntent = (...) => {
open Promise
let isExpired = switch endTimestamp {
| Some(timestamp) =>
let currentTime = Date.now() *. 1000000.0
currentTime >= timestamp
| None => false
}
if isExpired {
retrievePaymentIntent(...)
->then(json => resolve(json))
->catch(_ => resolve(JSON.Encode.null))
} else {
retrievePaymentIntent(...)
->then(json => {
let dict = json->getDictFromJson
let status = dict->getString("status", "")
if status === "succeeded" || status === "failed" {
resolve(json)
} else {
delay(delayInMs)
->then(_val => pollRetrievePaymentIntent(..., ~endTimestamp))
}
})
->catch(e => {
Console.error2("Unable to retrieve payment due to following error", e)
pollRetrievePaymentIntent(..., ~endTimestamp)
})
}
}posted by PR reviewer bot
|
|
||
| let getLoaderTextConfig = (paymentMethod: loaderPaymentMethod) => { | ||
| switch paymentMethod { | ||
| | PixAutomaticoPush => { |
There was a problem hiding this comment.
User-facing loader text is hardcoded in English, violating the codebase's i18n pattern used elsewhere (see PaymentMethodsRecord.res and locale files).
Suggested Fix:
Add to LocaleStringTypes.res:
type localeStrings = {
...
loaderProcessingTitle: string,
loaderProcessingSubtitle: string,
loaderPixPushTitle: string,
loaderPixPushSubtitle: string,
}Then pass localeString prop to Loader component.
posted by PR reviewer bot
src/Components/Loader.res
Outdated
| } | ||
| } | ||
|
|
||
| let getLoaderTextConfig = (paymentMethod: loaderPaymentMethod) => { |
There was a problem hiding this comment.
ReScript can infer the type from pattern matching; explicit annotation is unnecessary per best practices.
Suggested Fix:
let getLoaderTextConfig = paymentMethod => {posted by PR reviewer bot
| resolve(response) | ||
| } else if intent.nextAction.type_ === "wait_screen_information" { | ||
| let displayToTimestamp = intent.nextAction.display_to_timestamp->Option.getOr(0.0) | ||
| let pollConfig = |
There was a problem hiding this comment.
The default {delay_in_secs: 2, frequency: 0} duplicates PaymentConfirmTypes.defaultPollConfig.
Suggested Fix:
let pollConfig =
intent.nextAction.poll_config->Option.getOr(PaymentConfirmTypes.defaultPollConfig)posted by PR reviewer bot
src/Utilities/PaymentHelpers.res
Outdated
| ->catch(e => { | ||
| Console.error2("Unable to retrieve payment due to following error", e) | ||
| pollRetrievePaymentIntent( | ||
| | None => |
There was a problem hiding this comment.
The None branch has no termination condition. If payment status never becomes "succeeded" or "failed", the function recurses indefinitely, causing a stack overflow or hanging the browser.
Suggested Fix:
| None =>
// Add max retries or require endTimestamp to always be Some
let maxRetries = 30
let calculatedEndTimestamp = Date.now() *. 1000.0 +. maxRetries *. delayInMs
pollRetrievePaymentIntent(
clientSecret,
~headers,
~publishableKey,
~logger,
~customPodUri,
~isForceSync,
~sdkAuthorization,
~delayInMs,
~endTimestamp=Some(calculatedEndTimestamp),
)posted by PR reviewer bot
7887105 to
2f1689e
Compare
Type of Change
Description
This PR introduces comprehensive support for Pix Automatico payments (both QR and Push variants) in the Hyperswitch Web SDK by implementing a new
wait_screen_informationnext action flow. This enables Brazilian merchants to offer seamless Pix payment experiences where users can complete transactions through their banking applications with real-time status polling and customizable waiting screen messaging.What Changed
1. Wait Screen Information Flow Implementation
The core of this PR is the implementation of the
wait_screen_informationnext action type, which provides a robust mechanism for handling asynchronous payment confirmations that require user action outside the SDK iframe.Key Components:
Enhanced Type System: Extended the
nextActiontype to supportpoll_configwith configurabledelay_in_secsandfrequencyparameters, along withdisplay_to_timestampfor timeout management.Intelligent Polling Mechanism: Implemented a sophisticated polling system in
pollRetrievePaymentIntentthat:Metadata-Driven Loader: The payment loader now receives payment method information through metadata, enabling contextual messaging based on the specific payment method being used.
Flow Architecture:
When a payment returns
wait_screen_informationas the next action:poll_config.delay_in_secssucceededorfailed)display_to_timestampretrievePaymentIntentcall fetches the latest status2. Contextual Loader Messaging
A significant UX improvement in this PR is the introduction of payment-method-specific loader messages. Rather than showing generic "Processing payment" text for all flows, the loader now displays contextual information based on the payment method.
For Pix Automatico Push:
This approach:
How did you test it?
I have tested it locally by hardcoding confirm and PML response
PML
Screen.Recording.2026-03-23.at.5.13.33.pm.mov
Checklist
npm run re:build