Skip to content

Fix/simplefi#1406

Merged
Hugo0 merged 4 commits intopeanut-walletfrom
fix/simplefi
Nov 5, 2025
Merged

Fix/simplefi#1406
Hugo0 merged 4 commits intopeanut-walletfrom
fix/simplefi

Conversation

@Hugo0
Copy link
Contributor

@Hugo0 Hugo0 commented Nov 5, 2025

Support SimpleFi URL changes & Fix KYC requirement

Problem

  1. SimpleFi changed their URLs:

    • Domain: pagar.simplefi.techpay.simplefi.tech
    • Dynamic format: /merchant/payment/123/merchant/123 (removed "payment" from path)
  2. SimpleFi payments incorrectly required KYC:

    • The useQrKycGate hook was applying identity verification checks to all QR payment types
    • SimpleFi payments don't require KYC but were being blocked

Solution

URL Support Changes:

  • Updated regex patterns to support both pagar.simplefi.tech (legacy) and pay.simplefi.tech (new)
  • Updated dynamic URL regex to match both /merchant/payment/123 (old) and /merchant/123 (new)
  • Maintains backward compatibility - all old URLs continue to work

KYC Fix:

  • Updated useQrKycGate hook to accept paymentProcessor parameter
  • Added early return that bypasses KYC checks when paymentProcessor === 'SIMPLEFI'
  • SimpleFi payments now proceed without identity verification modal

Changes

  • peanut-ui/src/components/Global/DirectSendQR/utils.ts: Updated 3 SimpleFi regex patterns
  • peanut-ui/src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts: Added 27 new test cases
  • peanut-ui/src/hooks/useQrKycGate.ts: Added payment processor awareness
  • peanut-ui/src/app/(mobile-ui)/qr-pay/page.tsx: Pass payment processor to KYC gate
  • peanut-api-ts/src/depositWatchService.ts: Fixed null check for log.args.from
  • peanut-api-ts/src/requestPaymentValidator.ts: Added debug log for ongoing Squid transactions

Risks

Low Risk - Fully Backward Compatible:

  • ✅ Old SimpleFi URLs (pagar.simplefi.tech, /payment/ format) continue to work
  • ✅ Manteca payments (PIX, Mercado Pago, QR3) still properly require KYC
  • ✅ No changes to core payment logic, only URL recognition and KYC gating
  • ✅ 224 tests passing (up from 213)

Testing

  • Verified both pagar and pay subdomains work
  • Verified both old and new dynamic URL formats work
  • Verified SimpleFi QR codes work without KYC modal
  • Verified Manteca QR codes still show KYC modal when appropriate

Note

Adds support for pay.simplefi.tech (incl. new dynamic format) and makes KYC gating apply only to MANTECA, not SimpleFi.

  • QR recognition (utils):
    • Update SIMPLEFI_* regexes to support both pagar.simplefi.tech and pay.simplefi.tech, and handle dynamic URLs with/without /payment/.
  • KYC gating (hooks/ui):
    • Make useQrKycGate(paymentProcessor) processor-aware; bypass KYC when paymentProcessor === 'SIMPLEFI'.
    • In app/(mobile-ui)/qr-pay/page.tsx, derive paymentProcessor from qrType and pass to useQrKycGate.
    • Default currency to ARS for SIMPLEFI_USER_SPECIFIED flows.
  • Tests:
    • Expand recognizeQr.test.ts with cases for pay.simplefi.tech, new dynamic format, and precedence rules.

Written by Cursor Bugbot for commit 45ee052. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Nov 5, 2025

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

Project Deployment Preview Comments Updated (UTC)
peanut-wallet Ready Ready Preview Comment Nov 5, 2025 8:49pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Derives a memoized paymentProcessor from QR type, passes it into the updated useQrKycGate hook (adding a SIMPLEFI fast-path), sets a default currency for SimpleFi USER_SPECIFIED flows, and expands SimpleFi QR regexes and tests to support both pagar.simplefi.tech and pay.simplefi.tech.

Changes

Cohort / File(s) Summary
Hook signature & logic
src/hooks/useQrKycGate.ts
Added optional `paymentProcessor?: 'MANTECA'
QR payment page
src/app/(mobile-ui)/qr-pay/page.tsx
Memoized paymentProcessor from qrType; replaced no-arg useQrKycGate() with useQrKycGate(paymentProcessor); removed duplicate memo; added effect to default currency for SimpleFi USER_SPECIFIED.
SimpleFi regexes
src/components/Global/DirectSendQR/utils.ts
Updated SIMPLEFI_STATIC_REGEX, SIMPLEFI_USER_SPECIFIED_REGEX, SIMPLEFI_DYNAMIC_REGEX to accept pagar.simplefi.tech and pay.simplefi.tech, support flexible path/query formats, and preserve captures.
Recognition tests
src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts
Added comprehensive cases for pay.simplefi.tech across STATIC/DYNAMIC/USER_SPECIFIED; added precedence/ordering and trailing-slash/query variations.
Manifest
package.json
Touched in diff snapshot; no public API signature changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Files needing extra attention:
    • src/hooks/useQrKycGate.ts — ensure SIMPLEFI fast-path doesn't regress MANTECA or other gating behavior.
    • src/components/Global/DirectSendQR/utils.ts — verify regex capture groups, precedence, and edge cases (trailing slashes, queries).
    • src/app/(mobile-ui)/qr-pay/page.tsx — confirm default-currency effect triggers only for intended USER_SPECIFIED flow and updates price data correctly.
    • Tests — ensure ordering assertions match intended recognition priority and cover regressions.

Possibly related PRs

Suggested reviewers

  • jjramirezn
  • kushagrasarathe
  • Zishan-7

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Fix/simplefi' is vague and generic; it does not convey meaningful information about the specific changes (URL support, KYC gating, or backward compatibility). Use a more descriptive title such as 'Support new SimpleFi pay.simplefi.tech domain and fix KYC requirement' or 'Fix SimpleFi URL support and KYC gating for SIMPLEFI processor'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the SimpleFi URL changes, KYC fix, specific files modified, and testing verification.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/simplefi

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1833035 and 45ee052.

📒 Files selected for processing (1)
  • src/app/(mobile-ui)/qr-pay/page.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/(mobile-ui)/qr-pay/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Deploy-Preview

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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)
src/hooks/useQrKycGate.ts (1)

33-71: Prevent stale Manteca requests from overriding the SimpleFi bypass
When a Manteca QR triggers the getBridgeCustomerCountry fetch and the user immediately scans a SimpleFi QR, the old Manteca promise can still resolve/reject afterwards and overwrite the new PROCEED_TO_PAY state (e.g. the catch path sets REQUIRES_IDENTITY_VERIFICATION). That breaks the SimpleFi KYC bypass intermittently. Please guard the async branch so only the latest paymentProcessor run is allowed to mutate state—for example, track the current processor in a ref and bail out before setting state if it no longer matches.

-import { useCallback, useState, useEffect } from 'react'
+import { useCallback, useState, useEffect, useRef } from 'react'
@@
-export function useQrKycGate(paymentProcessor?: 'MANTECA' | 'SIMPLEFI' | null): QrKycGateResult {
+export function useQrKycGate(paymentProcessor?: 'MANTECA' | 'SIMPLEFI' | null): QrKycGateResult {
     const { user } = useAuth()
     const [kycGateState, setKycGateState] = useState<QrKycState>(QrKycState.LOADING)
+    const latestPaymentProcessorRef = useRef(paymentProcessor)
+
+    useEffect(() => {
+        latestPaymentProcessorRef.current = paymentProcessor
+    }, [paymentProcessor])
 
     const determineKycGateState = useCallback(async () => {
+        const runProcessor = paymentProcessor
         // SimpleFi payments do not require KYC - allow payment immediately
-        if (paymentProcessor === 'SIMPLEFI') {
+        if (runProcessor === 'SIMPLEFI') {
             setKycGateState(QrKycState.PROCEED_TO_PAY)
             return
         }
@@
-        if (currentUser.bridgeKycStatus === 'approved' && currentUser.bridgeCustomerId) {
+        if (currentUser.bridgeKycStatus === 'approved' && currentUser.bridgeCustomerId) {
             try {
                 const { countryCode } = await getBridgeCustomerCountry(currentUser.bridgeCustomerId)
+                if (latestPaymentProcessorRef.current !== runProcessor) {
+                    return
+                }
                 // if (countryCode && countryCode.toUpperCase() === 'AR') {
                 if (false) {
                     setKycGateState(QrKycState.REQUIRES_MANTECA_KYC_FOR_ARG_BRIDGE_USER)
                 } else {
                     setKycGateState(QrKycState.PROCEED_TO_PAY)
                 }
             } catch {
+                if (latestPaymentProcessorRef.current !== runProcessor) {
+                    return
+                }
                 // fail to require identity verification to avoid blocking pay due to rare outages
                 setKycGateState(QrKycState.REQUIRES_IDENTITY_VERIFICATION)
             }
             return
         }
     }, [user?.user, paymentProcessor])
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87019db and 2f55dec.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/qr-pay/page.tsx (1 hunks)
  • src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts (5 hunks)
  • src/components/Global/DirectSendQR/utils.ts (1 hunks)
  • src/hooks/useQrKycGate.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1014
File: src/components/Claim/Link/Initial.view.tsx:413-413
Timestamp: 2025-07-24T13:26:10.290Z
Learning: In the peanut-ui repository, the change from `${SQUID_API_URL}/route` to `${SQUID_API_URL}/v2/route` in src/components/Claim/Link/Initial.view.tsx was a typo fix, not an API migration, as the codebase was already using Squid API v2.
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.
📚 Learning: 2025-08-11T10:35:02.715Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1078
File: src/hooks/useKycFlow.ts:129-141
Timestamp: 2025-08-11T10:35:02.715Z
Learning: In the KYC flow implementation in `src/hooks/useKycFlow.ts`, when Terms of Service (ToS) is accepted, there will always be a KYC link available in the `apiResponse`. The system ensures this invariant, so defensive checks for missing KYC links after ToS acceptance are unnecessary.

Applied to files:

  • src/hooks/useQrKycGate.ts
📚 Learning: 2025-08-20T09:08:19.266Z
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1112
File: src/components/Claim/Link/views/BankFlowManager.view.tsx:336-343
Timestamp: 2025-08-20T09:08:19.266Z
Learning: In the KYC flow implementation, `setJustCompletedKyc` must be called after `await fetchUser()` in the `handleKycSuccess` callback. Setting `justCompletedKyc` before fetching the user would cause a re-fetching loop because `handleKycSuccess` is set in a useEffect inside the KYC hook, which would cause the UI flow to get stuck in one view.

Applied to files:

  • src/hooks/useQrKycGate.ts
📚 Learning: 2024-10-23T09:38:04.446Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 469
File: src/app/request/pay/page.tsx:32-64
Timestamp: 2024-10-23T09:38:04.446Z
Learning: Within `src/app/request/pay/page.tsx`, extracting the `getBaseUrl` function does not add significant readability, and the host URL construction code is expected to change soon.

Applied to files:

  • src/components/Global/DirectSendQR/utils.ts
  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-07T15:25:45.170Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:25:45.170Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(...)` return strings, ensuring that `calculatedFee` consistently returns a string without the need for additional type conversion.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-18T09:30:42.901Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1230
