Skip to content

Expose MergeProcessing state on RealUnit registration during account merge#3848

Closed
TaprootFreak wants to merge 1 commit into
developfrom
fix/realunit-registration-merge-processing
Closed

Expose MergeProcessing state on RealUnit registration during account merge#3848
TaprootFreak wants to merge 1 commit into
developfrom
fix/realunit-registration-merge-processing

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Summary

After an account merge (new RealUnit wallet signs up with an email that already belongs to an existing account), GET /v1/realunit/registration briefly returned state=NewRegistration with an empty userData — because mergeUserData hasn't re-parented the merged-in KYC steps/personal data yet. The RealUnit app misreads the absent userData as a registration failure and shows a red "wallet registration not complete" error, even though the merge succeeded.

This adds a MergeProcessing registration state so the client can render a waiting state and poll instead of inferring failure — consistent with "API as Decision Authority".

Changes

  • RealUnitRegistrationState: add MergeProcessing (realunit-registration.dto.ts), document it on the state @ApiProperty.
  • RealUnitService.getRegistrationInfo is now async and returns MergeProcessing (no userData) when accountMergeService.hasProcessingMerge(userData.id) is true — mirrors the KYC path (KycService via hasProcessingMerge). accountMergeService was already injected but unused here.
  • Tests: existing getRegistrationInfo tests converted to async; mock now provides hasProcessingMerge; new test asserts the MergeProcessing short-circuit.

Behaviour / correctness

  • The processing window is exactly the gap between executeMerge setting processingStartedAt and mergeUserData finishing (and complete() setting isCompleted). Once complete (or after the 10-min self-heal / expiration), the guard returns false and the normal states resume — no permanent stuck state.
  • Purely additive enum value; non-merge behaviour is unchanged.

Backwards compatibility

Old app versions throw on an unknown state value — but only inside the merge-processing window, where they already fail today (empty userData → error). So there is no regression; only updated app versions get the improved waiting UX.

Pair-PR

This is the backend half. The RealUnit app companion PR (consume MergeProcessing → render the existing merge-processing waiting screen; make the state enum fromJson tolerant) follows on RealUnitCH/app after this lands on develop (pair-PR discipline).

PR completeness

  • Migration — n/a (no entity/column change)
  • Environment/Infrastructure — n/a
  • Service/DTO update (new state on the registration contract)
  • Frontend synchronization — companion app PR to follow

Test plan

  • npx jest realunit.service (32/32 pass)
  • npm run type-check, eslint, prettier --check clean
  • After merge to develop: trigger a merge on DEV and confirm GET /v1/realunit/registration returns state=MergeProcessing during the processing window

@TaprootFreak TaprootFreak marked this pull request as ready for review June 9, 2026 11:37
@TaprootFreak TaprootFreak marked this pull request as draft June 9, 2026 11:59
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Closing: the runtime root cause turned out to be different. PROD App Insights shows GET /v1/realunit/registration returns 200 (state=NewRegistration) and POST /v1/realunit/register/wallet returns 400 'No RealUnit registration found' — the merged account has no prior RealUnit registration, so the email-verification flow calls the wrong endpoint (register/wallet instead of the full register/complete form). This is an app-only fix in the email-verification cubit; the MergeProcessing approach here does not address it. A corrected PR follows.

TaprootFreak added a commit to RealUnitCH/app that referenced this pull request Jun 9, 2026
## Summary
Fixes the post-account-merge "Wallet registration not complete" error
(red banner) in the RealUnit app, the CONTRIBUTING-aligned way.

When a new wallet signs up with an email that already belongs to an
existing **DFX** account (account merge) and that account has **no prior
RealUnit registration**, `GET /v1/realunit/registration` returns
`state=NewRegistration`. The email-verification step, however, called
`registerWallet` (`POST /register/wallet`) **unconditionally** — that
endpoint only *adds* a wallet to an **existing** registration, so the
API returns `400 "No RealUnit registration found"`.

## Root cause — runtime-confirmed (PROD App Insights)
- `GET /v1/realunit/registration` → **200** (state=NewRegistration,
userData present from existing KYC)
- `POST /v1/realunit/register/wallet` → **400 `{"message":"No RealUnit
registration found"}`** (×4 retries)
- recovery: the user reached the KYC registration form → `POST
/v1/realunit/register/complete` → **201**
- DB: the merged account had no `RealUnitRegistration` step until
`register/complete` created it. 7-day breadth: 7× this 400 (affects
every merge into a DFX account without a prior RealUnit registration).

## Fix (app-only) — single source of registration routing
The email-verification flow is reduced to its actual job: **confirm the
merge** (detect the JWT account change) and hand back to the KYC flow.
`KycCubit` is now the only place that interprets the registration
`state` and routes it (addWallet → link wallet, NewRegistration → full
registration form, AlreadyRegistered → forward) — per CONTRIBUTING.md
"API as Decision Authority". This removes the duplicated, unconditional
`register/wallet` call.

Dead code removed accordingly: `_completeRegistration`,
`_mergeDetected`, the `RealUnitRegistrationService` dependency, the
`KycEmailVerificationRegistrationFailure` state, and the now-unused i18n
key `registerEmailVerificationRegistrationFailed` (de + en).

## Tests
- email cubit (simplified): same account → Failure (link not visited);
changed account → Success (merge confirmed; no registration here); retry
(Failure → Success).
- `kyc_step_states_test` updated for the removed state.

## Test plan
- [x] `flutter analyze` clean
- [x] `flutter test --exclude-tags golden` — full suite passes (2312)
- [x] Coverage Floor Gate replicated locally — scoped **lines 100.0%**
(floor 100)
- [ ] DEV end-to-end: merge into a DFX account without RealUnit
registration → no red error → lands on the registration form →
register/complete

Supersedes #711 (minimal variant) and the earlier incorrect
MergeProcessing PRs DFXswiss/api#3848 + #709.
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.

2 participants