Payment Overlay Widget
A reusable payment verification overlay that supports both 6-digit PIN input and biometric authentication for secure payment processing.
Features
- 6-digit PIN Input: Secure numeric PIN entry with automatic focus management
- Biometric Authentication: Support for fingerprint and face recognition
- Order Summary: Display payment details including amount, description, and remarks
- Integrated API Calls: Automatically handles payment processing via /orders/{orderId}/pay
- Error Handling: Comprehensive error handling with user-friendly messages
- Loading States: Visual feedback during payment processing
- Responsive Design: Adapts to different screen sizes and orientations
- Customizable: Flexible callbacks and styling options
- Accessibility: Screen reader support and proper focus management
- Localization: Full i18n support with easy_localization
Usage
import 'package:flutter/material.dart';
import 'package:solian/models/wallet.dart';
import 'package:solian/widgets/payment/payment_overlay.dart';
// Create an order
final order = SnWalletOrder(
  id: 'order_123',
  amount: 2500, // $25.00 in cents
  currency: 'USD',
  description: 'Premium Subscription',
  remarks: 'Monthly billing',
  status: 'pending',
);
// Show payment overlay
PaymentOverlay.show(
  context: context,
  order: order,
  onPaymentSuccess: (completedOrder) {
    // Handle successful payment
    print('Payment completed: ${completedOrder.id}');
    // Navigate to success page or update UI
  },
  onPaymentError: (error) {
    // Handle payment error
    print('Payment failed: $error');
    // Show error message to user
  },
  onCancel: () {
    Navigator.of(context).pop();
    print('Payment cancelled');
  },
  enableBiometric: true,
);
Advanced Usage with Loading States
bool isLoading = false;
PaymentOverlay.show(
  context: context,
  order: order,
  enableBiometric: true,
  isLoading: isLoading,
  onPinSubmit: (String pin) async {
    setState(() => isLoading = true);
    try {
      await processPaymentWithPin(pin);
      Navigator.of(context).pop();
    } catch (e) {
      showErrorDialog(e.toString());
    } finally {
      setState(() => isLoading = false);
    }
  },
  onBiometricAuth: () async {
    setState(() => isLoading = true);
    try {
      final authenticated = await authenticateWithBiometrics();
      if (authenticated) {
        await processPaymentWithBiometrics();
        Navigator.of(context).pop();
      }
    } catch (e) {
      showErrorDialog(e.toString());
    } finally {
      setState(() => isLoading = false);
    }
  },
);
Parameters
PaymentOverlay.show()
| Parameter | Type | Required | Description | 
|---|---|---|---|
| context | BuildContext | ✅ | The build context for showing the overlay | 
| order | SnWalletOrder | ✅ | The order to be paid | 
| onPaymentSuccess | Function(SnWalletOrder)? | ❌ | Callback when payment succeeds with completed order | 
| onPaymentError | Function(String)? | ❌ | Callback when payment fails with error message | 
| onCancel | VoidCallback? | ❌ | Callback when payment is cancelled | 
| enableBiometric | bool | ❌ | Whether to show biometric option (default: true) | 
API Integration
The PaymentOverlay automatically handles payment processing by calling the /orders/{orderId}/pay endpoint with the following request body:
PIN Payment
{
  "pin": "123456"
}
Biometric Payment
{
  "biometric": true
}
Response
The API should return the completed SnWalletOrder object:
{
  "id": "order_123",
  "amount": 2500,
  "currency": "USD",
  "description": "Premium Subscription",
  "status": "completed",
  "processorReference": "txn_abc123",
  // ... other order fields
}
Error Handling
The widget handles common HTTP status codes:
- 401: Invalid PIN or biometric authentication failed
- 400: Bad request with custom error message
- Other errors: Generic payment failed message
Implementation Example
import 'package:local_auth/local_auth.dart';
class BiometricService {
  final LocalAuthentication _auth = LocalAuthentication();
  Future<bool> isAvailable() async {
    final isAvailable = await _auth.canCheckBiometrics;
    final isDeviceSupported = await _auth.isDeviceSupported();
    return isAvailable && isDeviceSupported;
  }
  Future<bool> authenticate() async {
    try {
      final bool didAuthenticate = await _auth.authenticate(
        localizedReason: 'Please authenticate to complete payment',
        options: const AuthenticationOptions(
          biometricOnly: true,
          stickyAuth: true,
        ),
      );
      return didAuthenticate;
    } catch (e) {
      print('Biometric authentication error: $e');
      return false;
    }
  }
}
Localization
Add these keys to your localization files:
{
  "paymentVerification": "Payment Verification",
  "paymentSummary": "Payment Summary",
  "amount": "Amount",
  "description": "Description",
  "pinCode": "PIN Code",
  "biometric": "Biometric",
  "enterPinToConfirmPayment": "Enter your 6-digit PIN to confirm payment",
  "clearPin": "Clear PIN",
  "useBiometricToConfirm": "Use biometric authentication to confirm payment",
  "touchSensorToAuthenticate": "Touch the sensor to authenticate",
  "authenticating": "Authenticating...",
  "authenticateNow": "Authenticate Now",
  "confirm": "Confirm",
  "cancel": "Cancel",
  "paymentFailed": "Payment failed. Please try again.",
  "invalidPin": "Invalid PIN. Please try again.",
  "biometricAuthFailed": "Biometric authentication failed. Please try again.",
  "paymentSuccess": "Payment completed successfully!",
  "paymentError": "Payment failed: {error}"
}
Styling
The widget automatically adapts to your app's theme. It uses:
- Theme.of(context).colorScheme.primaryfor primary elements
- Theme.of(context).colorScheme.surfacefor backgrounds
- Theme.of(context).textThemefor typography
Security Considerations
- PIN Handling: The PIN is passed as a string to your callback. Ensure you handle it securely and don't log it.
- Biometric Authentication: Always verify biometric authentication on your backend.
- Network Security: Use HTTPS for all payment-related API calls.
- Data Validation: Validate all payment data on your backend before processing.
Example Integration
See payment_overlay_example.dart for a complete working example that demonstrates:
- How to show the overlay
- Handling PIN and biometric authentication
- Processing payments
- Error handling
- Loading states
Dependencies
- flutter/material.dart- Material Design components
- flutter/services.dart- Input formatters and system services
- flutter_riverpod/flutter_riverpod.dart- State management and dependency injection
- gap/gap.dart- Spacing widgets
- material_symbols_icons/symbols.dart- Material Symbols icons
- easy_localization/easy_localization.dart- Internationalization
- dio/dio.dart- HTTP client for API calls
- solian/models/wallet.dart- Wallet order model
- solian/widgets/common/sheet_scaffold.dart- Sheet scaffold widget
- solian/pods/network.dart- API client provider