Skip to main content

Payment Flow

Understanding the payment flow helps you build better payment experiences and handle edge cases.

Payment Flow Diagram

Step-by-Step Breakdown

1. Payment Initiation

User clicks "Pay" button or calls pay():

const { pay } = useVolrPay();

pay({
amount: '10.00',
currency: 'USDC',
onSuccess: () => {},
onError: () => {},
});

2. Authentication Check

PaymentModal checks if user is authenticated:

  • Not authenticated: Shows SigninModal automatically
  • Authenticated: Continues to payment flow

3. Wallet Setup

If user doesn't have a wallet:

  • Create Volr wallet: User enrolls passkey and creates wallet
  • Connect external wallet: User connects MetaMask or other wallet

4. Token Selection

User selects which token to pay with:

  • Available tokens are configured in Dashboard
  • Shows token balances
  • User can switch tokens

5. Balance Check

PaymentModal checks if user has sufficient balance:

// Insufficient balance
if (balance < amount) {
// Show deposit option
// Opens DepositModal
}

// Sufficient balance
if (balance >= amount) {
// Continue to payment confirmation
}

6. Payment Confirmation

User reviews payment details:

  • Amount
  • Token
  • Item details (if provided)
  • Gas fee (if applicable)

7. Transaction Processing

Payment is processed:

  1. Create payment intent: Backend creates payment record
  2. Sign transaction: User signs transaction with wallet
  3. Submit transaction: Transaction is submitted to blockchain
  4. Wait for confirmation: Wait for transaction confirmation

8. Result

Payment completes:

  • Success: Shows success message, calls onSuccess
  • Error: Shows error message, calls onError

State Management

PaymentModal manages these states internally:

type PaymentStep = 
| 'info' // Payment info and token selection
| 'wallet' // Wallet choice or setup
| 'processing' // Transaction processing
| 'result'; // Success or error

type WalletStep =
| 'choice' // Choose wallet type
| 'create-account' // Create Volr wallet
| 'external-wallet'; // Connect external wallet

Handling Edge Cases

Insufficient Balance

When user has insufficient balance:

// PaymentModal automatically shows deposit option
// User can:
// 1. Deposit funds via DepositModal
// 2. Cancel payment
// 3. Switch to different token (if available)

User Cancels

Handle user cancellation:

pay({
amount: '10.00',
currency: 'USDC',
onCancel: () => {
console.log('User cancelled payment');
// No action needed, modal closes automatically
},
});

Network Errors

Handle network errors:

pay({
amount: '10.00',
currency: 'USDC',
onError: (error) => {
if (error.message.includes('network')) {
alert('Network error. Please check your connection.');
}
},
});

Transaction Timeout

If transaction takes too long:

// PaymentModal shows processing state
// User can:
// 1. Wait for confirmation
// 2. Check transaction on block explorer (via transaction hash)
// 3. Retry if transaction fails

Best Practices

1. Show Loading States

Always show loading states during payment:

const [isPaying, setIsPaying] = useState(false);

const handlePay = () => {
setIsPaying(true);
pay({
amount: '10.00',
currency: 'USDC',
onSuccess: () => {
setIsPaying(false);
// Handle success
},
onError: () => {
setIsPaying(false);
// Handle error
},
});
};

return (
<button onClick={handlePay} disabled={isPaying}>
{isPaying ? 'Processing...' : 'Pay'}
</button>
);

2. Provide Clear Feedback

Give users clear feedback at each step:

pay({
amount: '10.00',
currency: 'USDC',
onSuccess: () => {
// Show success message
toast.success('Payment successful!');
// Redirect or update UI
},
onError: (error) => {
// Show user-friendly error
toast.error('Payment failed. Please try again.');
},
});

3. Handle Authentication

Ensure user is authenticated before payment:

import { useVolr } from '@volr/react';

function PaymentButton() {
const { isLoggedIn } = useVolr();
const { pay } = useVolrPay();

if (!isLoggedIn) {
return <div>Please sign in to make a payment</div>;
}

return <button onClick={() => pay({...})}>Pay</button>;
}

Next Steps