Skip to content

TASK-23054: QR pay is open everywhere; checklist follow-ups (First payment, held card, Hide) - #3481

Merged
Hugo0 merged 4 commits into
devfrom
task-23054-qr-pay-everywhere
Sep 25, 2026
Merged

Hugo0 merged 4 commits into
devfrom
task-23054-qr-pay-everywhere

Conversation

@Hugo0

@Hugo0 Hugo0 commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

TASK-23054. Follow-up to #3474. Hugo caught a wrong rule and asked for four more checklist changes.

QR pay is open to every user, in any country

Hugo: any verified Peanut user anywhere can pay QR codes. Only topping up through Manteca is limited to Argentina and Brazil. #3474 had its own residence rule (canReachQrPay, AR/BR only), which hid the QR option from everyone else.

  • The checklist now asks the QR-pay gate that the QR pay page uses. selectQrKycGate is extracted from useQrPayKycGate into features/payments/flows/qr-pay/qrKycGate.utils.ts. The page, the checklist and the carousel's "Pay with QR" slide all read it, so they cannot disagree.
  • qrPayIsAPath(state) maps the gate to the checklist:
    • PROCEED_TO_PAY, REQUIRES_IDENTITY_VERIFICATION, IDENTITY_VERIFICATION_IN_PROGRESS, PROVIDER_REJECTION_FIXABLE and PROVIDER_RESTART_IDENTITY → QR is a path (now, or once verified or fixed).
    • PROVIDER_REJECTION_BLOCKED and REGION_RESTRICTED → no QR path.
    • LOADING → the route is pending: the row holds its place and the list cannot complete.
  • canReachQrPay, hasQrPayRail and the AR/BR list are deleted.
  • The carousel slide shows only on PROCEED_TO_PAY (a scan would pay now), and never after a QR pay.

API, read-only:

  • A user who has not verified has no Manteca user_rails row. autoEnrollUserRails gives Manteca geo rails only to AR/BR at KYC start, so the resolver emits no Manteca rail and the gate returns REQUIRES_IDENTITY_VERIFICATION, which is a path.
  • On every Sumsub approval, enableQrPoolRails (sumsub/status-processor.ts, "QR is geo-agnostic for pool users") sets PIX_BR and MERCADOPAGO_QR_AR to ENABLED at pool tier, whatever the residence.
  • Prod: 4,854 of 4,858 Sumsub-approved users hold an ENABLED pool rail.

Hugo's checklist follow-ups

  1. The row title is "First payment" (Primer pago, Primeiro pagamento).
  2. A card holder is not told to get the card again. Once a card is issued or its application is in (hasCardRelationship), the subtitle is "Pay with the card", or "Pay a QR or with the card" when QR is also open. A card-only tap opens /card. The row is done only on the first spend (API isActivated), as before. The copy has no pronoun ("the card", not "your card"), per the copy rule.
  3. The list can be hidden once only the payment row is left (Create, Verify and Add money done). This covers people who only move money in and out, who would otherwise keep a 75% list forever.
    • A tertiary "Hide" LinkButton sits under the list, 24px below the last row.
    • Hiding shows the carousel and sends home_checklist_hidden.
    • The choice goes in the Home CTA dismissal store that carousel cards use (hideHomeCta / readHiddenHomeCtas in home-carousel.utils.ts). Both now merge into the stored record instead of overwriting it.
    • The link does not show before Add money or while the ID check is in review.
    • Hiding never hides a rejection or region card. A hidden list comes back if the user is no longer at the payment row.
  4. Stuck-state fixtures:
    • home-verify-processing-long: ID check in review for two weeks.
    • home-card-application-pending: card application in review.
    • home-card-info-failed: card info fails to load, so the payment row holds.
    • home-money-in-out-only: money in and out, no spend, so the Hide link shows.
    • home-qr-blocked-unfunded: the three-row case. QR pay is blocked and a bank rail works.

What changes per state

