Skip to content

Show why onboarding waits when a client profile already has an IBAN - #1436

Merged
ebma merged 8 commits into
stagingfrom
fix/monerium-iban-already-provisioned
Oct 21, 2026
Merged

ebma merged 8 commits into
stagingfrom
fix/monerium-iban-already-provisioned

Conversation

@ebma

@ebma ebma commented Oct 19, 2026

Copy link
Copy Markdown
Member

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 profile to 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 stayed onboarding with nothing telling the partner or the operator why.

What changes

  • Onboarding reads the profile's IBANs before requesting one. If the forwarder has an IBAN it is adopted as before. If the profile already has an IBAN on another address or chain, the account records 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 the iban.updated webhook) adopts it and clears the reason. A profile with no IBAN is requested exactly as before.
  • The wait is written only while the account still has no IBAN, so a webhook landing at the same moment cannot leave a stale reason.
  • Moving the IBAN is an operator decision taken with the partner, not automatic: every euro sent to it converts from then on, and its old address stays linked to the profile (Monerium cannot unlink), so the association monitor reports that address on every pass for the account. The docs now require a dedicated provider profile per client (no IBAN of its own, no other linked addresses).

Compatibility

  • Additive: the account snapshot (account reads and ACCOUNT_UPDATED) gains waitingReason: AccountWaitingReason | null, with one value monerium_profile_has_iban; shared types, OpenAPI and the wire-contract snapshot are updated. No route changes.
  • Migration 088 adds a nullable waiting_reason column to the B2B accounts table (no backfill).
  • After deploy, every mapped account sends one extra 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.

ebma added 8 commits October 19, 2026 15:26
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.
@netlify

netlify Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vortex-sandbox ready!

Name Link
🔨 Latest commit e526d90
🔍 Latest deploy log https://app.netlify.com/projects/vortex-sandbox/deploys/6ad623b31b59da215a636c37
😎 Deploy Preview https://deploy-preview-1436--vortex-sandbox.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vortexfi ready!

Name Link
🔨 Latest commit e526d90
🔍 Latest deploy log https://app.netlify.com/projects/vortexfi/deploys/6ad623b351858356ae76f8d0
😎 Deploy Preview https://deploy-preview-1436--vortexfi.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vrtx-dashboard canceled.

Name Link
🔨 Latest commit e526d90
🔍 Latest deploy log https://app.netlify.com/projects/vrtx-dashboard/deploys/6ad623b3e202c4015934f106

@ebma
ebma merged commit 03010b0 into staging Oct 21, 2026
6 checks passed
@ebma
ebma deleted the fix/monerium-iban-already-provisioned branch October 21, 2026 08:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant