Skip to content

refactor(rn_cli_wallet): simplify payment flow with centralized store#384

Open
ignaciosantise wants to merge 4 commits intomainfrom
feat/payment-flow-improvements
Open

refactor(rn_cli_wallet): simplify payment flow with centralized store#384
ignaciosantise wants to merge 4 commits intomainfrom
feat/payment-flow-improvements

Conversation

@ignaciosantise
Copy link
Collaborator

Summary

This refactoring streamlines the WalletConnect Pay payment flow by migrating from local component state (useReducer) to a centralized Valtio store. Removes 3 files (IntroView, CollectDataView, reducer) and reduces PaymentOptionsModal by 63% (573 → 203 lines).

Key Changes

  • New PaymentStore.ts: Centralized payment state with lifecycle management
  • Simplified modal flow: Loading → CollectData (WebView) → Confirm → Confirming → Result
  • WebView theme support: Respects wallet's light/dark theme preference
  • Improved deep link handling: Enhanced logging for debugging payment URIs
  • Cleaner interfaces: ModalStore no longer carries payment-specific data

Architecture

graph TB
    subgraph Before["Before: Local State"]
        direction LR
        OM1["PaymentOptionsModal<br/>(573 lines)"]
        IV["IntroView"]
        CV["CollectDataView"]
        R["reducer.ts"]
        OM1 --> IV & CV & R
    end
    
    subgraph After["After: Centralized Store"]
        direction LR
        OM2["PaymentOptionsModal<br/>(203 lines)"]
        PS["PaymentStore<br/>(Valtio)"]
        OM2 --> PS
    end
    
    Before -->|Refactor| After
Loading

Test Plan

  • Verify payment flow on testnet (Loading → Data Collection → Confirm → Result)
  • Test with both light and dark theme modes
  • Validate WebView data submission
  • Check deep link handling with enhanced logging

Migrate payment state from local useReducer to centralized Valtio PaymentStore,
reducing PaymentOptionsModal from 573 to 203 lines. Remove IntroView,
CollectDataView, and reducer.ts. Add theme support for WebView data collection
and improve deep link debugging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 13, 2026 16:42
@vercel
Copy link

vercel bot commented Feb 13, 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 13, 2026 6:43pm

Request Review

@claude
Copy link

claude bot commented Feb 13, 2026

Claude finished @ignaciosantise's task in 1m 49s —— View job


🔍 Reviewing Payment Flow Refactoring

  • Assess PR size and scope
  • Review new PaymentStore.ts architecture
  • Analyze PaymentOptionsModal refactoring
  • Check deleted components and reducer logic
  • Verify WebView theme implementation
  • Check for payment anti-patterns
  • Review security and error handling
  • Post final review

Summary

Clean refactoring that successfully centralizes payment state management. The migration from useReducer to Valtio store improves maintainability and eliminates 63% of modal code. Found 5 issues requiring attention.

Found 5 issue(s)

Issue 1: Race condition in cleanup - unmounted state update

ID: paymen-unmounted-update-8f4a
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:102-110
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: isMountedRef check at line 89/103/116 but callbacks passed to props can execute after unmount
  • Risk: Parent component can call onError callback after WebView unmounts, causing state update on unmounted component
  • Impact: React warning, potential memory leak if parent holds callback reference
  • Trigger: Modal closes while WebView is processing message/error

Recommendation: Check isMountedRef before calling callbacks:

const handleError = useCallback((syntheticEvent: { nativeEvent: { description: string } }) => {
  if (!isMountedRef.current) return;
  const { description } = syntheticEvent.nativeEvent;
  LogStore.error('WebView error', 'CollectDataWebView', 'handleError', { error: description });
  if (isMountedRef.current) onError(description || 'Failed to load the form');
}, [onError]);

Issue 2: Missing timeout on external payment API calls

ID: paymen-api-timeout-9a2c
File: wallets/rn_cli_wallet/src/store/PaymentStore.ts:186-189
Severity: HIGH
Category: performance

Context:

  • Pattern: payClient.getRequiredPaymentActions and payClient.confirmPayment at lines 186, 309 have no timeout
  • Risk: User stuck on loading screen if external Pay API hangs or network drops
  • Impact: Poor UX, modal becomes unresponsive, requires app restart
  • Trigger: Slow network, API service degradation, or network interruption mid-request

Recommendation: Add timeout with AbortController:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
try {
  const actions = await payClient.getRequiredPaymentActions({
    paymentId: state.paymentOptions.paymentId,
    optionId: option.id,
    signal: controller.signal,
  });
  clearTimeout(timeoutId);
  // ...
} catch (error: any) {
  clearTimeout(timeoutId);
  if (error.name === 'AbortError') {
    // Handle timeout
  }
  // ...
}

Issue 3: Hardcoded test data in production code

ID: collec-prefill-testdata-3f7b
File: wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx:24-28
Severity: MEDIUM
Category: code_quality

Context:

  • Pattern: PREFILL_DATA constant at lines 25-28 contains hardcoded test data sent to production WebView
  • Risk: Test data ('John Doe', '1990-06-15') always injected into KYC/payment forms
  • Impact: Confusing UX (fake data in forms), potential regulatory issues if users accidentally submit test data
  • Trigger: Every payment flow that requires data collection

Recommendation: Make prefill configurable or use actual user data:

