App/lib/widgets/payment/README.md
2025-06-22 17:55:35 +08:00

7.1 KiB

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",
  "enterPinToConfirm": "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.primary for primary elements
  • Theme.of(context).colorScheme.surface for backgrounds
  • Theme.of(context).textTheme for typography

Security Considerations

  1. PIN Handling: The PIN is passed as a string to your callback. Ensure you handle it securely and don't log it.
  2. Biometric Authentication: Always verify biometric authentication on your backend.
  3. Network Security: Use HTTPS for all payment-related API calls.
  4. 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