Skip to content

refactor(payments): unify SplitDetailPage with shared checkout logic#545

Merged
OlufunbiIK merged 1 commit into
OlufunbiIK:mainfrom
Nanfe01:check
Apr 29, 2026
Merged

refactor(payments): unify SplitDetailPage with shared checkout logic#545
OlufunbiIK merged 1 commit into
OlufunbiIK:mainfrom
Nanfe01:check

Conversation

@Nanfe01
Copy link
Copy Markdown
Contributor

@Nanfe01 Nanfe01 commented Apr 28, 2026

refactor(payments): unify SplitDetailPage with shared checkout logic

  • replace bespoke wallet/network logic with usePaymentCheckout hook
  • centralize submit, error, and loading states
  • prevent duplicate payment submissions
  • align SplitDetailPage with shared payment contract
  • update PaymentButton usage to delegate control flow

closes #464

Summary by CodeRabbit

  • New Features
    • Added payment checkout functionality with comprehensive wallet connectivity and network status validation to ensure transactions only proceed with correct setup
    • Implemented submission state management with real-time loading indicators and detailed error messaging for improved user feedback
    • Integrated duplicate submission prevention to ensure secure, reliable payment processing without concurrent transaction attempts

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

@Nanfe01 is attempting to deploy a commit to the olufunbiik's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

A new React hook usePaymentCheckout is introduced to manage payment submission state and logic. The hook validates wallet connection and network status, prevents concurrent submissions, executes payment requests, and provides loading and error state tracking.

Changes

Cohort / File(s) Summary
Payment Checkout Hook
frontend/src/components/usePaymentCheckout.ts
New hook implementing payment submission logic with duplicate prevention, wallet/network validation, async request handling, and error state management returning submitPayment, loading, error, isWalletConnected, and isCorrectNetwork.

Sequence Diagram

sequenceDiagram
    participant Caller as Component
    participant Hook as usePaymentCheckout
    participant Wallet as Wallet Validator
    participant Network as Network Validator
    participant API as Payment API
    
    Caller->>Hook: submitPayment(params)
    Hook->>Hook: Check if loading or duplicate
    alt Already Processing or Duplicate
        Hook->>Caller: Return early
    else Proceed
        Hook->>Wallet: isWalletConnected()
        Wallet-->>Hook: Connected status
        Hook->>Network: isCorrectNetwork()
        Network-->>Hook: Network status
        alt Validation Failed
            Hook->>Hook: Set error state
            Hook->>Caller: Return
        else Validation Passed
            Hook->>Hook: Set loading=true
            Hook->>API: Execute payment request
            API-->>Hook: Success/Response
            Hook->>Hook: Log success, update lastSubmittedId
            Hook->>Caller: Return with data
        end
        Hook->>Hook: Clear loading state (finally)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 A hook hops in, so spry and neat,
With payments flowing, never repeat!
Wallets checked, networks aligned,
Error-safe, well-designed!
One more bounce 'cross the checkout beat!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing a new shared usePaymentCheckout hook to unify payment logic across components, specifically mentioned in the PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/usePaymentCheckout.ts`:
- Around line 3-6: The hook in frontend/src/components/usePaymentCheckout.ts
currently hardcodes network validity, ignores the CheckoutParams.amount, and
returns a simulated success; replace the stubbed logic by delegating to the
shared payment flow: call the shared hook/function from
frontend/src/hooks/usePaymentCheckout.ts (or the wallet submit/transaction
helpers in frontend/src/utils/stellar/wallet.ts) to validate network, build and
submit the on‑chain payment using the provided amount and splitId, and propagate
real success/error responses instead of the simulated 200 response; ensure you
remove the hardcoded network check and the fake success branch and forward any
errors from the wallet helper back to callers.
- Around line 26-27: Replace the asynchronous state-based duplicate-submit guard
in usePaymentCheckout with a synchronous ref-based in-flight guard: introduce a
useRef (e.g., inFlightRef) and check/flip it immediately at the top of the
submit handler (instead of relying on loading or lastSubmittedId) to prevent
race conditions; continue to update lastSubmittedId (and loading state) for UI,
but ensure you set lastSubmittedId only after a successful submit, and always
clear inFlightRef and reset loading inside the catch/finally path so failed
attempts can be retried (adjust references to splitId, setLastSubmittedId,
setLoading, and existing try/catch blocks accordingly).
- Around line 13-16: The hook currently reads window?.ethereum.selectedAddress
directly in isWalletConnected and computes snapshot values at initialization,
causing SSR errors and stale state; make isWalletConnected and isCorrectNetwork
SSR-safe by guarding with typeof window !== 'undefined' before accessing
window.ethereum and add a proper Window/Ethereum type augmentation for the
injected provider; instead of returning eager boolean snapshots (the current
return object calling isWalletConnected() and isCorrectNetwork()), either return
the functions themselves or implement useState + useEffect to subscribe to
provider events (e.g., accountsChanged, chainChanged) so the hook updates
reactively (update function names isWalletConnected, isCorrectNetwork and any
return shape accordingly).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70ebb16d-b325-4e68-b91c-0e5581bcecba