// Move to settings/config
const PREFILL_DATA = __DEV__ ? {
  fullName: 'John Doe',
  dob: '1990-06-15',
} : {};

Issue 4: Missing idempotency for payment confirmation

ID: paymen-no-idempotency-key-2c8f
File: wallets/rn_cli_wallet/src/store/PaymentStore.ts:309-313
Severity: HIGH
Category: security

Context:

  • Pattern: payClient.confirmPayment call at line 309 has no idempotency protection
  • Risk: If user taps "Pay" twice quickly or network retries occur, duplicate payment submissions possible
  • Impact: Double charging, incorrect payment state, user complaints
  • Trigger: Impatient user double-tapping, slow network causing retry, or race condition in button disable logic

Recommendation: Disable during confirmation and check step state:

async approvePayment() {
  // Prevent duplicate calls
  if (state.step === 'confirming') {
    LogStore.warn('Payment already in progress', 'PaymentStore', 'approvePayment');
    return;
  }
  
  state.step = 'confirming';
  // ... existing logic
}

And ensure button is disabled in ConfirmPaymentView.tsx:287: disabled={isSigningPayment || isLoadingActions || !selectedOption || snap.step === 'confirming'}

Issue 5: Valtio ref() usage inconsistency

ID: paymen-ref-inconsistent-1d9a
File: wallets/rn_cli_wallet/src/store/PaymentStore.ts:85,92,141,151,196
Severity: LOW
Category: code_quality

Context:

  • Pattern: ref() wrapper used inconsistently - applied to paymentOptions, selectedOption, paymentActions but NOT to arrays in some places
  • Risk: Valtio will proxy objects by default; ref() marks them as non-reactive, preventing unnecessary re-renders
  • Impact: Potential performance issue if large payment data structures cause excessive reactivity
  • Trigger: Payment options with many items or frequent updates

Recommendation: Consistent ref() usage based on mutability needs. If these objects never mutate internally, keep ref(). Document why:

// PaymentStore.ts - add comment
// Use ref() for external API responses that shouldn't be deeply reactive
state.paymentOptions = ref(options);

Additional Notes

✅ Good practices observed:

  • Proper error categorization (expired, insufficient_funds, etc.) at PaymentStore.ts:99-106
  • Comprehensive logging for debugging payment flows
  • Theme mode passed to WebView (line 47-48 in CollectDataWebView.tsx)
  • Structured error handling with typed error states

Minor observations:

  • Line 88 in PaymentOptionsModal/index.tsx: Type cast as { url?: string } | undefined needed due to incomplete @walletconnect/pay types - consider opening upstream PR to add url to CollectData type
  • Android manifest addition (debug/AndroidManifest.xml) enables deep link testing - ensure this doesn't leak to production builds

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

Refactors the RN CLI wallet’s WalletConnect Pay modal flow by moving payment state from component-local useReducer into a centralized Valtio PaymentStore, simplifying the modal’s step orchestration and removing now-redundant view/reducer files.

Changes:

  • Introduce PaymentStore as the single source of truth for payment lifecycle/steps and migrate PaymentOptionsModal + pairing logic to use it.
  • Remove legacy payment modal reducer and intro/native collect-data views; keep WebView-based collect-data and add theme propagation.
  • Misc platform/tooling updates (pinned react-native-webview, Android debug deep links/internal run script, iOS pods lock updates, versionCode bump, doc updates).

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
wallets/rn_cli_wallet/src/store/PaymentStore.ts New centralized Valtio store for payment flow state and business logic.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/index.tsx Payment modal rewritten to drive UI purely from PaymentStore steps/state.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataWebView.tsx Add theme param support and adjust prefill URL construction.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/ViewWrapper.tsx Simplify wrapper step handling; add isWebView prop to control layout.
wallets/rn_cli_wallet/src/hooks/usePairing.ts Initialize payment flow via PaymentStore and open modal without payment data in ModalStore.
wallets/rn_cli_wallet/src/store/ModalStore.ts Remove payment-specific modal data; make open() data optional.
wallets/rn_cli_wallet/src/screens/App.tsx Add additional deep link logging and listener lifecycle logs.
wallets/rn_cli_wallet/src/utils/TypesUtil.ts Add shared Step union type for the payment modal flow.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/reducer.ts Deleted legacy reducer.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/IntroView.tsx Deleted legacy intro step view.
wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/CollectDataView.tsx Deleted legacy native data collection form.
wallets/rn_cli_wallet/package.json Pin react-native-webview and add android:internal script.
wallets/rn_cli_wallet/yarn.lock Lockfile update for pinned react-native-webview.
wallets/rn_cli_wallet/ios/Podfile.lock Pod dependency/metadata updates.
wallets/rn_cli_wallet/android/app/src/debug/AndroidManifest.xml Add debug-only deep link intent filters.
wallets/rn_cli_wallet/android/app/build.gradle Bump versionCode.
wallets/rn_cli_wallet/AGENTS.md Update docs to reference PaymentStore and logging guidance.

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

Prevent duplicate payment submissions by checking if step is already
'confirming' before proceeding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lectDataUrl

Remove dead `dataCollectionSuccess` flag from PaymentStore. Use the
already-computed `collectDataUrl` variable in `goBack` instead of
re-deriving it with a type cast.

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.

1 participant