merge: sync main hotfixes into dev#1963
Conversation
…andling
the bridge KYC state handling in transfer flows (withdraw, deposit, claim, qr-pay)
had multiple issues: wrong priority order, missing ToS checks, redirects instead of
inline flows, and broken rejection handling.
changes:
- new `useBridgeTransferReadiness` hook: unified pre-transfer gate with correct
priority (hard rejection → tos → fixable rejection → enrollment → ready)
- `useBridgeTosStatus`: expanded to check `tos_acceptance`/`tos_v2_acceptance` in
rail `additionalRequirements` metadata, not just `REQUIRES_INFORMATION` status
- `InitiateKycModal`: added `blocked` variant (contact support CTA), `error` prop
for failed self-heal attempts
- withdraw/bank, add-money/bank, AddWithdrawCountriesList: replaced fragmented
`needsBridgeEnrollment` + `guardWithTos` + `bridgeRejection` checks with unified gate
- BankFlowManager (claim flow): added ToS guard + rejection handling for non-guest paths
- qr-pay: replaced redirect-based KYC (`router.push('/profile/identity-verification')`)
with inline sumsub SDK initiation (`handleInitiateKyc('LATAM')`)
- BridgeTosStep: show confirmation modal before iframe, prevent modal flash during
confirmation with `isConfirming` state
- KycProviderRejection, ActivationCTAs, RegionsVerification: replaced broken
`$crisp.push` calls with `setIsSupportModalOpen`
- useSumsubKycFlow: fixed race condition where `fetchCurrentStatus` closes SDK wrapper
on first attempt by guarding with `showWrapperRef`
- useProviderRejectionStatus: permanent rejections now show generic support message
instead of misleading "upload a clearer photo"
- DynamicBankAccountForm: `__silent__` sentinel to prevent form navigation when gate blocks
- close kyc modal via `sumsubFlow.showWrapper` effect instead of in `onVerify` callback
…ata cast, gate fallthrough - BridgeTosStep: wrap confirmBridgeTosAndAwaitRails in try/catch to prevent isConfirming getting stuck on error - useBridgeTosStatus: use Array.isArray guard for additionalRequirements metadata - all gate checks: always return when gate is not ready, even if guardWithTos returns false (defensive)
…lt, dedup variant mapping
- useSumsubKycFlow: save/restore prevStatusRef on crossRegion failure to prevent
suppressing subsequent legitimate APPROVED transitions
- DynamicBankAccountForm: replace __silent__ magic string with typed { silent: true }
- useBridgeTransferReadiness: extract getKycModalVariant() and getGateProviderMessage()
helpers to deduplicate gate→modal mapping across 4 files
- BankFlowManager: only close kyc modal if sdk opened (check showWrapper), preserving
error visibility on failure
…minate SDK auto-close race the fetchCurrentStatus effect could still race with handleInitiateKyc in rare cases — initiatingRef resets in the finally block before React batches the showWrapper state update. adding userInitiatedRef as a guard permanently disables the background fetch after any user-initiated flow, since the SDK and websocket handle status updates from that point.
Users paying with a Pix QR were told to ask the merchant for a Mercado Pago QR, which is wrong and confusing. Branch the message on qrType so Pix scans get Pix-specific guidance. Also capture qr_decoding_error_shown so we can measure how often this fallback fires and which rails are most affected.
Per CodeRabbit review on #1948: ARGENTINA_QR3 was hitting the default branch and getting told to ask the merchant for a Mercado Pago QR — QR3 and Mercado Pago are distinct Argentine standards, so the copy was actively misleading. Now branches PIX, ARGENTINA_QR3, and the MP default explicitly.
In Argentina, Mercado Pago is the dominant rail and a workable fallback when a QR3 code fails to decode — so suggesting "ask for a Mercado Pago QR" is the most useful guidance for QR3 users too. Only Pix needs the rail-specific copy because Brazil has no equivalent fallback. Reverts the previous QR3-specific message; adds a comment explaining the regional reasoning so future readers don't trip on the same point CodeRabbit raised.
…hine fix: bridge KYC state machine — unified gate, inline ToS, rejection handling
Signed-off-by: Juan José Ramírez <70615692+jjramirezn@users.noreply.github.com>
…rror-message [TASK-19529] fix(qr-pay): Pix-specific copy on QR decoding error + capture event
Sister change to peanut-api-ts hotfix/bridge-developer-fee-zero. With the backend no longer attaching developer_fee_percent on Bridge transfers, the UI's "amount you will receive" math must stop deducting the same 50bps locally — otherwise we under-quote. Setting BRIDGE_DEVELOPER_FEE_RATE to 0 turns applyBridgeCrossCurrencyFee and reverseBridgeCrossCurrencyFee into identity functions, which matches what Bridge actually delivers. The hardcoded "Fee: $ 0.00" PaymentInfoRows in withdraw/bank-claim flows now become truthful. Tests are reparameterized against the constant so they stay correct when the FX-spread followup re-enables a margin.
…fee-zero hotfix(bridge): zero-out cross-currency dev fee to match backend
ci: fix auto-bump PR base + add pull_request trigger to Tests
brings in: - fix(bridge): zero-out cross-currency dev fee (#1957) - fix(qr-pay): Pix-specific decoding error copy (#1948) - ci: push trigger + pull_request for API-created PRs (#1956) - ci: target dev for content submodule bumps conflicts resolved: - tests.yml: combined both triggers (push + pull_request + workflow_dispatch + permissions) - qr-pay: take main's useQrKycGate(paymentProcessor) signature - AddWithdrawCountriesList: keep dev's native routing (query param support) - analytics.consts: keep both QR_NOTIFY_ME_CLICKED and QR_DECODING_ERROR_SHOWN - useBridgeTransferReadiness.test: take main's stricter typing (no `as any`)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis PR makes QR-pay KYC gating processor-aware and adds QR-type-specific decoding-error copy with a new analytics event; sets the bridge developer fee rate to 0 and updates tests to compute expected outcomes from the shared constant; and expands CI test triggers and sets update-content PR base to dev. ChangesQR Pay Error Messaging and Analytics
Bridge Developer Fee Rate and Test Updates
CI Workflow Configuration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
Code-analysis diffPainscore total: 5748.41 → 5728.23 (-20.18) 🆕 New findings (25)
…and 5 more. ✅ Resolved (25)
…and 5 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/update-content.yml (1)
45-69:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAlign commit parent with the PR base branch (
dev).The workflow uses the default branch (
main) as the parent commit but hardcodes--base dev. Ifmainanddevhave diverged, unrelated commits frommainwill be included in the PR.Suggested fix
run: | + BASE_BRANCH="dev" BRANCH="auto/update-content-$(date -u +%Y%m%d-%H%M%S)" SUBMODULE_SHA=$(git -C src/content rev-parse HEAD) - PARENT=$(git rev-parse HEAD) + PARENT=$(gh api repos/${{ github.repository }}/git/ref/refs/heads/$BASE_BRANCH --jq '.object.sha') BASE_TREE=$(gh api repos/${{ github.repository }}/git/commits/$PARENT --jq '.tree.sha') @@ gh pr create \ --head "$BRANCH" \ - --base dev \ + --base "$BASE_BRANCH" \ --title "Update content submodule" \ --body "Auto-generated: updates content submodule to latest peanut-content main."🤖 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 @.github/workflows/update-content.yml around lines 45 - 69, The workflow is creating the commit with PARENT set to the default branch instead of the PR base (dev), causing unrelated commits to be included; update the logic that computes PARENT so it resolves the commit SHA of the target PR base (dev) and use that SHA for BASE_TREE and as the parents[] when creating the commit (affecting the variables PARENT, BASE_TREE, TREE, COMMIT_SHA, and BRANCH), e.g. fetch the ref for the dev branch via the GitHub API and assign that SHA to PARENT before creating the tree/commit/branch and opening the PR with --head "$BRANCH" --base dev.
🤖 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.
Inline comments:
In `@src/app/`(mobile-ui)/qr-pay/page.tsx:
- Line 112: The call site passes paymentProcessor into useQrKycGate but the hook
signature (useQrKycGate) currently accepts no parameters; either remove the
argument from the call (change useQrKycGate(paymentProcessor) to useQrKycGate())
to match the existing export, or modify the hook definition (export function
useQrKycGate(paymentProcessor: PaymentProcessorType): QrKycGateResult) and
update its implementation to consume the paymentProcessor parameter where needed
(adjust types and any internal references inside useQrKycGate accordingly).
---
Outside diff comments:
In @.github/workflows/update-content.yml:
- Around line 45-69: The workflow is creating the commit with PARENT set to the
default branch instead of the PR base (dev), causing unrelated commits to be
included; update the logic that computes PARENT so it resolves the commit SHA of
the target PR base (dev) and use that SHA for BASE_TREE and as the parents[]
when creating the commit (affecting the variables PARENT, BASE_TREE, TREE,
COMMIT_SHA, and BRANCH), e.g. fetch the ref for the dev branch via the GitHub
API and assign that SHA to PARENT before creating the tree/commit/branch and
opening the PR with --head "$BRANCH" --base dev.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 079e99d8-515a-4b17-a06e-ad1c7dec036e
📒 Files selected for processing (7)
.github/workflows/tests.yml.github/workflows/update-content.ymlsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/constants/analytics.consts.tssrc/constants/payment.consts.tssrc/hooks/__tests__/useBridgeTransferReadiness.test.tssrc/utils/__tests__/bridge.utils.test.ts
- useQrKycGate: restore paymentProcessor param (regressed by cherry-pick), remove SimpleFi references (removed from dev), keep user?.rails dep - useBridgeTransferReadiness.test: use ProviderRejectionState type instead of `as const` to allow state comparisons in mock setup
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useQrKycGate.ts (1)
30-127:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse
paymentProcessorto actually scope Manteca-only gatingLine 30 introduces
paymentProcessor, but it is never used in the decision tree. As written, Lines 66-115 still enforce Manteca rejection/KYC paths for every rail, so non-Manteca QR flows can be incorrectly blocked by Manteca history.Suggested fix
export function useQrKycGate(paymentProcessor?: 'MANTECA' | null): QrKycGateResult { const { user, isFetchingUser, fetchUser } = useAuth() + const isMantecaProcessor = paymentProcessor === 'MANTECA' @@ if (hasSumsubApproved) { - // check if manteca has rejected rails (qr payments use manteca) - const rejectedMantecaRails = (user?.rails ?? []).filter( - (r) => r.rail.provider.code === 'MANTECA' && r.status === 'REJECTED' - ) - if (rejectedMantecaRails.length > 0) { - const railMeta = (rejectedMantecaRails[0].metadata ?? {}) as Record<string, unknown> - const mantecaKyc = currentUser.kycVerifications - ?.filter((v) => v.provider === 'MANTECA') - .sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())[0] - const kycMeta = (mantecaKyc?.metadata ?? {}) as Record<string, unknown> - const isFixable = - railMeta.selfHealable === true && - mantecaKyc?.rejectType !== 'PROVIDER_FINAL' && - ((kycMeta.selfHealAttempt as number) || 0) < MAX_SELF_HEAL_ATTEMPTS - setKycGateState( - isFixable ? QrKycState.PROVIDER_REJECTION_FIXABLE : QrKycState.PROVIDER_REJECTION_BLOCKED - ) - return + if (isMantecaProcessor) { + const rejectedMantecaRails = (user?.rails ?? []).filter( + (r) => r.rail.provider.code === 'MANTECA' && r.status === 'REJECTED' + ) + if (rejectedMantecaRails.length > 0) { + const railMeta = (rejectedMantecaRails[0].metadata ?? {}) as Record<string, unknown> + const mantecaKyc = currentUser.kycVerifications + ?.filter((v) => v.provider === 'MANTECA') + .sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())[0] + const kycMeta = (mantecaKyc?.metadata ?? {}) as Record<string, unknown> + const isFixable = + railMeta.selfHealable === true && + mantecaKyc?.rejectType !== 'PROVIDER_FINAL' && + ((kycMeta.selfHealAttempt as number) || 0) < MAX_SELF_HEAL_ATTEMPTS + setKycGateState( + isFixable ? QrKycState.PROVIDER_REJECTION_FIXABLE : QrKycState.PROVIDER_REJECTION_BLOCKED + ) + return + } } setKycGateState(QrKycState.PROCEED_TO_PAY) return } - const mantecaKycs = currentUser.kycVerifications?.filter((v) => v.provider === 'MANTECA') ?? [] + const mantecaKycs = isMantecaProcessor + ? currentUser.kycVerifications?.filter((v) => v.provider === 'MANTECA') ?? [] + : []🤖 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/hooks/useQrKycGate.ts` around lines 30 - 127, The code never uses the paymentProcessor param so MANTECA-specific checks are applied to all flows; update useQrKycGate.determineKycGateState to only run Manteca-specific logic when paymentProcessor === 'MANTECA'. Concretely, wrap the rejectedMantecaRails check and the mantecaKyc/mantecaKycs branches (references: rejectedMantecaRails, mantecaKyc, mantecaKycs, MantecaKycStatus) in a guard such as if (paymentProcessor === 'MANTECA') { ... } and otherwise skip to the non-Manteca paths (e.g., treat sumsub-approved or bridge KYC as allowing PROCEED_TO_PAY without consulting Manteca rails). Ensure the dependency array still includes paymentProcessor.
🤖 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.
Outside diff comments:
In `@src/hooks/useQrKycGate.ts`:
- Around line 30-127: The code never uses the paymentProcessor param so
MANTECA-specific checks are applied to all flows; update
useQrKycGate.determineKycGateState to only run Manteca-specific logic when
paymentProcessor === 'MANTECA'. Concretely, wrap the rejectedMantecaRails check
and the mantecaKyc/mantecaKycs branches (references: rejectedMantecaRails,
mantecaKyc, mantecaKycs, MantecaKycStatus) in a guard such as if
(paymentProcessor === 'MANTECA') { ... } and otherwise skip to the non-Manteca
paths (e.g., treat sumsub-approved or bridge KYC as allowing PROCEED_TO_PAY
without consulting Manteca rails). Ensure the dependency array still includes
paymentProcessor.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3f9208fd-1941-41b0-bbbe-2e454b92986f
📒 Files selected for processing (2)
src/hooks/__tests__/useBridgeTransferReadiness.test.tssrc/hooks/useQrKycGate.ts
✅ Files skipped from review due to trivial changes (1)
- src/hooks/tests/useBridgeTransferReadiness.test.ts
Summary
Changes synced from main:
pushtrigger +pull_requestso API-created PRs run tests (ci: fix auto-bump PR base + add pull_request trigger to Tests #1956)Conflict resolutions:
tests.ymlpushtrigger + dev'sworkflow_dispatch+permissionsqr-pay/page.tsxuseQrKycGate(paymentProcessor)needed for rail-specific gatingAddWithdrawCountriesList.tsxanalytics.consts.tsQR_NOTIFY_ME_CLICKED+QR_DECODING_ERROR_SHOWN)useBridgeTransferReadiness.test.tsas consttyping instead ofas anyTest plan