Repository navigation
Show why onboarding waits when a client profile already has an IBAN - #1436
Merged
Merged
Conversation
An onboarding account can wait for its IBAN on something only the partner or Vortex can resolve, and the account reads and ACCOUNT_UPDATED gave the partner no way to see that. The snapshot gains a nullable waitingReason, backed by a monerium_accounts column. The change is additive; every existing account reports null, and its ACCOUNT_UPDATED is sent once more because the snapshot hash now includes the field.
Monerium issues one IBAN per profile and refuses a second request with a 400. For a client whose profile already had an IBAN on another address, onboarding sent the request again every keeper cycle and logged an error each time, with the provider's reason redacted. Nothing told the partner or the operator why the account stayed in onboarding. The cycle's IBAN read now also looks for an IBAN elsewhere on the profile. If it finds one, the account records monerium_profile_has_iban, one operator line names the existing IBAN and the remedy, and no request is sent. Moving that IBAN changes where the partner's payments go, so it stays a human decision. The same read adopts the IBAN once it sits on the forwarder, and the iban.updated path clears the wait as well.
The onboarding invariant, the architecture's onboarding step and the partner pages did not say that the EUR provider issues one IBAN per profile, what the keeper does when a registered profile already has one, or how the partner sees that state and resolves it.
The live onboarding test also showed that an address linked to the client's profile before onboarding raises the association alert on every pass, because the provider cannot unlink it and the monitor cannot tell an existing link from a later one. The partner page now states the precondition once (no IBAN of its own, no other linked addresses), and the architecture and security spec note the alert.
The wait was written unconditionally after the IBAN read and outside the forwarder lock. An iban.updated delivery recording the moved IBAN in between would leave the row with both the IBAN and the wait, and onboarding never revisits an account that has an IBAN, so the stale wait would stay forever.
The alert offered moving the IBAN as a plain remedy. A move leaves the IBAN's old address linked to the profile, because the provider has no unlink, so the association monitor then reports that address on every pass. A fresh profile is no alternative either, since the waiting account cannot leave onboarding. The alert now presents the move as an operator decision taken with the partner and states that consequence. It words the case of an IBAN on the forwarder's own address on another chain separately, which also gains a test.
The partner docs offered two remedies, and neither works cleanly. Moving the IBAN to the forwarder leaves its old address linked, which the association monitor then reports on every pass. A fresh profile has no supported flow, because the waiting account cannot leave onboarding and the same client with another profile is a client conflict. The partner page and the API reference now say to contact Vortex. The architecture and the security spec describe the move as an operator decision taken with the partner, along with its standing alert and the fresh-profile limitation.
In the full suite the logger.error spy also holds calls from earlier tests, so the new other-chain and mid-cycle-webhook cases read another account's alert and failed. The occurrence-count assertion only passed because of test order. All three now match the alert for their own account.
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vrtx-dashboard canceled.
|
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.
Why
Found on the live sandbox on 2026-10-19: Monerium allows one IBAN per profile and answers
400 IBAN already requested or provisioned for this profileto a second request. When a partner registered a client whose Monerium profile already had an IBAN on another address, onboarding linked the forwarder and refund wallet, then re-requested an IBAN every cycle with an error log each time, and the account stayedonboardingwith nothing telling the partner or the operator why.What changes
waitingReason: monerium_profile_has_iban, the keeper logs one error line naming the IBAN, its address and what an operator can do, and no request is sent, so the refusal never happens. When the IBAN is later moved to the forwarder, the next cycle (or theiban.updatedwebhook) adopts it and clears the reason. A profile with no IBAN is requested exactly as before.Compatibility
ACCOUNT_UPDATED) gainswaitingReason: AccountWaitingReason | null, with one valuemonerium_profile_has_iban; shared types, OpenAPI and the wire-contract snapshot are updated. No route changes.waiting_reasoncolumn to the B2B accounts table (no backfill).ACCOUNT_UPDATED, because the snapshot now includes the new field (null). Production has no B2B accounts.Docs
Security spec (onboarding invariant 13, association monitor note, checklist), architecture onboarding step, partner pages 14 and 07 (contact Vortex if the account reports the new reason). The operator procedure is in the internal B2B runbook §1.5, updated separately.
Tests
onboarding.test.ts: the conflict sends no request, records the reason and logs once; a moved IBAN is adopted and clears the reason; the webhook clears it; an IBAN on another chain is detected; a concurrent IBAN wins over the wait; another client's IBAN never blocks; an unrelated request failure keeps today's retry. Each fails without the fix. Full apps/api suite 2387 pass, 0 fail; typecheck, verify, wire-contract, docs check and the api build pass. An independent review found no blockers; its points are addressed.