Skip to content

feat(rn_cli_wallet): per-option collectData for WalletConnect Pay#387

Open
ganchoradkov wants to merge 4 commits intomainfrom
feat/per-option-collect-data
Open

feat(rn_cli_wallet): per-option collectData for WalletConnect Pay#387
ganchoradkov wants to merge 4 commits intomainfrom
feat/per-option-collect-data

Conversation

@ganchoradkov
Copy link
Member

Summary

  • Per-option collectData flow: The getPaymentOptions API now returns collectData on each option. The payment modal shows options immediately after scanning, with an "Info required" pill on options needing data collection. Selecting such an option changes the button to "Next" which opens the per-option WebView.
  • Simplified options UI: Removed the collapsible "Pay with" dropdown in favor of a flat list of options. After completing collectData for an option, only that option remains visible.
  • Info explainer: Added a "Why info required?" button (matching the close button style) that opens an in-modal explainer view with a "Got it!" dismissal.
  • WebView improvements: Added bottom safe area padding so content isn't hidden by device nav bar. Injected viewport JS to fit form content without scrolling.
  • Dependency updates: Bumped @walletconnect/react-native-compat and @walletconnect/pay to canary versions with per-option collectData support.
flowchart TD
    A[Scan QR] --> B[Loading]
    B --> C[Confirm - show all options]
    C -->|Option needs collectData| D{Button: Next}
    C -->|Option has no collectData| E{Button: Pay}
    D -->|Tap Next| F[CollectData WebView]
    F -->|Complete| G[Confirm - show only completed option]
    G --> E
    E -->|Tap Pay| H[Confirming]
    H --> I[Result]
    C -->|Why info required?| J[Info Explainer]
    J -->|Got it!| C
Loading

Test plan

  • Scan a payment QR that returns options with per-option collectData
  • Verify options list shows immediately with "Info required" pills
  • Select an option with collectData → button shows "Next"
  • Select an option without collectData → button shows "Pay $X"
  • Tap "Next" → WebView opens with correct per-option URL
  • Complete WebView form → returns to confirm with only the completed option visible
  • Tap "Pay" → payment completes successfully
  • Tap "Why info required?" → explainer view shows
  • Tap "Got it!" → returns to options
  • WebView content fits without scrolling and respects bottom safe area

Made with Cursor

ganchoradkov and others added 4 commits February 16, 2026 13:29
The getPaymentOptions API now returns collectData on each option rather
than only at the top level. This updates the payment flow to:

- Show the payment options list immediately after scanning a QR
- Display an "Info required" pill on options that need collectData
- Change the pay button to "Next" when the selected option needs data
- Load the per-option collectData URL in WebView on "Next" tap
- Show only the completed option after returning from the WebView
- Add "Why info required?" explainer accessible from the header
- Simplify the options list to a flat layout (remove collapsible select)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings February 16, 2026 15:34
@vercel
Copy link

vercel bot commented Feb 16, 2026

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

Project Deployment Actions Updated (UTC)
react-native-examples Ready Ready Preview, Comment Feb 16, 2026 3:35pm

Request Review

@claude
Copy link

claude bot commented Feb 16, 2026

Claude finished @ganchoradkov's task in 2m 28s —— View job


Code Review Complete

PR Size: 11 files, 274 additions, 277 deletions ✅ Within acceptable range

Found 4 issue(s)

Issue 1: WebView originWhitelist doesn't validate final URL matches allowed origins

ID: collectdata-origin-bypass-a8c4
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:212
Severity: HIGH
Category: security

Context:

  • Pattern: originWhitelist at line 212 restricts domains, but finalUrl (built at line 86 from untrusted url prop) is never validated against this whitelist before loading
  • Risk: If url prop contains attacker-controlled domain (from compromised API or MITM), WebView loads arbitrary site despite whitelist
  • Impact: Phishing via fake payment form, credential theft, JavaScript injection attacks
  • Trigger: Malicious or compromised getPaymentOptions API response with collectData.url pointing to evil.com

Recommendation: Validate URL origin before constructing finalUrl:

const finalUrl = useMemo(() => {
  const allowedOrigins = ['https://dev.pay.walletconnect.com', 'https://staging.pay.walletconnect.com', 'https://pay.walletconnect.com'];
  const urlOrigin = getBaseUrl(url);
  if (!allowedOrigins.includes(urlOrigin)) {
    LogStore.error('Invalid collectData URL origin', 'CollectDataWebView', 'finalUrl', { url });
    onError('Invalid payment form URL');
    return '';
  }
  return buildUrlWithPrefill(url, themeMode);
}, [url, themeMode, onError]);

Issue 2: Hardcoded PII in WebView prefill data

ID: collectdata-hardcoded-pii-7f2e
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:40-43
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: PREFILL_DATA at lines 40-43 hardcodes fullName: 'John Doe' and dob: '1990-06-15' sent to external WebView
  • Risk: Example wallet shipping with placeholder PII may confuse users or leak test data in production
  • Impact: Poor UX if users see pre-filled fake data; potential privacy issue if logged by payment provider
  • Trigger: Any payment requiring collectData

Recommendation: For example app, either remove prefill or make it configurable:

// Option 1: Remove prefill for production
const PREFILL_DATA = __DEV__ ? { fullName: 'John Doe', dob: '1990-06-15' } : {};

// Option 2: Make it a debug setting
const PREFILL_DATA = SettingsStore.state.enablePrefillDebug 
  ? { fullName: 'John Doe', dob: '1990-06-15' }
  : {};

Issue 3: Missing error boundary for InfoExplainerView render

