243 lines
7.1 KiB
Markdown
243 lines
7.1 KiB
Markdown
# 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
|
|
|
|
```dart
|
|
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
|
|
|
|
```dart
|
|
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
|
|
```json
|
|
{
|
|
"pin": "123456"
|
|
}
|
|
```
|
|
|
|
### Biometric Payment
|
|
```json
|
|
{
|
|
"biometric": true
|
|
}
|
|
```
|
|
|
|
### Response
|
|
The API should return the completed `SnWalletOrder` object:
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```dart
|
|
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:
|
|
|
|
```json
|
|
{
|
|
"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 |