Skip to content

kyc(gate): consume wait:bridge NextAction + SEPA v2 ToS copy#2132

Merged
Hugo0 merged 3 commits into
devfrom
feat/wait-bridge-gate
May 29, 2026
Merged

kyc(gate): consume wait:bridge NextAction + SEPA v2 ToS copy#2132
Hugo0 merged 3 commits into
devfrom
feat/wait-bridge-gate

Conversation

@Hugo0

@Hugo0 Hugo0 commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Two FE-side follow-ups to peanut-api-ts#906 (BE Bridge requirement vocabulary fix) — neither feature is usable without that BE PR.

  • New waiting-on-provider gate kind. BE now emits {kind:'wait', key:'wait:bridge'} when Bridge is doing internal review (`kyc_approval`, `post_processing`, generic `stripe_database_lookup_*`). Without a FE handler the wait-only rail fell through to `needs-enrollment` → user saw a misleading "verify your identity" CTA and got bounced back to Sumsub for nothing. Now: `deriveGate` returns `waiting-on-provider` with the BE-supplied "we're finalizing with Bridge" message, and the 4 consumer call sites suppress the KYC modal (treated like `loading`).
  • BridgeTosStep copy varies for SEPA v2. New `reasonCode` prop; `bridge_tos_v2_required` → SEPA-specific copy ("Accept SEPA Terms of Service" / "...to enable EUR (SEPA) and GBP (Faster Payments)…"). `bridge_tos_required` (base) keeps existing copy. Endpoint call is unchanged (Bridge's `tos_acceptance_link` is opaque to endorsement — Bridge picks the right ToS server-side based on customer state).
  • Manteca `handleOnboardingError` intentional-fallback comment. The Manteca hosted widget is dead-by-design per KYC 2.0 (centralized on Sumsub → `submitToManteca` via API), but the error-recovery redirect stays as a last-resort escape hatch for users stuck in incomplete Manteca state. Comment clarifies the intent so the next reader doesn't think it's a primary flow.

Priority order change in deriveGate

`waiting-on-provider` slots between `pending` and `needs-identity` — AFTER `ready`, so a usable rail wins (don't block a user from a rail they can use), BEFORE `needs-identity` so the wait copy beats a misleading "verify" CTA.

```

  1. loading
  2. blocked-rejection
  3. accept-tos
  4. fixable-rejection
  5. ready ← user has a usable rail → use it
  6. pending ← provisioning, also a wait but more concrete
  7. waiting-on-provider ← NEW
  8. needs-identity
  9. needs-enrollment
    ```

Test plan

  • `pnpm jest --testPathPattern='capability-gate'` → 12/12 green (new file)
  • `pnpm jest` full suite → 1187 pass, 3 pre-existing content.test.ts failures (unrelated)
  • `pnpm typecheck` → clean (asset-import errors are Next.js worktree quirk, pre-existing)
  • `pnpm prettier --check` on all touched files → clean
  • Manual: in sandbox, force a Bridge `kyc_approval` state on a user, hit `/add-money/EU/bank`, verify the wait copy renders instead of the verify-identity modal
  • Manual: in sandbox, EU user mid-SEPA-ToS, verify the BridgeTosStep modal shows SEPA-specific title/description

Risks

  • Behavioral change: `waiting-on-provider` is new, so consumers' `gate.kind !== 'ready'` guards now have one more branch they suppress (same as `loading`). Wide-coverage Jest suite passes.
  • Reason-prop addition on `accept-tos` GateState is additive (optional field). Existing consumers unaffected.

…or SEPA v2

Two FE-side follow-ups to peanut-api-ts#906 (BE Bridge requirement vocabulary
fix). Neither feature is testable without the BE PR.

1. `waiting-on-provider` gate kind. The BE now emits
   `{kind:'wait', key:'wait:bridge'}` when Bridge is doing internal review
   (`kyc_approval`, `post_processing`, generic `stripe_database_lookup_*`).
   Without a gate-side handler, the wait-only rail falls through to
   `needs-enrollment` and the user sees a misleading "verify your identity"
   CTA — exactly the loop the new `wait` kind was meant to prevent. Add a
   branch in `deriveGate` between `pending` and `needs-identity` so:
   - a usable rail (ready/pending) still wins (don't block the user from a rail they CAN use)
   - the wait-only case shows the BE-supplied "we're checking with Bridge" copy
     instead of a Sumsub bounce
   Consumers treat `waiting-on-provider` like `loading` — silent no-op
   instead of opening the KYC modal.

2. BridgeTosStep accepts a `reasonCode` prop. SEPA v2 (`bridge_tos_v2_required`)
   gets SEPA-specific copy; base ToS (`bridge_tos_required`) keeps the
   existing copy. Callsite plumbs `gate.reason?.code` from the new
   `accept-tos` GateState `reason` field. The Bridge endpoint
   (`/users/bridge-tos-link`) is opaque to endorsement — Bridge serves the
   right ToS based on customer state — so this is copy-only.

3. Manteca `handleOnboardingError` gets an intentional-fallback comment.
   The Manteca hosted widget is dead-by-design per the KYC 2.0 plan
   (centralized on Sumsub → submitToManteca via API), but the error-recovery
   redirect stays as a last-resort escape hatch until we root-cause and fix
   the cases where users end up half-onboarded.
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment May 29, 2026 4:17pm

Request Review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Hugo0, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 45 minutes and 33 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f89cf72a-dfa6-4ec7-81a7-845e7fcd761f

📥 Commits

Reviewing files that changed from the base of the PR and between 79b200f and 0ff2832.

📒 Files selected for processing (2)
  • src/components/Kyc/BridgeTosStep.tsx
  • src/utils/capability-gate.test.ts

Walkthrough

This PR introduces a new waiting-on-provider capability-gate state to prevent KYC/ToS flows while a payment provider is under review, extends BridgeTosStep with variant ToS copy selection via reasonCode, and applies the new guard pattern across four integration points (add-money, withdraw, AddWithdrawCountriesList, BankFlowManager).

Changes

Provider-review gate state and variant ToS copy

Layer / File(s) Summary
Capability-gate state machine: waiting-on-provider addition
src/utils/capability-gate.ts
GateState union extends with waiting-on-provider kind carrying user message and optional reason; accept-tos gains optional reason field; deriveGate priority order places waiting-on-provider below ready/pending and above identity needs; new branch detects waiting when requires-info rails expose only wait-type actions; getGateUserMessage extracts message for new state.
Capability-gate tests: waiting-on-provider coverage
src/utils/capability-gate.test.ts
Jest suite validates waiting-on-provider derivation against other gate kinds, edge-case empty blocking-actions handling, reason propagation from accept-tos rails, and user-message/modal-variant mappings for the new state.
BridgeTosStep component: reasonCode variant copy
src/components/Kyc/BridgeTosStep.tsx
BridgeTosStepProps accepts optional reasonCode?: string; new TOS_COPY constant holds base and SEPA v2 variants; component selects copy at render based on reasonCode and updates ActionModal title/description accordingly.
Capability-gate consumers: guard pattern and reasonCode propagation
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx, src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/Claim/Link/views/BankFlowManager.view.tsx
All four callers add early-return guards for both loading and waiting-on-provider states; each passes reasonCode to BridgeTosStep derived from gate.reason.code when gate is accept-tos.
Manteca onboarding fallback documentation
src/app/(mobile-ui)/withdraw/manteca/page.tsx
Expanded comment clarifies onboarding-incomplete error fallback as intentional escape hatch and notes eventual removal targets (no logic change).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • peanutprotocol/peanut-ui#2064: Both PRs update src/components/AddWithdraw/AddWithdrawCountriesList.tsx to centralize Bridge/TOS gating via checkBridgeGate and render BridgeTosStep for accept-tos; main PR extends logic to handle waiting-on-provider and pass reasonCode.
  • peanutprotocol/peanut-ui#2063: Both PRs modify bridge gate handling in src/components/AddWithdraw/AddWithdrawCountriesList.tsx and BridgeTosStep—main PR adds waiting-on-provider guard and reasonCode support while retrieved PR refactors readiness gating for "under review" vs TOS flow.

Suggested labels

enhancement


Suggested reviewers

  • jjramirezn
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: introducing the 'waiting-on-provider' gate state for wait:bridge NextAction and adding SEPA v2-specific ToS copy variation.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the three main features, priority order changes, test coverage, and identified risks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added the enhancement New feature or request label May 29, 2026
@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5811.62 → 5813.62 (+2)
Findings: +1 net (+48 new, -47 resolved)

🆕 New findings (48)

  • critical complexity — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 117, MI 57.31, SLOC 342
  • critical complexity — src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 96, MI 48.28, SLOC 388
  • critical complexity — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 92, MI 52.38, SLOC 309
  • critical complexity — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 87, MI 57.5, SLOC 302
  • high hotspot — src/app/(mobile-ui)/withdraw/manteca/page.tsx — 63 commits, +637/-383 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — 47 commits, +523/-471 lines since 6 months ago
  • high complexity — src/utils/capability-gate.ts — CC 47, MI 65.51, SLOC 126
  • high hotspot — src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 35 commits, +560/-413 lines since 6 months ago
  • high hotspot — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — 34 commits, +307/-154 lines since 6 months ago
  • high method-complexity — src/components/Claim/Link/views/BankFlowManager.view.tsx:264 — CC 32 SLOC 104
  • medium react-long-component — src/app/(mobile-ui)/withdraw/manteca/page.tsx:76 — MantecaWithdrawFlow is 822 lines — split it
  • medium react-long-component — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:52 — WithdrawBankPage is 452 lines — split it
  • medium react-long-component — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:45 — OnrampBankPage is 404 lines — split it
  • medium high-mdd — src/app/(mobile-ui)/withdraw/manteca/page.tsx:76 — MantecaWithdrawFlow: MDD 231.8 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:45 — OnrampBankPage: MDD 131.6 (uses across many lines from declarations)
  • medium high-mdd — src/components/AddWithdraw/AddWithdrawCountriesList.tsx:42 — AddWithdrawCountriesList: MDD 132.3 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:52 — WithdrawBankPage: MDD 131.4 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Link/views/BankFlowManager.view.tsx:56 — BankFlowManager: MDD 97.7 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/withdraw/manteca/page.tsx:316 — handleWithdraw: MDD 44.7 (uses across many lines from declarations)
  • medium high-mdd — src/components/Claim/Link/views/BankFlowManager.view.tsx:264 — handleSuccess: MDD 37.9 (uses across many lines from declarations)

…and 28 more.

✅ Resolved (47)

  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — CC 114, MI 57.37, SLOC 342
  • src/components/Claim/Link/views/BankFlowManager.view.tsx — CC 94, MI 48.31, SLOC 388
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — CC 90, MI 52.44, SLOC 309
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — CC 85, MI 57.56, SLOC 302
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx — 62 commits, +624/-383 lines since 6 months ago
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx — 46 commits, +517/-468 lines since 6 months ago
  • src/utils/capability-gate.ts — CC 41, MI 66.33, SLOC 110
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx — 34 commits, +547/-406 lines since 6 months ago
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx — 33 commits, +303/-153 lines since 6 months ago
  • src/components/Claim/Link/views/BankFlowManager.view.tsx:260 — CC 32 SLOC 104
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:76 — MantecaWithdrawFlow is 809 lines — split it
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:52 — WithdrawBankPage is 449 lines — split it
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:45 — OnrampBankPage is 401 lines — split it
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:76 — MantecaWithdrawFlow: MDD 226.9 (uses across many lines from declarations)
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx:42 — AddWithdrawCountriesList: MDD 128.9 (uses across many lines from declarations)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:45 — OnrampBankPage: MDD 127.4 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:52 — WithdrawBankPage: MDD 123.8 (uses across many lines from declarations)
  • src/components/Claim/Link/views/BankFlowManager.view.tsx:56 — BankFlowManager: MDD 92.3 (uses across many lines from declarations)
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx:303 — handleWithdraw: MDD 44.7 (uses across many lines from declarations)
  • src/components/Claim/Link/views/BankFlowManager.view.tsx:260 — handleSuccess: MDD 37.9 (uses across many lines from declarations)

…and 27 more.

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1194 ran, 0 failed, 0 skipped, 19.0s

📊 Coverage (unit)

metric %
statements 49.1%
branches 30.2%
functions 33.4%
lines 48.9%
⏱ 10 slowest test cases
time test
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every stamp stays within canvas at any count
0.3s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid UK IBAN with spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (missing 0x prefix)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ENS name
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle minimum length (6 digits) US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces 2
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/components/Kyc/BridgeTosStep.tsx (1)

24-25: ⚡ Quick win

Tighten reasonCode typing to avoid silent copy regressions.

Use a typed reason-code contract instead of raw strings so backend-code typos get caught at compile time.

Proposed refactor
+import type { CapabilityReason } from '`@/types/capabilities`'
+
 interface BridgeTosStepProps {
@@
-    reasonCode?: string
+    reasonCode?: CapabilityReason['code']
 }
@@
+const BRIDGE_TOS_V2_REQUIRED = 'bridge_tos_v2_required' as const
@@
-    const copy = reasonCode === 'bridge_tos_v2_required' ? TOS_COPY.sepa : TOS_COPY.base
+    const copy = reasonCode === BRIDGE_TOS_V2_REQUIRED ? TOS_COPY.sepa : TOS_COPY.base

Also applies to: 105-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Kyc/BridgeTosStep.tsx` around lines 24 - 25, The reasonCode is
currently typed as a raw string which allows silent typos; introduce a specific
ReasonCode union type or enum (e.g., ReasonCode) reflecting the allowed backend
values (or import the contract type from the shared types package), replace the
loose property signature reasonCode?: string in the BridgeTosStep
props/interface with reasonCode?: ReasonCode, and update all usages in this file
(including the occurrence around the component render/logic) to use the new
ReasonCode type so TypeScript will catch invalid values at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 24-25: The reasonCode is currently typed as a raw string which
allows silent typos; introduce a specific ReasonCode union type or enum (e.g.,
ReasonCode) reflecting the allowed backend values (or import the contract type
from the shared types package), replace the loose property signature
reasonCode?: string in the BridgeTosStep props/interface with reasonCode?:
ReasonCode, and update all usages in this file (including the occurrence around
the component render/logic) to use the new ReasonCode type so TypeScript will
catch invalid values at compile time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef5950a1-d3cf-4106-9f79-35ff135cf479

📥 Commits

Reviewing files that changed from the base of the PR and between f7ab628 and 79b200f.

📒 Files selected for processing (8)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx
  • src/components/Kyc/BridgeTosStep.tsx
  • src/utils/capability-gate.test.ts
  • src/utils/capability-gate.ts

CR nitpick: BE-emitted reason codes are typed as free-form string on the
contract, so equality comparisons in FE code don't get compile-time typo
protection. Extracting `bridge_tos_v2_required` to a `const`-typed identifier
catches misspellings of MY comparison string; the prop itself stays
`string` because the upstream type is still wider.
@Hugo0

Hugo0 commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

Merge order: depends on https://github.com/peanutprotocol/peanut-api-ts/pull/906 — the BE resolver PR that emits the new kind:'wait' NextAction (wait:bridge key) and the bridge_tos_v2_required reason code this FE consumes. Without #906 merged, this FE is functionally a no-op (no rail will ever return waiting-on-provider from deriveGate, and no BridgeTosStep will see reasonCode === 'bridge_tos_v2_required'). Merging this before #906 is safe (no regressions) but produces no observable user-facing change until #906 ships.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant