Skip to content

feat(pay): add WebView for hosted collect data form#367

Open
ganchoradkov wants to merge 11 commits intomainfrom
feat/pay-webview-collect-data
Open

feat(pay): add WebView for hosted collect data form#367
ganchoradkov wants to merge 11 commits intomainfrom
feat/pay-webview-collect-data

Conversation

@ganchoradkov
Copy link
Member

Summary

  • Add CollectDataWebView component that displays WalletConnect Pay's hosted form in a fullscreen WebView
  • When collectData.url is present in payment options, use WebView instead of native form
  • Handle IC_COMPLETE / IC_ERROR postMessage events from the WebView
  • Improved UX: X button in WebView navigates to confirm step (not closes entire modal)

Changes

flowchart TD
    A[Payment Link Received] --> B[Show Intro]
    B --> C{collectData.url exists?}
    C -->|Yes| D[Open WebView - Fullscreen]
    C -->|No| E[Show Native Form]
    D --> F{IC_COMPLETE message?}
    F -->|Yes| G[Move to Confirm Step]
    F -->|No - X pressed| G
    E --> G
    G --> H[Select Payment Option]
    H --> I[Sign & Confirm Payment]
Loading

Test Plan

  • Scan a WalletConnect Pay link that requires collect data
  • Verify WebView opens fullscreen after clicking "Let's start"
  • Complete the form in WebView
  • Verify automatic navigation to confirm step after form completion
  • Verify X button navigates to confirm step (not closes modal)
  • Test error handling when WebView fails to load

- Add CollectDataWebView component for WalletConnect Pay hosted form
- Use fullscreen WebView when collectData.url is available
- Handle IC_COMPLETE/IC_ERROR postMessage events from WebView
- X button in WebView navigates to confirm step (not closes modal)
- Add proper cleanup with isMountedRef to prevent memory leaks
- Add react-native-webview dependency
@vercel
Copy link

vercel bot commented Jan 30, 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 6, 2026 7:24pm

Request Review

@claude
Copy link

claude bot commented Jan 30, 2026

Claude finished @ganchoradkov's task in 1m 7s —— View job


Code Review

Found 3 issue(s)

Issue 1: WebView security - missing origin whitelist

ID: collectdatawebview-origin-whitelist-a4f2
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:126-140
Severity: HIGH
Category: security

Context:

  • Pattern: WebView accepts any URL without origin validation or whitelist
  • Risk: Malicious URL in collectData.url could load arbitrary external content
  • Impact: Phishing attacks, credential theft, or malicious script execution in payment flow
  • Trigger: If API response is compromised or MITM attack modifies collectData.url

Recommendation: Add origin whitelist to restrict allowed domains:

<WebView
  ref={webViewRef}
  source={{ uri: url }}
  originWhitelist={['https://pay.walletconnect.com', 'https://pay.reown.com']}
  // ... rest of props
/>

Issue 2: postMessage validation insufficient

ID: collectdatawebview-message-validation-7bc3
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:77-109
Severity: MEDIUM
Category: security

Context:

  • Pattern: handleMessage only validates message structure, not origin
  • Risk: Any script in WebView can send IC_COMPLETE/IC_ERROR messages
  • Impact: Malicious scripts could bypass form completion, trigger false errors
  • Trigger: XSS in hosted form or compromised CDN serving form assets

Recommendation: Validate message origin before processing:

const handleMessage = useCallback((event: { nativeEvent: { data: string; url: string } }) => {
  if (!isMountedRef.current) return;
  
  const messageOrigin = new URL(event.nativeEvent.url).origin;
  const allowedOrigins = ['https://pay.walletconnect.com', 'https://pay.reown.com'];
  if (!allowedOrigins.includes(messageOrigin)) {
    LogStore.warn('Blocked postMessage from untrusted origin', 'CollectDataWebView', 'handleMessage', { origin: messageOrigin });
    return;
  }
  
  // ... rest of handler
}, [onComplete, onError]);

Issue 3: Type safety - unsafe type casting

ID: index-collectdata-type-cast-9e1a
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/index.tsx:45-47
Severity: LOW
Category: code_quality

Context:

  • Pattern: Type cast to access undocumented url property: (paymentData?.collectData as { url?: string } | undefined)?.url
  • Risk: Runtime errors if API shape changes, no compile-time safety
  • Impact: Silent failures, hard-to-debug issues when property added to types
  • Trigger: When @walletconnect/pay package adds url to CollectData type