File: src/app/(mobile-ui)/withdraw/page.tsx:92-97
Timestamp: 2025-09-18T09:30:42.901Z
Learning: In src/app/(mobile-ui)/withdraw/page.tsx, the useEffect that calls setShowAllWithdrawMethods(true) when amountFromContext exists is intentionally designed to run only on component mount (empty dependency array), not when amountFromContext changes. This is the correct behavior for the withdraw flow where showing all methods should only happen on initial load when an amount is already present.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-10-02T15:23:01.513Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1266
File: src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx:46-57
Timestamp: 2025-10-02T15:23:01.513Z
Learning: In the withdraw flow at src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx, the points calculation query intentionally uses crypto.randomUUID() in the queryKey dependency array to bypass React Query caching, ensuring fresh points estimates on every render. This is the intended behavior.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-07T15:28:25.280Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 422
File: src/components/Request/Pay/Views/Initial.view.tsx:76-78
Timestamp: 2024-10-07T15:28:25.280Z
Learning: In `src/components/Request/Pay/Views/Initial.view.tsx`, both `txFee` and `utils.formatTokenAmount(estimatedGasCost, 3)` return strings, ensuring consistent return types for `calculatedFee`.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:10:56.955Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 469
File: src/app/request/pay/page.tsx:25-25
Timestamp: 2024-10-22T18:10:56.955Z
Learning: In the `src/app/request/pay/page.tsx` file, the `PreviewType` enum values are strings, so when adding `previewType` to `URLSearchParams`, there's no need to convert them to strings.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-08-22T07:28:32.281Z
Learnt from: Zishan-7
Repo: peanutprotocol/peanut-ui PR: 1104
File: src/components/Payment/PaymentForm/index.tsx:522-545
Timestamp: 2025-08-22T07:28:32.281Z
Learning: In `src/components/Payment/PaymentForm/index.tsx`, the `handleCompleteDaimoPayment` function is only for updating payment status in the backend after a successful Daimo payment. Payment success/failure is handled by Daimo itself, so try/catch error handling and error display are not needed for backend sync failures - users shouldn't see errors if payment succeeded but database update failed.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2025-09-08T03:13:09.111Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page after scanning MERCADO_PAGO or PIX QR codes.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
📚 Learning: 2024-10-22T18:11:36.864Z
Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 469
File: src/app/request/pay/page.tsx:32-49
Timestamp: 2024-10-22T18:11:36.864Z
Learning: In `src/app/request/pay/page.tsx`, the `id` parameter is accessed via `searchParams.id` in the `generateMetadata` function.

Applied to files:

  • src/app/(mobile-ui)/qr-pay/page.tsx
🧬 Code graph analysis (3)
src/hooks/useQrKycGate.ts (1)
src/context/authContext.tsx (1)
  • useAuth (192-198)
src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts (1)
src/components/Global/DirectSendQR/utils.ts (1)
  • recognizeQr (113-135)
src/app/(mobile-ui)/qr-pay/page.tsx (1)
src/hooks/useQrKycGate.ts (1)
  • useQrKycGate (29-96)
🪛 GitHub Actions: Tests
src/hooks/useQrKycGate.ts

[warning] 1-1: Code style issues found in the above file. Run Prettier with --write to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Deploy-Preview

@Hugo0
Copy link
Contributor Author

Hugo0 commented Nov 5, 2025

@coderabbitai review deeply

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the modified files for deep review
echo "=== Fetching utils.ts ==="
cat src/components/Global/DirectSendQR/utils.ts

echo -e "\n\n=== Fetching recognizeQr.test.ts ==="
cat src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts

echo -e "\n\n=== Fetching useQrKycGate.ts ==="
cat src/hooks/useQrKycGate.ts

echo -e "\n\n=== Fetching qr-pay page.tsx ==="
cat src/app/\(mobile-ui\)/qr-pay/page.tsx

echo -e "\n\n=== Checking for any other SimpleFi or KYC related files ==="
rg -n -C2 "SIMPLEFI|SimpleFi" --type=ts --type=tsx -g '!node_modules' -g '!*.test.ts' -g '!*.spec.ts' | head -100

Length of output: 110506


@Hugo0, I'll conduct a comprehensive deep review of this PR. Let me analyze the security, correctness, and edge cases.

🔍 Deep Review Findings

1. SECURITY CONCERN: Regex Patterns Allow Empty Merchant Slugs

The SIMPLEFI_USER_SPECIFIED_REGEX pattern allows capturing empty merchant slugs:

SIMPLEFI_USER_SPECIFIED_REGEX =
    /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantSlug>[^\/\?]*)(?:\/)?(?:\?.*)?$/

Issue: [^\/\?]* allows zero or more characters, meaning:

  • https://pay.simplefi.tech/ → matches with merchantSlug = ""
  • https://pagar.simplefi.tech/?foo=bar → matches with merchantSlug = ""

Impact: In qr-pay/page.tsx line 135-140, when simpleFiQrData?.type === 'SIMPLEFI_USER_SPECIFIED', the code constructs a merchant name from the slug:

return simpleFiQrData.merchantSlug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())

This will return an empty string for display, which is poor UX.

Recommendation: Change regex to require at least one character:

SIMPLEFI_USER_SPECIFIED_REGEX =
    /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantSlug>[^\/\?]+)(?:\/)?(?:\?.*)?$/
    //                                                                              ^ plus instead of asterisk

2. SECURITY: KYC Bypass Logic Needs Validation

The KYC bypass in useQrKycGate.ts (lines 39-42) is implemented correctly with early return:

if (paymentProcessor === 'SIMPLEFI') {
    setKycGateState(QrKycState.PROCEED_TO_PAY)
    return
}

✅ Good: The bypass happens at the hook level before any user checks.

⚠️ Concern: The paymentProcessor calculation in qr-pay/page.tsx (lines 64-76) relies entirely on the qrType URL parameter which comes from searchParams:

const qrType = searchParams.get('type')
const paymentProcessor: PaymentProcessor | null = useMemo(() => {
    switch (qrType) {
        case EQrType.SIMPLEFI_STATIC:
        // ...

Security Question: Can users manipulate the URL to bypass KYC? Let me trace the flow:


🏁 Script executed:

#!/bin/bash
# Find where the qr-pay route is called and how qrType parameter is set
echo "=== Searching for qr-pay route navigation ==="
rg -nP -C5 "router\.(push|replace)\(['\"].*qr-pay" --type=ts --type=tsx -g '!node_modules' -g '!*.test.ts'

echo -e "\n=== Searching for qrType parameter usage ==="
rg -nP -C3 "type.*=.*EQrType\.|qrType.*=" --type=ts --type=tsx -g '!node_modules' -g '!*.test.ts' -g '!qr-pay/page.tsx' | head -80

Length of output: 147


🏁 Script executed:

#!/bin/bash
# Find where the qr-pay route is called and how qrType parameter is set
echo "=== Searching for qr-pay route navigation ==="
rg -n -C5 "qr-pay" -g "*.ts" -g "*.tsx" -g '!node_modules' -g '!*.test.ts' -g '!qr-pay/page.tsx' | head -100

echo -e "\n=== Searching for DirectSendQR component usage ==="
rg -n -C5 "DirectSendQR" -g "*.ts" -g "*.tsx" -g '!node_modules' -g '!*.test.ts' -g '!utils.ts' | head -80

Length of output: 9273


🏁 Script executed:

#!/bin/bash
# Check how DirectSendQR component determines qrType from QR code
echo "=== DirectSendQR component - QR recognition flow ==="
cat src/components/Global/DirectSendQR/index.tsx | grep -A 30 "recognizeQr" | head -50

echo -e "\n=== Check if qr-pay page validates qrCode against qrType ==="
rg -n -C3 "isPaymentProcessorQR\(qrCode\)" -g "*.tsx" -g "*.ts" -g '!node_modules'

Length of output: 3110


🏁 Script executed:

#!/bin/bash
# Check if qr-pay page re-validates qrType against qrCode
echo "=== Check for qrCode re-validation against qrType ==="
rg -n -C5 "recognizeQr\(qrCode\)" src/app/\(mobile-ui\)/qr-pay/page.tsx

echo -e "\n=== Check SimpleFi API backend validation ==="
cat src/services/simplefi.ts

echo -e "\n=== Check Manteca API backend validation ==="
cat src/services/manteca.ts | head -150

Length of output: 6559


🏁 Script executed:

#!/bin/bash
# Check regex edge cases and potential issues
echo "=== Testing SIMPLEFI_DYNAMIC_REGEX edge cases ==="
cat << 'EOF' > test_regex.js
const SIMPLEFI_DYNAMIC_REGEX = /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantId>[^\/]*)\/(?:payment\/)?(?<paymentId>[^\/\?]+)(?:\/)?(?:\?.*)?$/

const testCases = [
    // Valid cases
    "https://pay.simplefi.tech/merchant/123",
    "https://pagar.simplefi.tech/merchant/payment/123",
    
    // Edge cases that might be problematic
    "https://pay.simplefi.tech//123",  // double slash
    "https://pay.simplefi.tech/ /123", // space in merchantId
    "https://pay.simplefi.tech/merchant/", // empty paymentId
    "https://pay.simplefi.tech/merchant/payment/", // empty paymentId with payment/
]

testCases.forEach(url => {
    const match = url.match(SIMPLEFI_DYNAMIC_REGEX)
    if (match) {
        console.log(`✓ MATCHED: ${url}`)
        console.log(`  merchantId: "${match.groups.merchantId}"`)
        console.log(`  paymentId: "${match.groups.paymentId}"`)
    } else {
        console.log(`✗ NO MATCH: ${url}`)
    }
})
EOF
node test_regex.js
rm test_regex.js

echo -e "\n=== Check for XSS vulnerabilities in merchant name display ==="
rg -n "merchantSlug.*replace" src/app/\(mobile-ui\)/qr-pay/page.tsx

Length of output: 735


🏁 Script executed:

#!/bin/bash
# Check test coverage for edge cases
echo "=== Check if tests cover empty or whitespace merchant IDs ==="
rg -n "merchantId.*empty|empty.*merchant|whitespace" src/components/Global/DirectSendQR/__tests__/recognizeQr.test.ts

echo -e "\n=== Check backend API validation ==="
echo "Looking for peanut-api-ts validation files..."
fd "simplefi|qr-pay" peanut-api-ts --type f | head -10

echo -e "\n=== Check if there's input sanitization ==="
rg -n "sanitize|escape|xss" src/app/\(mobile-ui\)/qr-pay/page.tsx

Length of output: 497


🏁 Script executed:

#!/bin/bash
# Check if there are race conditions in the KYC gate dependency array
echo "=== useQrKycGate dependency analysis ==="
cat src/hooks/useQrKycGate.ts | grep -A 5 "useCallback\|useEffect"

echo -e "\n=== Check if paymentProcessor can change mid-flow ==="
rg -n "setPaymentProcessor|paymentProcessor.*=" src/app/\(mobile-ui\)/qr-pay/page.tsx | head -20

Length of output: 2026


@Hugo0, I've completed a comprehensive deep review. Here are my findings:

🚨 MEDIUM PRIORITY: Regex Allows Empty/Whitespace Merchant IDs

Your regexes allow empty and whitespace-only values in critical fields:

// Current implementation
SIMPLEFI_USER_SPECIFIED_REGEX = /(?<merchantSlug>[^\/\?]*)/  // * allows ZERO or more
SIMPLEFI_DYNAMIC_REGEX = /(?<merchantId>[^\/]*)/             // * allows ZERO or more

// Edge cases that PASS validation:
"https://pay.simplefi.tech/ /123"       merchantId = " " (space)
"https://pay.simplefi.tech//123"        merchantId = ""  (empty)
"https://pay.simplefi.tech/"            merchantSlug = "" (empty)

Impact on UX: Line 432 in qr-pay/page.tsx:

return simpleFiQrData.merchantSlug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())

This displays an empty merchant name when merchantSlug is empty or whitespace-only.

Recommendation: Require at least one character:

export const SIMPLEFI_STATIC_REGEX =
    /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantSlug>[^\/]+)(?:\/static|\/?\?.*static\=true.*)/
    //                                                                              ^^^ plus instead of asterisk
export const SIMPLEFI_USER_SPECIFIED_REGEX =
    /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantSlug>[^\/\?]+)(?:\/)?(?:\?.*)?$/
    //                                                                              ^^^
export const SIMPLEFI_DYNAMIC_REGEX =
    /^(?:https?:\/\/)?(?:www\.)?(?:pagar|pay)\.simplefi\.tech\/(?<merchantId>[^\/]+)\/(?:payment\/)?(?<paymentId>[^\/\?]+)(?:\/)?(?:\?.*)?$/
    //                                                                              ^^^                              ^^^

SECURITY VALIDATED: KYC Bypass is Safe

I traced the entire flow and confirmed the KYC bypass for SimpleFi is secure:

Trust Boundary Analysis:

  1. DirectSendQR/index.tsx:329 - Frontend determines qrType by calling recognizeQr(data) before navigation
  2. qr-pay/page.tsx:238 - Validates actual QR code content with isPaymentProcessorQR(qrCode)
  3. ✅ Backend APIs require JWT authentication
  4. ✅ Manteca API receives the actual QR code content for server-side validation
  5. ✅ SimpleFi API receives merchant slug/ID and validates merchant existence

Conclusion: Even if a user manipulates the URL parameter ?type=SIMPLEFI_STATIC, the backend will reject invalid payment requests because it validates the actual QR code content.


🟡 Test Coverage Gaps

Your tests don't cover these important scenarios:

  1. Malicious inputs: XSS payloads in merchant names like <script>alert(1)</script>-merchant
  2. Special characters: Unicode, emojis, control characters in merchant slugs
  3. Backend validation: Tests only check regex matching, not end-to-end flow
  4. Empty value handling: Tests acknowledge empty slugs (lines 406, 411) but don't test the UX impact

