01a03535 - Add referral programme and promo-code client - #966
01a03535 - Add referral programme and promo-code client#966JonnyLuca wants to merge 113 commits into
Conversation
4d7b83c to
a145ef2
Compare
|
EN. Ready after the referral/promo client was driven to zero review findings across three cross-vendor review passes (the last two confirming the finalisation fixes). The full effective PR CI is green at this head — Analyze & Test, Coverage Floor Gate and Visual Regression — with the same effective job set additionally run and green on a build host at the CI-pinned toolchain. DE. Bereit, nachdem der Referral-/Promo-Client über drei Cross-Vendor-Review-Durchläufe auf null Findings gebracht wurde (die letzten beiden bestätigten die Finalisierungs-Fixes). Die vollständige wirksame PR-CI ist auf diesem Head grün — Analyze & Test, Coverage Floor Gate und Visual Regression — und dieselbe wirksame Jobmenge wurde zusätzlich auf einem Build-Host mit der CI-fixierten Toolchain grün gefahren. Gate evidence
Deferred with rationale (not defects): the lenient |
07b16c3 to
fef34be
Compare
…e to pop. Staying on /settings after unlock must still bind; KYC is gated separately.
A 200 with a non-JSON body is not retried, so the code is not restashed forever.
Leaving the page mid-fetch must not throw StateError.
SharedPreferences errors after a successful submit must not skip checkKyc or SyncWalletServices. Guard context.mounted after the await.
A crash between backend acceptance and the success listener must not lose the looked-up code; AlreadyRegistered will not re-open the step.
findTxIdIgnoreCase already matches the lowercased hash, so a second query with the original casing cannot find a different row.
The dashboard gate is fail-closed: 2, -1, 0.5 and "yes" no longer open it.
InvitesLoading precedes InviteUrlLabel; referrals sits with the share keys.
The helper is only used by tests; mark it @VisibleForTesting.
Widget tests already construct ReferralOverviewPage with a real tree.
Lookup, bind and terms timeouts must not leave 30-second wall-clock futures.
The invites list paragraph now reads as one sentence; the lookup spinner copy is a single pair of guillemets.
The auth gate returns 401 for a missing PNG too, so HTTP status cannot prove Golden assembly. docker exec test -f checks the file exists.
A 200 with a non-JSON body must drop the stash instead of retrying. TypeError is terminal for the same reason.
Take and discard serialize on the in-flight flag; a second take returns null and the stash is empty when they finish.
Closing mid-getSummary must keep only the synchronous Loading emit.
Unlock restore schedules bind on the next frame so staying on settings still consumes the stash.
Move the nine widget flows out of the unwired integration_test/ harness so coverage CI executes them. Drop the unused SDK dependency.
dart: then Flutter SDK, then other packages, then realunit_wallet, alphabetically within each group.
Skip and invalid lookup drop the typed prefs stash so KYC exit cannot bind against the user's will. Submit awaits that stash so a crash after backend accept still has the code.
Walk the whole match list so a payment push on a restored KYC stack cannot bind or drop the pending code.
…d rows. A settled prize with missing amount/created no longer lets sync report success while hiding the rest of the ledger.
The SDK dep was removed from pubspec.yaml; the lock still listed it as direct dev.
A second take after the race must also return null so a lost claim fails.
Double-tap during the new prefs awaits no longer issues two getUser calls. The cubit is captured before those awaits, a newer deeplink is not last-write-wins overwritten, and the tax CTA disables itself before the first await. Tests go red if the stash-before-submit await is removed.
apiBasedSync was fire-and-forget at both wallet-load callers, so a parse throw became an uncaught async error nobody saw. Catch at the bloc and show a snackbar when the flag rises.
A Pending row without amount/created threw FormatException before isSettled could skip it, so the new fail-closed path fired on every wallet load. Status is read from the raw JSON first.
anyOf(null, the original code) allowed the loss case. After the race the stash must be empty so a second take cannot still bind it.
Drop duplicate Cupertino delegates, dispose extra GoRouters, point deeplink docs at kReferralCodeMaxLength, load the real de TB in the terms matrix, DRY the create-page harness, and move widget flows out of the e2e/ name that the repo reserves for Maestro.
integration_test is gone from pubspec.yaml; flutter pub get drops the four leftover transitives (flutter_driver, fuchsia_remote_debug_protocol, sync_http, webdriver) that were not on staging.
Summary / Zusammenfassung
EN. RealUnit referral programme and promo-code client (Offerte 24.08.2026 + mail Dani 17:42). The DFX API is the decision authority; this app is the rendering layer for eligibility, terms, invites, overview, frozen-CHF prizes, the KYC invite/promo step, and App Links. This branch supersedes #942 with clean, signed, tool-neutral commits and carries the review finalisation on top of the feature. It was hardened through a cross-vendor double-review (Claude Opus 5 + Codex) to zero findings.
DE. RealUnit-Empfehlungsprogramm und Promo-Code in der App (Offerte 24.08.2026 + Mail Dani 17:42). Die DFX-API entscheidet; die App rendert Kachel, Teilnahmebedingungen, Einladungen, Übersicht, Prämien mit eingefrorenem Frankenwert, KYC-Schritt Invite/Promo und App Links. Dieser Branch ersetzt #942 mit sauberen, signierten, werkzeugneutralen Commits und enthält die Review-Finalisierung zusätzlich zum Feature. Er wurde per Cross-Vendor-Doppelreview (Claude Opus 5 + Codex) auf null Findings gehärtet.
Scope, review finalisation, measurements
Scope
GET /v1/realunit/referral/summaryeligible(KYC-verified, ≥ 70 REALU, not an employee). Subtitle Erhalte 20 REALU pro Weiterempfehlung.GET /v1/realunit/referral/terms(version,markdown,markdownEn), with bundled assets only as an outage fallback; create-invite only after the acceptance checkbox. Acceptance posts the exact rendered version so a newer server version reopens the gate, and the posted version always matches the text actually shown.https://realunit.app/invite/{code}.ScrollableActionsLayoutinside aSafeAreaso next/skip clear system insets. No stacking with an invite prize (API rejects).referralPayout(type 5).#spec-referral. Referral/promo goldens regenerated viagolden-regenerate.yaml.Review finalisation on top of the feature (this replaces #942)
campaignTextForLocale; a generation guard in the eligibility reload; terms acceptance reset on a language reload, adopting the API terms version only together with the text it belongs to; the overview Equatable state compares every rendered per-invite field plus the share-price label.ScrollableActionsLayoutin aSafeAreaand is covered by a responsive-matrix test that asserts both sticky CTAs; the pureformat_frozen_chfhelper moved intopackages/io; the frozen-CHF label uses the text theme; the unused payout merger and the orphanedgetPayoutsservice method were removed; the referral surfaces are analyzer-clean.emitwithisClosed(no emit-after-close when a cubit is closed mid-fetch); KYC registration hands the deeplink stash to the referral field only while the referral step is the active step, preserving the do-not-refill-after-skip contract while the field keeps stash-over-clipboard precedence;frozen_chf_labelmoved intolib/widgets/(only non-referral importers) with imports re-sorted per CONTRIBUTING.md; the test-only pending-code sync helpers marked@visibleForTesting;shareReferralInviteannotated for integration-test exclusion. New regression test: the eligibility cubit does not emit after being closed mid-flight.referralJson*DTO helpers are kept deliberately (locale-tolerant CHF/number parsing; strictascasts would throw on the API's locale-formatted values); a few rawTextStyle(color:)sites are left as-is (a golden-affecting style refinement tracked as follow-up, not a correctness issue); theNEEDS_TERMSretry-only screen is escapable via back and only reachable through an unusual backend state race (reviewer: not a blocker).Measured (self-hosted / native)
flutter test --exclude-tags goldenflutter analyzeflutter analyze+ referral/kyc/routing suites atfef34be)golden-regenerate.yamlon RealUnitCH/appfddb4ec, 33925355107 →69db0ea)Post-merge / operator actions
/invite/*are HTTP 200.PRIZE_WALLET_KEYfor settled 20 REALU transfers.Related: RealUnitCH/intern#3 (do not auto-close).