@@ -60,9 +60,6 @@ const Confirmation: React.FC = () => {
6060 const [ pusherPayoutTxHash , setPusherPayoutTxHash ] = useState <
6161 string | undefined
6262 > ( undefined ) ;
63- const [ pusherPayoutTxHashUrl , setPusherPayoutTxHashUrl ] = useState <
64- string | undefined
65- > ( undefined ) ;
6663
6764 // Track Pusher initialization and data activity for timeout logic
6865 const [ pusherEnabled , setPusherEnabled ] = useState < boolean > ( true ) ;
@@ -73,6 +70,7 @@ const Confirmation: React.FC = () => {
7370 const payoutCompletedRef = useRef < boolean > ( false ) ;
7471 const timeoutIdRef = useRef < NodeJS . Timeout | null > ( null ) ;
7572 const pusherEnabledRef = useRef < boolean > ( true ) ;
73+ const prevRozoPaymentIdRef = useRef < string | undefined > ( undefined ) ;
7674
7775 const showProcessingPayout = useMemo ( ( ) => {
7876 const { payParams, tokenMode } = paymentStateContext ;
@@ -87,6 +85,16 @@ const Confirmation: React.FC = () => {
8785 return false ;
8886 } , [ paymentStateContext ] ) ;
8987
88+ // Compute Pusher payout URL at render time to avoid stale closure issues
89+ // (the onPayoutCompleted callback may have stale `order` reference)
90+ const computedPusherPayoutTxHashUrl = useMemo ( ( ) => {
91+ if ( pusherPayoutTxHash && order ) {
92+ const destChainId = getOrderDestChainId ( order ) ;
93+ return getChainExplorerTxUrl ( destChainId , pusherPayoutTxHash ) ;
94+ }
95+ return undefined ;
96+ } , [ pusherPayoutTxHash , order ] ) ;
97+
9098 const rozoPaymentId = useMemo ( ( ) => {
9199 const id = order ?. externalId || paymentStateContext . rozoPaymentId ;
92100 return id ;
@@ -229,19 +237,9 @@ const Confirmation: React.FC = () => {
229237 payoutCompletedSent . current = payoutKey ;
230238 payoutCompletedRef . current = true ;
231239
232- // Update local state for UI display
240+ // Update local state for UI display (URL computed at render time via useMemo)
233241 setPusherPayoutTxHash ( payload . destination_txhash ) ;
234242
235- // Generate transaction URL if we have the order
236- if ( order ) {
237- const destChainId = getOrderDestChainId ( order ) ;
238- const txUrl = getChainExplorerTxUrl (
239- destChainId ,
240- payload . destination_txhash
241- ) ;
242- setPusherPayoutTxHashUrl ( txUrl ) ;
243- }
244-
245243 // Update payment state
246244 setPaymentPayoutCompleted ( payload . destination_txhash , rozoPaymentId ) ;
247245 triggerResize ( ) ;
@@ -303,7 +301,7 @@ const Confirmation: React.FC = () => {
303301 ) ;
304302 }
305303
306- // Set up timeout to switch to polling after 1 minute if no data received
304+ // Set up timeout to switch to polling after 1 minute if payout not completed
307305 context . log ( "[CONFIRMATION] Setting up 1-minute timeout" ) ;
308306 timeoutIdRef . current = setTimeout ( ( ) => {
309307 context . log (
@@ -312,14 +310,12 @@ const Confirmation: React.FC = () => {
312310 "- checking conditions for polling switch"
313311 ) ;
314312
315- // Only switch to polling if payout hasn't been completed and Pusher is still enabled
316- if (
317- ! pusherDataReceivedRef . current &&
318- pusherEnabledRef . current &&
319- ! payoutCompletedRef . current
320- ) {
313+ // Switch to polling if payout hasn't been completed and Pusher is still enabled
314+ // Note: We check payoutCompletedRef, not pusherDataReceivedRef, because we may
315+ // receive payin data but still need to poll for payout if it doesn't arrive via Pusher
316+ if ( pusherEnabledRef . current && ! payoutCompletedRef . current ) {
321317 context . log (
322- "[CONFIRMATION] 1 minute elapsed with no Pusher data , switching to polling"
318+ "[CONFIRMATION] 1 minute elapsed without payout completion , switching to polling"
323319 ) ;
324320
325321 // Unsubscribe from Pusher
@@ -334,7 +330,6 @@ const Confirmation: React.FC = () => {
334330 setPollingEnabled ( true ) ;
335331 } else {
336332 context . log ( "[CONFIRMATION] Timeout fired but conditions not met:" , {
337- pusherDataReceived : pusherDataReceivedRef . current ,
338333 pusherEnabled : pusherEnabledRef . current ,
339334 payoutCompleted : payoutCompletedRef . current ,
340335 } ) ;
@@ -357,22 +352,30 @@ const Confirmation: React.FC = () => {
357352 // eslint-disable-next-line react-hooks/exhaustive-deps
358353 } , [ rozoPaymentId , pusherEnabled ] ) ;
359354
360- // Reset tracking when rozoPaymentId changes
355+ // Reset tracking when rozoPaymentId changes to a DIFFERENT value (not on initial mount)
361356 useEffect ( ( ) => {
362- context . log ( "[CONFIRMATION] Resetting tracking for new payment ID" ) ;
363- pusherInitializedTimeRef . current = null ;
364- pusherDataReceivedRef . current = false ;
365- payoutCompletedRef . current = false ;
366- pusherEnabledRef . current = true ;
367- setPusherEnabled ( true ) ;
368- setPollingEnabled ( false ) ;
369- if ( timeoutIdRef . current ) {
370- context . log (
371- "[CONFIRMATION] Clearing existing timeout on payment ID change"
372- ) ;
373- clearTimeout ( timeoutIdRef . current ) ;
374- timeoutIdRef . current = null ;
357+ // Only reset if we're switching to a different payment ID, not on initial mount
358+ if (
359+ prevRozoPaymentIdRef . current &&
360+ prevRozoPaymentIdRef . current !== rozoPaymentId
361+ ) {
362+ context . log ( "[CONFIRMATION] Resetting tracking for new payment ID" ) ;
363+ pusherInitializedTimeRef . current = null ;
364+ pusherDataReceivedRef . current = false ;
365+ payoutCompletedRef . current = false ;
366+ pusherEnabledRef . current = true ;
367+ setPusherEnabled ( true ) ;
368+ setPollingEnabled ( false ) ;
369+ if ( timeoutIdRef . current ) {
370+ context . log (
371+ "[CONFIRMATION] Clearing existing timeout on payment ID change"
372+ ) ;
373+ clearTimeout ( timeoutIdRef . current ) ;
374+ timeoutIdRef . current = null ;
375+ }
375376 }
377+ // Update the previous value for next comparison
378+ prevRozoPaymentIdRef . current = rozoPaymentId ;
376379 // eslint-disable-next-line react-hooks/exhaustive-deps
377380 } , [ rozoPaymentId ] ) ;
378381
@@ -519,10 +522,15 @@ const Confirmation: React.FC = () => {
519522 < ModalBody >
520523 { payoutLoading ? (
521524 < LoadingText > Processing payout...</ LoadingText >
522- ) : ( pusherPayoutTxHashUrl && pusherPayoutTxHash ) ||
525+ ) : ( computedPusherPayoutTxHashUrl &&
526+ pusherPayoutTxHash ) ||
523527 ( payoutTxHashUrl && payoutTxHash ) ? (
524528 < Link
525- href = { pusherPayoutTxHashUrl || payoutTxHashUrl || "#" }
529+ href = {
530+ computedPusherPayoutTxHashUrl ||
531+ payoutTxHashUrl ||
532+ "#"
533+ }
526534 target = "_blank"
527535 rel = "noopener noreferrer"
528536 style = { { fontSize : 14 , fontWeight : 400 } }
0 commit comments