ID: index-explainer-error-boundary-3b8f
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/index.tsx:149-151
Severity: LOW
Category: code_quality

Context:

  • Pattern: showInfoExplainer state at line 26 conditionally renders InfoExplainerView without error boundary
  • Risk: If InfoExplainerView throws (rare), entire modal crashes instead of graceful fallback
  • Impact: Modal becomes unresponsive, user must restart app
  • Trigger: Unexpected error in InfoExplainerView (e.g., theme value missing)

Recommendation: Wrap in React error boundary or add defensive null check in InfoExplainerView. For example app, low priority but good practice:

{showInfoExplainer && (
  <ErrorBoundary fallback={<View />}>
    <InfoExplainerView onDismiss={() => setShowInfoExplainer(false)} />
  </ErrorBoundary>
)}

Issue 4: Non-strict equality check could allow falsy bypass

ID: index-needs-collect-logic-9c2a
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/index.tsx:142-146
Severity: LOW
Category: code_quality

Context:

  • Pattern: selectedNeedsCollectData at lines 142-146 uses !! coercion but doesn't validate URL is non-empty string
  • Risk: If API returns collectData: { url: '' } (empty string), !!'' is falsy but URL is technically present
  • Impact: Empty URL would skip collectData flow, potentially breaking payment if server expects it
  • Trigger: Malformed API response with empty string collectData.url

Recommendation: Add explicit string length check:

const selectedNeedsCollectData = !!(
  snap.selectedOption &&
  (snap.selectedOption as PaymentOptionWithCollectData).collectData?.url?.trim() &&
  !snap.collectDataCompletedIds.includes(snap.selectedOption.id)
);

Summary

Reviewed per-option collectData refactor. Main concern: WebView security - originWhitelist doesn't validate url prop before loading (Issue 1). Also flagged hardcoded PII in prefill (Issue 2) and minor logic improvements (Issues 3-4).

Critical Action Required: Validate collectData URL origin against whitelist before constructing finalUrl to prevent phishing attacks.

Positive Notes:

  • Clean React patterns with proper hooks usage
  • Comprehensive logging throughout
  • Good TypeScript typing with PaymentOptionWithCollectData extension
  • collectData completion tracking correctly prevents duplicates (PaymentStore.ts:146)
  • State management via Valtio is consistent with existing patterns
  • InfoExplainerView is simple and effective

);

const renderContent = useCallback(() => {
if (showInfoExplainer) {

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Missing error boundary for InfoExplainerView render

Severity: LOW
Category: code_quality
Tool: Claude Auto Review

Context:

  • Pattern: showInfoExplainer state at line 26 conditionally renders InfoExplainerView without error boundary
  • Risk: If InfoExplainerView throws (rare), entire modal crashes instead of graceful fallback
  • Impact: Modal becomes unresponsive, user must restart app
  • Trigger: Unexpected error in InfoExplainerView (e.g., theme value missing)

Recommendation: Wrap in React error boundary or add defensive null check in InfoExplainerView. For example app, low priority but good practice:

{showInfoExplainer && (
  <ErrorBoundary fallback={<View />}>
    <InfoExplainerView onDismiss={() => setShowInfoExplainer(false)} />
  </ErrorBoundary>
)}

}
}, [snap.step, snap.paymentOptions?.options, snap.selectedOption, onSelectOption]);

const selectedNeedsCollectData = !!(

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Non-strict equality check could allow falsy bypass

Severity: LOW
Category: code_quality
Tool: Claude Auto Review

Context:

  • Pattern: selectedNeedsCollectData at lines 142-146 uses !! coercion but doesn't validate URL is non-empty string
  • Risk: If API returns collectData: { url: '' } (empty string), !!'' is falsy but URL is technically present
  • Impact: Empty URL would skip collectData flow, potentially breaking payment if server expects it
  • Trigger: Malformed API response with empty string collectData.url

Recommendation: Add explicit string length check:

const selectedNeedsCollectData = !!(
  snap.selectedOption &&
  (snap.selectedOption as PaymentOptionWithCollectData).collectData?.url?.trim() &&
  !snap.collectDataCompletedIds.includes(snap.selectedOption.id)
);

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a per-option collectData flow for WalletConnect Pay, improving the user experience by showing payment options immediately after scanning and handling data collection on a per-option basis rather than globally.

Changes:

  • Added per-option collectData tracking with collectDataCompletedIds state in PaymentStore
  • Replaced collapsible "Pay with" dropdown with a flat list of payment options showing "Info required" pills
  • Added an info explainer modal accessible via a "Why info required?" button

Reviewed changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
wallets/rn_cli_wallet/yarn.lock Updated @walletconnect/pay and @walletconnect/react-native-compat to canary versions with per-option collectData support
wallets/rn_cli_wallet/package.json Added @walletconnect/pay canary version to resolutions
wallets/rn_cli_wallet/ios/Podfile.lock Updated iOS dependencies to match new package versions
wallets/rn_cli_wallet/src/utils/TypesUtil.ts Added PaymentOptionWithCollectData interface extending PaymentOption with optional collectData field
wallets/rn_cli_wallet/src/store/PaymentStore.ts Added collectDataCompletedIds tracking and methods to mark/check completion status
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/index.tsx Refactored flow to support per-option collectData with info explainer modal
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ViewWrapper.tsx Added headerLeftContent prop for custom left header content and bottom safe area padding
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ConfirmPaymentView.tsx Simplified UI by removing collapsible dropdown and showing flat list with "Info required" pills
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/InfoExplainerView.tsx New component explaining why user information is required
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ResultView.tsx Added bottom margin to footer for proper spacing
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx Added viewport JavaScript injection to fit form content without scrolling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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