fix(buy): stop the first NIM payment failing on a blocked popup - #22
Merged
Merged
Conversation
Reported from a browser: the first tap on PAY WITH NIM failed with "The NIM
payment was not completed", and trying again a moment later worked.
`sendNimViaHub` awaits `loadHub()` before calling `checkout()`. On the first
payment of a session that await is a dynamic `import('@nimiq/hub-api')` over
the NETWORK, and crossing a task boundary ends the user activation the tap
granted. `checkout()` opens a popup, browsers only allow that from inside a
gesture, so the popup was blocked and the Hub rejected with nothing usable in
it. The retry worked because by then the module was cached and the await
resolved within the gesture — which is exactly the reported shape, and why it
looked intermittent rather than broken.
Adds `preloadNimiqHub()`, called from `NimPayPanel` as soon as it detects a
browser host, so the module is fetched long before anyone taps. A script fetch
raises no dialog, so this does not fall foul of the mini-app rule against
confirmations on load. The warm swallows its own errors: it is an
optimisation, and the real payment reports its own failures.
Also fixes the message that made this hard to report. A Hub rejection carrying
no message is far more often a blocked popup than a deliberate cancel, so the
fallback now says so — "The Nimiq Wallet window did not open. Check that
pop-ups are allowed for this site, then try again." — instead of "not
completed", which named no cause and suggested no action. A message the Hub
does supply still passes through untouched, and there is now a test for each.
Verification: lint clean, `tsc --noEmit` clean, 1066 tests pass (5 new).
Mutation-tested: making the preload a no-op turns 2 red, including a control
asserting that WITHOUT a warm the payment itself is what builds the Hub — the
state the bug happened in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pa7dcWrqWwkwZE2mZWSG1P
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported from a browser: the first tap on PAY WITH NIM failed with "The NIM payment was not completed", and trying again a moment later worked.
sendNimViaHubawaitsloadHub()before callingcheckout(). On the first payment of a session that await is a dynamicimport('@nimiq/hub-api')over the NETWORK, and crossing a task boundary ends the user activation the tap granted.checkout()opens a popup, browsers only allow that from inside a gesture, so the popup was blocked and the Hub rejected with nothing usable in it. The retry worked because by then the module was cached and the await resolved within the gesture — which is exactly the reported shape, and why it looked intermittent rather than broken.Adds
preloadNimiqHub(), called fromNimPayPanelas soon as it detects a browser host, so the module is fetched long before anyone taps. A script fetch raises no dialog, so this does not fall foul of the mini-app rule against confirmations on load. The warm swallows its own errors: it is an optimisation, and the real payment reports its own failures.Also fixes the message that made this hard to report. A Hub rejection carrying no message is far more often a blocked popup than a deliberate cancel, so the fallback now says so — "The Nimiq Wallet window did not open. Check that pop-ups are allowed for this site, then try again." — instead of "not completed", which named no cause and suggested no action. A message the Hub does supply still passes through untouched, and there is now a test for each.
Verification: lint clean,
tsc --noEmitclean, 1066 tests pass (5 new). Mutation-tested: making the preload a no-op turns 2 red, including a control asserting that WITHOUT a warm the payment itself is what builds the Hub — the state the bug happened in.Claude-Session: https://claude.ai/code/session_01Pa7dcWrqWwkwZE2mZWSG1P
The hole, and the fix
What this does NOT do / residual risk
Judgement calls
Issues
Closes #
Refs #
Stacking / conflicts
Verification evidence
Remaining ops steps
Checklist
mainlint/typecheck/testpass locally on the current headClosesissue — all met.env.example/ examples / error strings updated for the world this createsmain, lockfile regenerated (never hand-resolved)money-path-checklist.mdrun; payout logic compared against the other side