Recommendation: Add tests for:

describe('SIMPLEFI edge cases', () => {
    it('should NOT recognize URLs with empty merchant slugs', () => {
        expect(recognizeQr('https://pay.simplefi.tech/')).toBeNull()
        expect(recognizeQr('https://pay.simplefi.tech/ ')).toBeNull()
    })
    
    it('should handle special characters safely', () => {
        const xssSlug = 'https://pay.simplefi.tech/<script>alert(1)</script>'
        // Should either reject or properly escape
    })
})

Code Quality: What's Working Well

  1. Backward Compatibility: Both pagar.simplefi.tech and pay.simplefi.tech supported ✅
  2. Flexible Paths: Handles both /merchant/payment/123 and /merchant/123 formats ✅
  3. KYC Hook Dependencies: useQrKycGate.ts:88 correctly includes both user?.user and paymentProcessor
  4. Defense in Depth: Backend validation provides security even if frontend is bypassed ✅
  5. XSS Protection: React automatically escapes text content, preventing XSS ✅

🔍 Minor Issues

1. Inconsistent Error Handling for SimpleFi USER_SPECIFIED

In qr-pay/page.tsx lines 566-568, if SimpleFi payment initiation fails for USER_SPECIFIED type, you set a generic error message. Consider showing the actual SimpleFi error message like you do for STATIC/DYNAMIC types.

2. Potential Race Condition in Retry Logic

Lines 819-831: The retry logic for SimpleFi uses retryCount.current but doesn't reset it when the user navigates away and returns. Consider resetting in the cleanup effect.

3. Missing Merchant Name Validation

Line 432 transforms merchant slug but doesn't validate the result. Add validation:

const merchantName = simpleFiQrData.merchantSlug
    .replace(/-/g, ' ')
    .replace(/\b\w/g, (c) => c.toUpperCase())
    .trim()
return merchantName || 'SimpleFi Merchant'  // Fallback for empty names

📋 Summary

Category Status Priority
KYC Bypass Security ✅ Safe ✅ Verified
Regex Empty Values ⚠️ Issue 🟡 Medium
XSS Vulnerabilities ✅ Safe ✅ Mitigated by React
Test Coverage ⚠️ Gaps 🟡 Medium
Backward Compatibility ✅ Good ✅ Maintained
Code Dependencies ✅ Correct ✅ No issues

Overall Assessment: The PR is functionally sound with good backward compatibility. The main concerns are UX issues from empty merchant IDs and test coverage gaps. The KYC bypass is secure due to backend validation.

Recommendation: Fix the regex to require at least one character in merchant IDs before merging, or add validation in the merchant name display logic to handle empty values gracefully.

Would you like me to create an issue to track the regex improvements and test coverage enhancements?


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 823
File: src/context/kernelClient.context.tsx:79-86
Timestamp: 2025-04-29T19:36:38.121Z
Learning: When Hugo0 asks to "resolve coderabbit comments", they want to acknowledge the comment without necessarily implementing the suggested changes, as the current implementation might be intentional for their specific use case.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 942
File: src/components/AddMoney/consts/index.ts:2151-2162
Timestamp: 2025-06-30T10:44:08.048Z
Learning: Hugo0 often agrees with refactoring suggestions but defers implementation due to time constraints, preferring to track improvements as follow-up issues when they're part of larger architectural changes.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1014
File: src/components/Claim/Link/Initial.view.tsx:413-413
Timestamp: 2025-07-24T13:26:10.290Z
Learning: In the peanut-ui repository, the change from `${SQUID_API_URL}/route` to `${SQUID_API_URL}/v2/route` in src/components/Claim/Link/Initial.view.tsx was a typo fix, not an API migration, as the codebase was already using Squid API v2.

Learnt from: jjramirezn
Repo: peanutprotocol/peanut-ui PR: 1190
File: src/app/(mobile-ui)/qr-pay/page.tsx:156-176
Timestamp: 2025-09-08T03:13:09.111Z
Learning: In the peanut-ui mobile app, the `/qr-pay` route is only accessed through the DirectSendQR component which always includes the qrCode parameter in the URL when redirecting users to the QR pay page.

@Hugo0 Hugo0 merged commit 5264ed0 into peanut-wallet Nov 5, 2025
6 checks passed
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