Recommendation: Extend type properly or add runtime check:

// Option 1: Type extension (preferred)
interface CollectDataWithUrl extends CollectData {
  url?: string;
}
const collectDataUrl = (paymentData?.collectData as CollectDataWithUrl | undefined)?.url;

// Option 2: Add comment explaining temporary nature
const collectDataUrl = (
  // TEMP: url not yet in @walletconnect/pay types v1.x
  paymentData?.collectData as { url?: string } | undefined
)?.url;

Summary

Functional implementation with good error handling and UX flow. Critical security concern: WebView lacks origin restrictions allowing arbitrary URLs. Add originWhitelist and message origin validation before merging.

const hasCollectData =
paymentData?.collectData && paymentData.collectData.fields.length > 0;
// The `url` property is a new addition to the API - cast to access it
const collectDataUrl = (

Choose a reason for hiding this comment

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

🤖 Auto Review Issue: Type safety - unsafe type casting

Severity: LOW
Category: code_quality
Tool: Claude Auto Review

Context:

  • Pattern: Type cast to access undocumented url property: (paymentData?.collectData as { url?: string } | undefined)?.url
  • Risk: Runtime errors if API shape changes, no compile-time safety
  • Impact: Silent failures, hard-to-debug issues when property added to types
  • Trigger: When @walletconnect/pay package adds url to CollectData type

Recommendation: Extend type properly or add runtime check:

// Option 1: Type extension (preferred)
interface CollectDataWithUrl extends CollectData {
  url?: string;
}
const collectDataUrl = (paymentData?.collectData as CollectDataWithUrl | undefined)?.url;

// Option 2: Add comment explaining temporary nature
const collectDataUrl = (
  // TEMP: url not yet in @walletconnect/pay types v1.x
  paymentData?.collectData as { url?: string } | undefined
)?.url;

Summary

Functional implementation with good error handling and UX flow. Critical security concern: WebView lacks origin restrictions allowing arbitrary URLs. Add originWhitelist and message origin validation before merging.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Addressed. Added a TODO comment to update the type when @walletconnect/pay adds url to the CollectData type. The current cast is temporary until the SDK types are updated.

- Add getCurrencySymbol() utility to convert currency codes to symbols
- Support 35+ currencies (USD, EUR, GBP, JPY, etc.)
- Add prefill parameter to WebView URL with base64-encoded user data
- Skip collectedData validation when data was collected via WebView
- Use dynamic currency symbols in MerchantInfo and ConfirmPaymentView

Co-authored-by: Cursor <cursoragent@cursor.com>
@ignaciosantise
Copy link
Collaborator

@ganchoradkov can you also check how it behaves if you press the terms and conditions link from the webview checkbox? I think we could make the app open the native browser in that case so we dont break the flow

…10.30

Add git source to Podfile to resolve CDN propagation delay for new pod versions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ignaciosantise
Copy link
Collaborator

@ganchoradkov also check how the soft keyboard behaves when the user presses the inputs inside the webview 🙏

Updates YttriumWrapper to 0.10.31.

Co-authored-by: Cursor <cursoragent@cursor.com>
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 adds WebView support for WalletConnect Pay's hosted data collection forms, providing a more seamless UX when the payment API returns a collectData.url. The implementation allows users to complete payment data collection in a fullscreen WebView instead of native forms.

Changes:

  • Adds CollectDataWebView component that renders hosted forms in a fullscreen WebView with postMessage event handling
  • Introduces new collectDataWebView step in payment flow when collectData.url is available
  • Updates currency display formatting with getCurrencySymbol utility function for internationalization
  • Upgrades @walletconnect/react-native-compat package to version 2.23.4-canary.2

Reviewed changes

Copilot reviewed 10 out of 12 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds react-native-webview dependency and updates @walletconnect/react-native-compat to 2.23.4-canary.2
yarn.lock Updates lock file with new dependency versions and checksums
ios/Podfile Adds CocoaPods Specs source repository
ios/Podfile.lock Updates iOS native dependencies including react-native-webview and YttriumWrapper
utils.ts Adds getCurrencySymbol utility function with mapping for 40+ currency codes
reducer.ts Adds 'collectDataWebView' step to payment flow state machine
index.tsx Integrates WebView flow with conditional rendering based on collectData.url presence, adds completion/error handlers
ViewWrapper.tsx Updates UI to support fullscreen WebView step with modified header button behavior
MerchantInfo.tsx Uses getCurrencySymbol for dynamic currency symbol display instead of hardcoded $
ConfirmPaymentView.tsx Uses getCurrencySymbol for dynamic currency symbol display
CollectDataWebView.tsx New component implementing WebView with prefill data, postMessage handling, and URL-based navigation detection
usePairing.ts Adds console.log for debugging payment options response

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

Comment on lines +105 to +106
// Closing WebView means form was completed, move to confirm step
dispatch({ type: 'SET_STEP', payload: 'confirm' });
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

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

The comment states "Closing WebView means form was completed, move to confirm step", but clicking the back button (X) in the WebView should typically allow the user to go back to the previous step, not assume completion. According to the PR description, "X button in WebView navigates to confirm step (not closes entire modal)" which suggests this is intentional. However, this conflates two different user actions: (1) successfully completing the form (IC_COMPLETE postMessage) and (2) wanting to go back/cancel (pressing X). Consider navigating back to 'intro' step when X is pressed instead of assuming the form was completed, and only proceed to 'confirm' when IC_COMPLETE message is received.

Suggested change
// Closing WebView means form was completed, move to confirm step
dispatch({ type: 'SET_STEP', payload: 'confirm' });
// Closing WebView (X/back) should behave like cancel/back: return to intro
dispatch({ type: 'SET_STEP', payload: 'intro' });

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is by design. The X/back button on the WebView navigates to the confirm step so the user can continue the payment flow without rescanning the payment link. The IC_COMPLETE postMessage also transitions to confirm. Going back to intro would force the user to redo the entire flow.

Prevents navigation issues by opening links from different domains
in the system browser while keeping same-domain navigation in WebView.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ignaciosantise
Copy link
Collaborator

ignaciosantise commented Feb 4, 2026

Some findings while testing on iOS:

  1. Webview should be rendered inside a safe area so it doesn't use status bar space. Also, we should use the walletconnect loader + our text component that uses the custom font
  1. Remove paddings from the modal container in this view so we remove those dark spaces
  1. Privacy and Terms links should open the phones browser so we dont break the flow
IMG_4625
  1. If I close the webview i can continue with the flow. The expected behavior should be that if the user closes the webview, we close the whole flow.
    When i tested, i pressed close and it redirected to the confirm payment step

  2. We need to remove the native checkbox from the intro view because the checkbox is not inside the webview

…move checkbox

- Wrap WebView in SafeAreaView to prevent status bar overlap
- Replace ActivityIndicator with WalletConnectLoading component
- Remove native checkbox from IntroView (handled in WebView)
- Reduce padding in WebView container for better layout

Co-authored-by: Cursor <cursoragent@cursor.com>
@ganchoradkov
Copy link
Member Author

Addressed the feedback in commit eddd779:

  1. SafeArea for WebView - Wrapped WebView in SafeAreaView with edges={['top']} to prevent status bar overlap. Also replaced ActivityIndicator with WalletConnectLoading component.

  2. Remove paddings from modal container - Updated fullscreenContainer style to use minimal horizontal padding and added reduced margin for WebView header.

  3. Remove native checkbox from intro view - Removed the Checkbox component, termsAccepted state, and terms text from IntroView. The "Let's start" button is now always enabled since terms acceptance is handled in the WebView.

  4. ⏭️ Close WebView = close entire flow - Skipped for now.

SafeAreaView wasn't working correctly with react-native-modal's
statusBarTranslucent. Using explicit paddingTop from insets instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Simplify flow: show intro → WebView when collectData.url exists,
  otherwise go directly to confirm step
- Add origin whitelist for WebView (dev/staging/prod WalletConnect Pay)
- Remove step pills from payment modal
- Replace console.log with LogStore.log in usePairing
- Add comment to CollectDataView noting it's for custom UI implementations
- Fix about:blank URL handling in WebView
- Update back button logic to only show when there's a previous step
- Rely on IC_COMPLETE postMessage for WebView completion detection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolve merge conflicts in package.json and yarn.lock.
Keep @walletconnect/react-native-compat at 2.23.4-canary.2 and
add @zoontek/react-native-navigation-bar from main.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes build error caused by duplicate `import LogStore` in usePairing.ts.
Applies prettier formatting to affected files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants