Payment API for Mini-Apps
Overview
Payment API (lib/modular/api/payment.dart) provides a simple interface for mini-apps to process payments without needing access to Riverpod or Flutter widget trees.
Usage
Basic Setup
import 'package:island/modular/api/payment.dart';
// Get singleton instance
final paymentAPI = PaymentAPI.instance;
Creating a Payment Order
final order = await paymentAPI.createOrder(
CreateOrderRequest(
amount: 1000, // $10.00 in cents
currency: 'USD',
remarks: 'Premium subscription',
payeeWalletId: 'wallet_123',
appIdentifier: 'my.miniapp',
),
);
// Use the order ID for payment
final orderId = order.id;
Processing Payment with Overlay
final result = await paymentAPI.processPaymentWithOverlay(
context: context,
createOrderRequest: CreateOrderRequest(
amount: 1000,
currency: 'USD',
remarks: 'Premium subscription',
),
enableBiometric: true,
);
if (result.success) {
print('Payment successful: ${result.order}');
} else {
print('Payment failed: ${result.error}');
}
Processing Existing Payment
final result = await paymentAPI.processPaymentWithOverlay(
context: context,
request: PaymentRequest(
orderId: 'order_123',
amount: 1000,
currency: 'USD',
pinCode: '123456',
enableBiometric: true,
showOverlay: true,
),
);
Processing Payment Without Overlay (Direct)
final result = await paymentAPI.processDirectPayment(
PaymentRequest(
orderId: 'order_123',
amount: 1000,
currency: 'USD',
pinCode: '123456',
enableBiometric: false, // No biometric for direct
),
);
if (result.success) {
// Handle success
} else {
// Handle error
}
API Methods
createOrder(CreateOrderRequest)
Creates a new payment order on the server.
Parameters:
amount(required): Amount in smallest currency unit (cents for USD, etc.)currency(required): Currency code (e.g., 'USD', 'EUR')remarks(optional): Payment descriptionpayeeWalletId(optional): Target wallet IDappIdentifier(optional): Mini-app identifiermeta(optional): Additional metadata
Returns: SnWalletOrder? or throws exception
processPayment({String orderId, String pinCode, bool enableBiometric})
Processes a payment for an existing order. Must be called from within mini-app context.
Parameters:
orderId(required): Order ID to processpinCode(required): 6-digit PIN codeenableBiometric(optional, default: true): Allow biometric authentication
Returns: SnWalletOrder? or throws exception
processPaymentWithOverlay({BuildContext, PaymentRequest?, CreateOrderRequest?, bool enableBiometric})
Shows payment overlay UI and processes payment. Use this for user-facing payments.
Parameters:
context(required): BuildContext for showing overlayrequest(optional): Existing payment request with orderIdcreateOrderRequest(optional): New order request (must provide one)enableBiometric(optional, default: true): Enable biometric authentication
Returns: PaymentResult
processDirectPayment(PaymentRequest)
Processes payment without showing UI overlay. Use for automatic/background payments.
Parameters:
request(required): PaymentRequest with all details including pinCode
Returns: PaymentResult
Data Types
PaymentRequest
const factory PaymentRequest({
required String orderId,
required int amount,
required String currency,
String? remarks,
String? payeeWalletId,
String? pinCode,
@Default(true) bool showOverlay,
@Default(true) bool enableBiometric,
});
CreateOrderRequest
const factory CreateOrderRequest({
required int amount,
required String currency,
String? remarks,
String? payeeWalletId,
String? appIdentifier,
@Default({}) Map<String, dynamic> meta,
});
PaymentResult
const factory PaymentResult({
required bool success,
SnWalletOrder? order,
String? error,
String? errorCode,
});
Error Handling
The API handles common error scenarios:
- 401/403: Invalid PIN code
- 400: Payment error with message
- 404: Order not found
- 503: Service unavailable/maintenance
- Network errors: Connection issues
Internals
The API:
- Uses a singleton pattern (
PaymentAPI.instance) - Manages its own Dio instance with proper interceptors
- Reads server URL and token from SharedPreferences
- Handles authentication automatically
- Reuses existing
PaymentOverlaywidget for UI
Complete Example
import 'package:flutter/material.dart';
import 'package:island/modular/api/payment.dart';
class MiniAppPayment extends StatelessWidget {
const MiniAppPayment({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Payment Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => _processWithOverlay(context),
child: const Text('Pay $10.00 (with overlay)'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _processDirect(context),
child: const Text('Pay $10.00 (direct)'),
),
],
),
),
);
}
Future<void> _processWithOverlay(BuildContext context) async {
final api = PaymentAPI.instance;
final result = await api.processPaymentWithOverlay(
context: context,
createOrderRequest: CreateOrderRequest(
amount: 1000, // $10.00
currency: 'USD',
remarks: 'Test payment from mini-app',
appIdentifier: 'com.example.miniapp',
),
enableBiometric: true,
);
if (!context.mounted) return;
if (result.success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment successful!')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment failed: ${result.error}'),
backgroundColor: Colors.red,
),
);
}
}
Future<void> _processDirect(BuildContext context) async {
final api = PaymentAPI.instance;
final result = await api.processDirectPayment(
PaymentRequest(
orderId: 'order_${DateTime.now().millisecondsSinceEpoch}',
amount: 1000,
currency: 'USD',
pinCode: '123456', // Should come from user input
enableBiometric: false,
),
);
if (!context.mounted) return;
if (result.success) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Payment successful!')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Payment failed: ${result.error}'),
backgroundColor: Colors.red,
),
);
}
}
}
Notes
- All methods are async and return Futures
- Errors are thrown as exceptions, catch and handle in your mini-app
- PIN codes must be 6 digits
- Amount is in smallest currency unit (cents for USD)
- Token is managed internally, no need to provide it
- Server URL is loaded from app preferences
Integration with flutter_eval
To expose this API to mini-apps loaded via flutter_eval:
- Add to plugin registry:
// In lib/modular/registry.dart
import 'package:island/modular/api/payment.dart';
Future<PluginLoadResult> loadMiniApp(...) async {
// ... existing code ...
final runtime = Runtime(ByteData.sublistView(bytecode));
runtime.addPlugin(flutterEvalPlugin);
// Register Payment API
final paymentAPI = PaymentAPI.instance;
// You'll need to create a bridge to expose this to eval
// ... rest of loading code
}
- Mini-app can access API:
// mini_app/main.dart
final paymentAPI = PaymentAPI.instance; // Will be exposed via bridge
Security Considerations
- Never hardcode PIN codes: Always get from user input
- Use secure storage: App manages PIN storage securely
- Validate amounts: Ensure amounts are reasonable
- Handle errors gracefully: Show user-friendly messages
- Biometric is optional: Some devices may not support it