📥 Commits

Reviewing files that changed from the base of the PR and between b356a85 and 66f4ff1.

📒 Files selected for processing (1)
  • frontend/src/components/usePaymentCheckout.ts

Comment on lines +3 to +6
type CheckoutParams = {
amount: number;
splitId: string;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

This hook currently “succeeds” without submitting a real payment transaction.

Line 20 hardcodes network validity, Lines 46-49 simulate success, and amount is unused (Line 24). This diverges from the shared payment contract and can mark payments as successful without on-chain/API submission. Please wire this to the existing checkout/wallet flow (frontend/src/hooks/usePaymentCheckout.ts:1-77 and frontend/src/utils/stellar/wallet.ts:49-119) before release.

💡 Minimal alignment option (delegate to shared hook)
-import { useState } from "react";
-
-type CheckoutParams = {
-  amount: number;
-  splitId: string;
-};
-
-export const usePaymentCheckout = () => {
-  ...
-};
+export { usePaymentCheckout } from "../hooks/usePaymentCheckout";

Also applies to: 18-21, 45-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/usePaymentCheckout.ts` around lines 3 - 6, The hook
in frontend/src/components/usePaymentCheckout.ts currently hardcodes network
validity, ignores the CheckoutParams.amount, and returns a simulated success;
replace the stubbed logic by delegating to the shared payment flow: call the
shared hook/function from frontend/src/hooks/usePaymentCheckout.ts (or the
wallet submit/transaction helpers in frontend/src/utils/stellar/wallet.ts) to
validate network, build and submit the on‑chain payment using the provided
amount and splitId, and propagate real success/error responses instead of the
simulated 200 response; ensure you remove the hardcoded network check and the
fake success branch and forward any errors from the wallet helper back to
callers.

Comment on lines +13 to +16
const isWalletConnected = () => {
// replace with real wallet hook
return Boolean(window?.ethereum?.selectedAddress);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check whether Window.ethereum is typed anywhere
rg -n --type=ts --type=tsx 'interface\s+Window|declare\s+global|ethereum' -g '!**/node_modules/**'

# 2) Locate direct window.ethereum access patterns
rg -n --type=ts --type=tsx '\bwindow(\?|\.)' -g '!**/node_modules/**'

# 3) Confirm which usePaymentCheckout implementation is imported by consumers
rg -n --type=ts --type=tsx 'from\s+["'\''][^"'\'']*usePaymentCheckout["'\'']'

Repository: OlufunbiIK/StellarSplit

Length of output: 95


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List files in frontend/src/components to understand structure
ls -la frontend/src/components/ 2>/dev/null | head -20

# Check if the file exists
find . -name "usePaymentCheckout.ts" -type f

Repository: OlufunbiIK/StellarSplit

Length of output: 1352


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for Window interface/ethereum typing (adjusted for ts files only)
rg -n 'interface\s+Window|declare\s+global|ethereum' --type ts -g '!**/node_modules/**' 2>/dev/null || echo "No matches found"

# Search for window access patterns
rg -n 'window\s*[\?\.]' --type ts -g '!**/node_modules/**' 2>/dev/null | head -30 || echo "No matches found"

# Search for usePaymentCheckout imports
rg -n 'usePaymentCheckout' --type ts -g '!**/node_modules/**' 2>/dev/null || echo "No matches found"

Repository: OlufunbiIK/StellarSplit

Length of output: 3648


🏁 Script executed:

cat frontend/src/components/usePaymentCheckout.ts

Repository: OlufunbiIK/StellarSplit

Length of output: 1540


window?.ethereum usage creates SSR risk and requires explicit typing.

Line 15 can fail in server-side runtime environments, and window.ethereum is not a standard Web API—it's injected by wallet providers and needs explicit Window augmentation for TypeScript.

Also, lines 61-62 return eager snapshots of wallet state (calling isWalletConnected() and isCorrectNetwork() immediately in the return object). These values are computed once at hook initialization and won't update if the wallet connection or network changes. Return the functions themselves or use state/effects to track reactive wallet state:

SSR-safe pattern
  const isWalletConnected = () => {
+   if (typeof window === "undefined") return false;
-   return Boolean(window?.ethereum?.selectedAddress);
+   const eth = (window as Window & { ethereum?: { selectedAddress?: string } }).ethereum;
+   return Boolean(eth?.selectedAddress);
  };

Return functions in hook output:

  return {
    submitPayment,
    loading,
    error,
-   isWalletConnected: isWalletConnected(),
-   isCorrectNetwork: isCorrectNetwork(),
+   isWalletConnected,
+   isCorrectNetwork,
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/usePaymentCheckout.ts` around lines 13 - 16, The hook
currently reads window?.ethereum.selectedAddress directly in isWalletConnected
and computes snapshot values at initialization, causing SSR errors and stale
state; make isWalletConnected and isCorrectNetwork SSR-safe by guarding with
typeof window !== 'undefined' before accessing window.ethereum and add a proper
Window/Ethereum type augmentation for the injected provider; instead of
returning eager boolean snapshots (the current return object calling
isWalletConnected() and isCorrectNetwork()), either return the functions
themselves or implement useState + useEffect to subscribe to provider events
(e.g., accountsChanged, chainChanged) so the hook updates reactively (update
function names isWalletConnected, isCorrectNetwork and any return shape
accordingly).

Comment on lines +26 to +27
// 🚫 prevent duplicate submits
if (loading || lastSubmittedId === splitId) return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n frontend/src/components/usePaymentCheckout.ts

Repository: OlufunbiIK/StellarSplit

Length of output: 1988


Race condition in duplicate-submit protection allows double-submits.

The guard at line 27 checks state values, but React state updates are asynchronous. Two rapid calls can both pass the check before the first call's state updates are committed, causing both to proceed to the try block and submit duplicate payments.

Additionally, lastSubmittedId is set at line 43 but never reset on error (line 50-51), blocking retries of failed payments.

Solution: Use useRef for an in-flight flag to make the guard atomic with immediate synchronous state:

Fix with useRef
-import { useState } from "react";
+import { useRef, useState } from "react";
...
+  const inFlightRef = useRef(false);
...
-    if (loading || lastSubmittedId === splitId) return;
+    if (inFlightRef.current) return;
...
     try {
+      inFlightRef.current = true;
       setLoading(true);
       setLastSubmittedId(splitId);
...
     } catch (e: any) {
       setError(e.message || "Payment failed");
+      setLastSubmittedId(null);
     } finally {
+      inFlightRef.current = false;
       setLoading(false);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/usePaymentCheckout.ts` around lines 26 - 27, Replace
the asynchronous state-based duplicate-submit guard in usePaymentCheckout with a
synchronous ref-based in-flight guard: introduce a useRef (e.g., inFlightRef)
and check/flip it immediately at the top of the submit handler (instead of
relying on loading or lastSubmittedId) to prevent race conditions; continue to
update lastSubmittedId (and loading state) for UI, but ensure you set
lastSubmittedId only after a successful submit, and always clear inFlightRef and
reset loading inside the catch/finally path so failed attempts can be retried
(adjust references to splitId, setLastSubmittedId, setLoading, and existing
try/catch blocks accordingly).

@OlufunbiIK OlufunbiIK merged commit 31ba576 into OlufunbiIK:main Apr 29, 2026
4 of 6 checks passed
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.

Split Detail Payment Checkout Hook Adoption

2 participants