efc367bf - fix(kyc): scope the KYC route by URL and report unmapped steps - #933
Draft
Danswar wants to merge 4 commits into
Draft
efc367bf - fix(kyc): scope the KYC route by URL and report unmapped steps#933Danswar wants to merge 4 commits into
Danswar wants to merge 4 commits into
Conversation
Carry the KYC context in the route URL instead of go_router `extra`. The post-lock restore re-pushes the captured location by bare path and has no `extra` to give, so the flow rebuilt unscoped after a background lock: the API then computed `processStatus` over every globally required step instead of only those the entered flow needs, reporting a user who had finished Ident as still in progress for steps that do not gate buying. Report every route into the unsupported-step handoff as a non-fatal event. The handoff page already gave the user something to do, but the gap itself stayed invisible — every call in the flow returns 200, so a step name missing from the client mapping table only surfaced through a user report. The event carries the step's wire identifier and nothing else, through the existing DSN-gated crash-reporting channel.
Rename UnsupportedKycStepException to KycUnsupportedStepException to match the feature's Kyc-prefixed naming convention (KycUnsupportedStepFailure, KycUnsupportedStepPage, KycLevelRequiredException). Treat an empty-string context the same as absent so an API-supplied empty value can never overwrite a real stored context. Wrap the Sentry call in reportNonFatal in a try/catch so a reporting-layer failure can never propagate into a caller, matching the swallow-on-failure contract already used by initCrashReporting. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Normalize an empty-string context to absent in kycRouteQuery too, not just in KycCubit, so an API-supplied empty context can never produce an invented /kyc?context= query. Add sell-side coverage for context forwarding on kycRequired/registrationRequired, matching the existing buy-side test. Replace the sync-only try/catch around the Sentry call in reportNonFatal with Future.ignore(), which also discards an asynchronous rejection instead of letting it surface as an unhandled error. Fix an import-order regression introduced by the previous rename. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doc comment described the null case only; the function has treated empty the same as null since the previous commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
EN:
The KYC context now travels in the route URL instead of go_router
extra, so the flow stays scoped to the context its entry point reported even after a background lock — the restore re-pushes the captured location by bare path and had noextrato give, which rebuilt the flow unscoped. Every route into the unsupported-step handoff is now also reported as a non-fatal event carrying the step's wire identifier, so the next gap in the client's step-mapping table is visible instead of silent. No new screens, no new user-facing strings, no change to what the API is asked or told. Covers items 3 and 5 of DFXswiss/api#4556.DE:
Der KYC-Context reist neu in der Route-URL statt im go_router-
extra, damit der Flow auch nach einem Hintergrund-Lock auf den Context seines Einstiegspunkts eingegrenzt bleibt — der Restore pusht die gemerkte Location als blossen Pfad und hatte keinextramitzugeben, wodurch der Flow ungescoped neu aufgebaut wurde. Jeder Weg in den Unsupported-Step-Handoff wird zusätzlich als Non-Fatal-Event mit dem Wire-Identifier des Steps gemeldet, damit die nächste Lücke in der Step-Mapping-Tabelle des Clients sichtbar statt still ist. Keine neuen Screens, keine neuen Texte, keine Änderung daran, was die API gefragt oder ihr mitgeteilt wird. Deckt Items 3 und 5 aus DFXswiss/api#4556 ab.Details
Item 3 — the KYC context on entry
The buy and sell gates already forwarded the context the API attached to
KYC_LEVEL_REQUIRED/REGISTRATION_REQUIRED, so those entries were scoped. The one remaining way into/kycwithout a context was the post-lock restore:BootNavRestorere-pushes the captured location withrouter.push(location)— a bare path, noextra— soKycPageManagerrebuilt withkycContext: null. Unscoped, the API computesprocessStatusover every globally required step, including the onesRealunitBuydeliberately excludes, and a user who has finishedIdentis reportedInProgressfor steps that do not gate buying.Moving the context into the URL fixes that entry without touching the lock/restore machinery: the capture already stores
effectiveLocation, which includes the query, andisGateLocation/isRestorableLocationcompareUri.parse(loc).pathonly, so the allowlist match is unaffected.kycRouteQuery(String?)inapp_routes.dartbuilds the query; a null context yields an empty map, so a gate the API attached no context to enters exactly as unscoped as before — the app never invents one.payment_action_button.dartandsell_button.dartpassqueryParameters:instead ofextra:; the route builder readsstate.uri.queryParameters['context'].Item 5 — an unmapped step is no longer silent
The generic handoff page and its routing arm already exist (#883): an unmapped step renders
KycUnsupportedStepPagewith a refresh and a support link, not a fatal error screen. What was missing is the other half — nothing recorded the occurrence. Every request in the flow returns 200 and nothing is logged aboveLOGlevel, so a step name absent from_mapStepNameonly surfaced through a user report.KycUnsupportedStepFailurenow funnel through one_emitUnsupportedStep, which reports before emitting.reportNonFatalincrash_reporting.dartlogs for an attached developer and, when a DSN was injected at build time, captures an error event. Same channel, same DSN gate and same pinned option surface as the uncaught-error path — no PII, no attachments, no breadcrumb widening. Without a DSN (every local and test build) nothing was started and the call is a pure log line.UnsupportedKycStepException, whose rendered string is the step's API wire identifier and nothing else. Per CONTRIBUTING it is enumerated inexception_surface_test.dartin this PR.KycCubit(optional namedreport, defaulting toreportNonFatal), so the cubit tests observe the report and no test reaches the real sink.Deliberately not in scope
Recommendation/ResidencePermitpages) — the issue itself recommends the generic fallback first and building the individual forms only once a real affected population is measured. That measurement is what item 5 now supplies.Verification
flutter analyzeandflutter testwere not run locally — CI is the first execution of this diff. Any red check gets reworked before this leaves draft.Tests added or changed
boot_navigation_apply_test.dart/kyc?context=RealunitBuyrebuilds the flow with that contextkyc_route_query_test.dartpayment_action_button_test.dartkyc_cubit_test.dartunsupported_kyc_step_exception_test.dartcrash_reporting_test.dartreportNonFatalis inert and non-throwing when no reporter was started