State Before (#3474) After
New user, card offered, outside AR/BR "Get the Peanut Card" "Pay a QR or get the card" (chooser)
New user, no card, outside AR/BR 3 rows 4 rows, "Pay a QR code"
Verified, no card, not in AR/BR 3 rows, then carousel 4 rows, "Pay a QR code"
Card holder or application in "Get the Peanut Card" "Pay with the card" / "Pay a QR or with the card"
Verified, funded, not spent 75% forever 75%, with a Hide link that hands over to the carousel
QR pay blocked, no card (same) 3 rows 3 rows (only this case)

Captures of every state, the stuck-state table and the full copy table are in mono local/scratch/home-viz/onboarding.html (section 2 and 2b).

Tests

  • qrKycGate.utils.test.ts: every gate state, including the new user with no Manteca rail (REQUIRES_IDENTITY_VERIFICATION), and qrPayIsAPath for all of them.
  • activation-step.utils.test.ts: route pending while the QR gate loads, and canHideChecklist.
  • useActivationStatus.test.tsx: card + QR before verification for residences BR, US, DE and none. QR only without the card. Blocked QR with no card gives none. A held card sets cardHeld.
  • GettingStartedChecklist.test.tsx: held-card copy and tap. Hide shows only at the payment row, calls back and sends the event.
  • HomePage.test.tsx: a hidden list hands over. It comes back when it may not be hidden. It never hides a rejection card.
  • home-carousel.utils.test.ts: one store, merged, per user.
  • Local: prettier, tsc, Jest green.

Hugo: any verified user anywhere can pay QR codes; only Manteca top-up is
limited to Argentina and Brazil. The checklist had its own residence rule
(AR/BR only) and hid the QR option from everyone else. It now reads the same
selector as the QR pay page (selectQrKycGate, extracted from
useQrPayKycGate), so the two cannot disagree: QR is a path now, or once
verified or fixed, unless a provider blocks it or the region is refused.
The carousel's Pay with QR slide reads the same gate. canReachQrPay,
hasQrPayRail and the AR/BR list are gone.
… with, Hide, stuck states

Hugo: 'First payment' is the row title. A user who holds a card or applied
for one is told to pay with it, not to get it again. Once only the payment
row is left, the list can be hidden: people who only move money in and out
would otherwise keep a 75% list forever. Hiding uses the one Home CTA
dismissal store (carousel cards and the checklist now merge into it instead
of overwriting). Fixtures cover the stuck states.
@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 25, 2026 4:28pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: peanutprotocol/peanut-ui/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 604e88c5-8578-43bf-824c-b203312c16ad

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 8937.88 → 8946.85 (+8.97)
Findings: +8 net (+52 new, -44 resolved)

🆕 New findings (52)

  • critical complexity — src/hooks/useHomeCarouselCTAs.tsx — CC 56, MI 58.96, SLOC 273
  • critical complexity — src/dev/fixtures/registry.ts — CC 9, MI 22.44, SLOC 1580
  • high hotspot — src/constants/analytics.consts.ts — 84 commits, +439/-85 lines since 6 months ago
  • high hotspot — src/components/Home/ActivationCTAs.tsx — 69 commits, +1041/-807 lines since 6 months ago
  • high hotspot — src/dev/fixtures/registry.ts — 68 commits, +2223/-379 lines since 6 months ago
  • high hotspot — src/hooks/useHomeCarouselCTAs.tsx — 53 commits, +571/-441 lines since 6 months ago
  • high complexity — src/components/Home/ActivationCTAs.tsx — CC 37, MI 49.97, SLOC 161
  • high complexity — src/components/Home/GettingStartedChecklist.tsx — CC 37, MI 59.31, SLOC 153
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 30.16, SLOC 219
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:54 — useHomeCarouselCTAs: MDD 145.4 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useHomeCarouselCTAs.tsx:107 — : MDD 98.2 (uses across many lines from declarations)
  • medium high-mdd — src/components/Home/ActivationCTAs.tsx:47 — ActivationCTAs: MDD 81.1 (uses across many lines from declarations)
  • medium high-dlt — src/hooks/useHomeCarouselCTAs.tsx:54 — useHomeCarouselCTAs: DLT 50 (calls 50 distinct functions — high context load)
  • medium high-mdd — src/components/Home/GettingStartedChecklist.tsx:82 — GettingStartedChecklist: MDD 47.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Home/GettingStartedChecklist.tsx:93 — : MDD 30.2 (uses across many lines from declarations)
  • medium high-mdd — src/features/home/HomePage.tsx:36 — HomePage: MDD 26.7 (uses across many lines from declarations)
  • medium method-complexity — src/hooks/useHomeCarouselCTAs.tsx:107 — CC 27 SLOC 139
  • medium structural-dup — dev/fixtures/registry.ts:1465 — 26 duplicate lines / 97 tokens with dev/fixtures/registry.ts:1497
  • medium complexity — src/utils/activation-step.utils.ts — CC 26, MI 61.97, SLOC 47
  • medium complexity — src/features/payments/flows/qr-pay/qrKycGate.utils.ts — CC 23, MI 61.06, SLOC 81

…and 32 more.

✅ Resolved (44)

  • src/hooks/useHomeCarouselCTAs.tsx — CC 56, MI 59.06, SLOC 268
  • src/dev/fixtures/registry.ts — CC 9, MI 23.67, SLOC 1437
  • src/constants/analytics.consts.ts — 83 commits, +438/-85 lines since 6 months ago
  • src/components/Home/ActivationCTAs.tsx — 68 commits, +1037/-805 lines since 6 months ago
  • src/dev/fixtures/registry.ts — 64 commits, +2049/-372 lines since 6 months ago
  • src/hooks/useHomeCarouselCTAs.tsx — 51 commits, +552/-426 lines since 6 months ago
  • src/components/Home/ActivationCTAs.tsx — CC 37, MI 49.99, SLOC 161
  • src/components/Home/GettingStartedChecklist.tsx — CC 33, MI 59.39, SLOC 143
  • src/utils/activation-step.utils.ts — CC 33, MI 63.31, SLOC 69
  • src/features/payments/flows/qr-pay/useQrPayKycGate.ts — CC 31, MI 59.28, SLOC 126
  • src/constants/analytics.consts.ts — CC 1, MI 30.22, SLOC 218
  • src/hooks/useHomeCarouselCTAs.tsx:57 — useHomeCarouselCTAs: MDD 138.5 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:113 — : MDD 96.3 (uses across many lines from declarations)
  • src/components/Home/ActivationCTAs.tsx:45 — ActivationCTAs: MDD 81.1 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:57 — useHomeCarouselCTAs: DLT 50 (calls 50 distinct functions — high context load)
  • src/components/Home/GettingStartedChecklist.tsx:74 — GettingStartedChecklist: MDD 45.0 (uses across many lines from declarations)
  • src/components/Home/GettingStartedChecklist.tsx:85 — : MDD 30.2 (uses across many lines from declarations)
  • src/hooks/useHomeCarouselCTAs.tsx:113 — CC 27 SLOC 129
  • dev/fixtures/registry.ts:1450 — 26 duplicate lines / 97 tokens with dev/fixtures/registry.ts:1482
  • src/features/home/HomePage.tsx:34 — HomePage: MDD 25.7 (uses across many lines from declarations)

…and 24 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/features/payments/flows/qr-pay/qrKycGate.utils.ts 0.0 5.0 +5.0
src/hooks/useActivationStatus.ts 8.8 10.5 +1.7
src/features/home/HomePage.tsx 8.4 9.1 +0.7
src/utils/home-carousel.utils.ts 4.0 4.6 +0.6
src/dev/fixtures/registry.ts 13.2 13.7 +0.5
src/features/payments/flows/qr-pay/useQrPayKycGate.ts 6.4 5.8 -0.6

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 10251 ran, 0 failed, 0 skipped, 2.4m

📊 Coverage (unit)

metric %
statements 82.3%
branches 72.0%
functions 76.7%
lines 83.5%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Scan that recovers on the retry lands on the payment screen, not an error
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

One checklist state-update defect: the held-card subtitle can remain stale after an application appears.

Findings

  • MAJOR · src/components/Home/GettingStartedChecklist.tsx:152 · Recompute the payment subtitle when cardHeld changes
    If a card-eligible user opens Home before applying, the card_qr row says to get the card. When the card application is submitted and the refreshed capability adds a card relationship, cardHeld becomes true but the route can remain card_qr. The rows useMemo does not include onboarding.cardHeld, so it retains the old subtitle and still asks this applicant to get the card. Add cardHeld to that memo’s dependencies (and cover the false→true rerender).

Checked clean

  • Confirmed the exact head, trusted PR metadata, and two-commit diff from the merge base; no issue or review comments were fetched.
  • Checked QR selector extraction against the existing pay-page gate and API QR-pool enrollment behavior; found no additional actionable divergence.
  • Checked checklist route and hide rules, per-user dismissal merging, locale fallback, and the changed fixtures and tests.
  • At review time unit, typecheck, lint, format, and security-scan checks passed; visual captures and press-contract were still running.

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion: did not run — claude-api_error. This review is one reviewer short.

Exact head: f525ea9e9464 · Context: repo, other-repository, ci · Took 7m

Comment thread src/components/Home/GettingStartedChecklist.tsx
…no Hide while the payment row loads

Chip: the rows memo missed onboarding.cardHeld, so an application submitted
mid-session kept 'Get the Peanut Card'. And a pending payment row is not a
known last step, so the list cannot be hidden on it. The card-holder fixture
now carries its card rail.
@Hugo0
Hugo0 enabled auto-merge (squash) September 25, 2026 15:55

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

No findings at this head. Prior P1 is fixed.

Checked clean

  • Confirmed the exact head and trusted PR metadata; reviewed the follow-up commit against the prior head and the full PR diff from its merge base.
  • P1 fixed: the checklist row memo now depends on cardHeld, and a rerender test covers an application arriving without a route change.
  • The payment-row pending state now disables Hide, and the held-card fixture has matching card and QR capabilities.
  • Rechecked the previously reviewed QR gate, checklist route, carousel, dismissal storage, and locale behavior for new regressions.
  • At review time the available bot and security checks passed; remaining CI jobs were still running. Local dependencies were unavailable, so no duplicate test run was performed.

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion: did not run — it reads only the first review of a pull request; the first reviewer checks later rounds. This review is one reviewer short.

Exact head: 9e427919bc19 · Context: repo, other-repository, ci · Took 4m

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

English · Español · Español (Argentina) · Português (Brasil)

Open screen library dashboard

After merge: c5843c4 → 3f10b4a. Capture complete in all locales.

The screen captures wait for loaders to clear; this state is the held row's
placeholder, so it names that placeholder instead.

This branch was successfully deployed

1 active deployment
Preview — 279cb034 Deployed Sep 25, 2026 by vercel[bot]
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