@@ -21,7 +21,6 @@ import {
2121 DEFAULT_DONATION_RATE ,
2222 POLL_TX_HISTORY_LOOKBACK ,
2323 POLL_REQUEST_DELAY ,
24- POLL_MAX_RETRY ,
2524} from '../../util' ;
2625import { getAddressDetails } from '../../util/api-client' ;
2726
@@ -161,7 +160,6 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
161160 const [ thisPrice , setThisPrice ] = useState ( 0 ) ;
162161 const [ usdPrice , setUsdPrice ] = useState ( 0 ) ;
163162 const [ success , setSuccess ] = useState ( false ) ;
164- const [ retryCount , setRetryCount ] = useState ( 0 ) ;
165163 const { enqueueSnackbar } = useSnackbar ( ) ;
166164
167165 const [ shiftCompleted , setShiftCompleted ] = useState ( false ) ;
@@ -319,8 +317,15 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
319317
320318 let wasHidden = document . hidden ;
321319 let hiddenTimestamp = 0 ;
320+ let retryTimeoutId : NodeJS . Timeout | null = null ;
322321
323322 const handleVisibilityChange = async ( ) => {
323+ // Clear any pending retry timeout
324+ if ( retryTimeoutId ) {
325+ clearTimeout ( retryTimeoutId ) ;
326+ retryTimeoutId = null ;
327+ }
328+
324329 if ( document . hidden ) {
325330 wasHidden = true ;
326331 hiddenTimestamp = Date . now ( ) ;
@@ -352,46 +357,33 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
352357 const checkCompleted = await checkForTransactions ( ) ;
353358
354359 // If check completed successfully but payment hasn't succeeded yet,
355- // trigger retries. We might be missing the payment transaction.
360+ // Schedule a single retry after 2 seconds. This is only there to handle
361+ // the case where the transaction is discovered after the app has been
362+ // foregrounded, but before the chronik websocket is resumed. 2 seconds
363+ // should be plenty for this case which is not expected to happen under
364+ // normal circumstances.
365+ // Note that we can't check success at this stage because it is captured
366+ // and we would use a stale value. So we run the timeout unconditionally
367+ // and let the useEffect cancel it if success turns true. Worst case it
368+ // does an API call and then it's a no-op.
356369 if ( checkCompleted && ! success ) {
357- // Start retries
358- setRetryCount ( 1 ) ;
370+ retryTimeoutId = setTimeout ( async ( ) => {
371+ await checkForTransactions ( ) ;
372+ retryTimeoutId = null ;
373+ } , POLL_REQUEST_DELAY ) ;
359374 }
360375 } ;
361376
362377 document . addEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
363378
364379 return ( ) => {
365380 document . removeEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
366- } ;
367- } , [ to , thisPaymentId , success , disablePaymentId ] ) ;
368-
369- // Retry mechanism: check every second if payment hasn't succeeded yet
370- useEffect ( ( ) => {
371-
372- if ( retryCount === 0 || success || retryCount >= POLL_MAX_RETRY ) {
373- // Retry up to 5 times or until the payment succeeds. If the payment tx
374- // is not found within this time period, something has gone wrong.
375- return ;
376- }
377-
378- const intervalId = setInterval ( async ( ) => {
379- if ( success ) {
380- // Stop retries upon success
381- setRetryCount ( 0 ) ;
382- return ;
381+ if ( retryTimeoutId ) {
382+ clearTimeout ( retryTimeoutId ) ;
383+ retryTimeoutId = null ;
383384 }
384-
385- await checkForTransactions ( ) ;
386-
387- // Increment retry count for next attempt (regardless of success/error)
388- setRetryCount ( prev => prev + 1 ) ;
389- } , POLL_REQUEST_DELAY ) ;
390-
391- return ( ) => {
392- clearInterval ( intervalId ) ;
393385 } ;
394- } , [ retryCount , success ] ) ;
386+ } , [ to , thisPaymentId , success , disablePaymentId , checkForTransactions ] ) ;
395387
396388 return (
397389 < React . Fragment >
0 commit comments