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:
- Create payment intent: Backend creates payment record
- Sign transaction: User signs transaction with wallet
- Submit transaction: Transaction is submitted to blockchain
- 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
- Payment Modal - Implement modal-based payments
- Configuration - Configure Volr UI provider