Dart ๐ฏ
The official Dart client for the CardScan API provides support for Flutter, web, and server-side Dart applications.
Installation
Add to your pubspec.yaml
:
dependencies:
cardscan_client: ^1.0.0
Then run:
flutter pub get
# or for Dart-only projects
dart pub get
Basic Usage
import 'package:cardscan_client/cardscan_client.dart';
// Initialize with your API key
final apiKey = 'sk_test_cardscan_ai_...';
final client = CardScanApi(apiKey: apiKey);
// Generate a session token for a user
Future<void> authenticate() async {
final tokenResponse = await client.getAccessToken(
userId: 'unique-user-123',
);
final sessionToken = tokenResponse['Token'];
final identityId = tokenResponse['IdentityId'];
final sessionId = tokenResponse['session_id'];
// Initialize client with session token for frontend operations
final userClient = CardScanApi(
sessionToken: sessionToken,
live: false, // Use sandbox
);
}
Card Scanning Workflow
1. Create a Card
Future<CardApiResponse> createCard() async {
final card = await userClient.createCard(
CreateCardRequest(
enableBacksideScan: false,
enableLivescan: false,
metadata: {
'patient_id': '12345',
'visit_id': 'v-67890',
},
),
);
print('Card ID: ${card.cardId}');
print('State: ${card.state}'); // CardState.pending
return card;
}
2. Generate Upload URL
Future<GenerateCardUploadUrlResponse> generateUploadUrl(String cardId) async {
final uploadData = await userClient.generateCardUploadUrl(
cardId,
GenerateCardUploadUrlRequest(
orientation: ScanOrientation.front,
captureType: ScanCaptureType.manual,
),
);
return uploadData;
}
3. Upload Image
import 'dart:io';
import 'package:dio/dio.dart';
Future<void> uploadImage(
File imageFile,
GenerateCardUploadUrlResponse uploadData,
) async {
final dio = Dio();
final formData = FormData();
// Add upload parameters
uploadData.uploadParameters.forEach((key, value) {
formData.fields.add(MapEntry(key, value));
});
// Add image file
formData.files.add(MapEntry(
'file',
await MultipartFile.fromFile(
imageFile.path,
filename: 'card.jpg',
),
));
final response = await dio.post(
uploadData.uploadUrl,
data: formData,
);
if (response.statusCode != 204) {
throw Exception('Upload failed: ${response.statusCode}');
}
}
4. Poll for Results
Future<CardApiResponse> waitForCompletion(
String cardId, {
Duration timeout = const Duration(minutes: 5),
}) async {
final startTime = DateTime.now();
while (DateTime.now().difference(startTime) < timeout) {
final card = await userClient.getCard(cardId);
if (card.state == CardState.completed ||
card.state == CardState.error) {
return card;
}
await Future.delayed(const Duration(seconds: 2));
}
throw TimeoutException('Card processing timed out');
}
// Usage
final completedCard = await waitForCompletion(card.cardId);
if (completedCard.details != null) {
print('Member ID: ${completedCard.details!.memberId}');
print('Group Number: ${completedCard.details!.groupNumber}');
}
Error Handling
import 'package:cardscan_client/cardscan_client.dart';
try {
final card = await client.getCard('invalid-id');
} on ApiException catch (e) {
switch (e.statusCode) {
case 401:
print('Invalid API key or session token');
break;
case 404:
print('Card not found');
break;
case 429:
print('Rate limit exceeded');
break;
default:
print('API Error: ${e.statusCode} - ${e.message}');
}
} catch (e) {
print('Unexpected error: $e');
}
Flutter Integration
Camera Capture with image_picker
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:cardscan_client/cardscan_client.dart';
class CardScannerScreen extends StatefulWidget {
@override
_CardScannerScreenState createState() => _CardScannerScreenState();
}
class _CardScannerScreenState extends State<CardScannerScreen> {
final client = CardScanApi(sessionToken: 'tok_...');
final picker = ImagePicker();
bool _isLoading = false;
CardApiResponse? _scannedCard;
String? _error;
Future<void> _scanCard() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
// Capture image
final image = await picker.pickImage(
source: ImageSource.camera,
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 90,
);
if (image == null) {
setState(() => _isLoading = false);
return;
}
// Create card
final card = await client.createCard(
CreateCardRequest(enableBacksideScan: false),
);
// Generate upload URL
final uploadData = await client.generateCardUploadUrl(
card.cardId,
GenerateCardUploadUrlRequest(
orientation: ScanOrientation.front,
),
);
// Upload image
await uploadImage(File(image.path), uploadData);
// Wait for results
final completedCard = await waitForCompletion(card.cardId);
setState(() {
_scannedCard = completedCard;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Scan Insurance Card')),
body: Center(
child: _isLoading
? CircularProgressIndicator()
: _scannedCard != null
? _buildCardDetails()
: _buildScanButton(),
),
);
}
Widget _buildCardDetails() {
final details = _scannedCard!.details;
return Card(
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text('Member ID: ${details?.memberId ?? 'N/A'}'),
Text('Group: ${details?.groupNumber ?? 'N/A'}'),
Text('Payer: ${details?.payerName ?? 'N/A'}'),
if (details?.copayEr != null)
Text('ER Copay: \$${details!.copayEr}'),
],
),
),
);
}
Widget _buildScanButton() {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _scanCard,
icon: Icon(Icons.camera_alt),
label: Text('Scan Card'),
),
if (_error != null)
Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
_error!,
style: TextStyle(color: Colors.red),
),
),
],
);
}
}
WebSocket Support
import 'package:web_socket_channel/web_socket_channel.dart';
import 'dart:convert';
class CardScanWebSocket {
final String token;
WebSocketChannel? _channel;
CardScanWebSocket({required this.token});
Stream<CardWebsocketEvent> connect() {
_channel = WebSocketChannel.connect(
Uri.parse('wss://sandbox.cardscan.ai/v1/ws?token=$token'),
);
return _channel!.stream.map((data) {
final json = jsonDecode(data);
return CardWebsocketEvent.fromJson(json);
});
}
void disconnect() {
_channel?.sink.close();
}
}
// Usage
final websocket = CardScanWebSocket(token: sessionToken);
websocket.connect().listen((event) {
switch (event.type) {
case 'card.processing':
print('Processing started');
break;
case 'card.completed':
print('Card completed: ${event.cardId}');
break;
case 'card.error':
print('Error: ${event.error}');
break;
}
});
Eligibility Verification
Future<EligibilityApiResponse> verifyEligibility(
CardApiResponse card,
) async {
final request = CreateEligibilityRequest(
eligibility: EligibilityRequest(
provider: ProviderDto(
firstName: 'John',
lastName: 'Smith',
npi: '1234567890',
),
subscriber: SubscriberDto(
firstName: card.details?.memberName ?? '',
lastName: card.details?.memberName ?? '',
dateOfBirth: '1980-01-01',
),
),
);
final eligibility = await client.createEligibility(
card.cardId,
request,
);
// Poll for results
return await waitForEligibilityCompletion(
eligibility.eligibilityId,
);
}
State Management with Riverpod
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cardscan_client/cardscan_client.dart';
// Providers
final cardScanApiProvider = Provider<CardScanApi>((ref) {
return CardScanApi(sessionToken: 'tok_...');
});
final currentCardProvider = StateNotifierProvider<CardNotifier, AsyncValue<CardApiResponse?>>((ref) {
return CardNotifier(ref.read(cardScanApiProvider));
});
// State Notifier
class CardNotifier extends StateNotifier<AsyncValue<CardApiResponse?>> {
final CardScanApi _api;
CardNotifier(this._api) : super(AsyncValue.data(null));
Future<void> scanCard(File imageFile) async {
state = AsyncValue.loading();
try {
// Create card
final card = await _api.createCard(
CreateCardRequest(enableBacksideScan: false),
);
// Generate upload URL
final uploadData = await _api.generateCardUploadUrl(
card.cardId,
GenerateCardUploadUrlRequest(
orientation: ScanOrientation.front,
),
);
// Upload image
await uploadImage(imageFile, uploadData);
// Wait for results
final completedCard = await waitForCompletion(card.cardId);
state = AsyncValue.data(completedCard);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
}
// Widget
class CardScanView extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final cardState = ref.watch(currentCardProvider);
return cardState.when(
data: (card) => card != null
? CardDetailsWidget(card: card)
: ScanButton(
onPressed: () => _pickAndScanImage(ref),
),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
);
}
Future<void> _pickAndScanImage(WidgetRef ref) async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
ref.read(currentCardProvider.notifier).scanCard(File(image.path));
}
}
}
Web Support
For Flutter Web, use the HTML file input:
import 'dart:html' as html;
import 'dart:typed_data';
Future<void> uploadImageWeb(
Uint8List imageBytes,
GenerateCardUploadUrlResponse uploadData,
) async {
final formData = html.FormData();
// Add upload parameters
uploadData.uploadParameters.forEach((key, value) {
formData.append(key, value);
});
// Create blob from bytes
final blob = html.Blob([imageBytes], 'image/jpeg');
formData.appendBlob('file', blob, 'card.jpg');
// Upload using Fetch API
final response = await html.HttpRequest.request(
uploadData.uploadUrl,
method: 'POST',
sendData: formData,
);
if (response.status != 204) {
throw Exception('Upload failed: ${response.status}');
}
}
Configuration
// Custom configuration
final client = CardScanApi(
apiKey: 'sk_test_cardscan_ai_...',
baseUrl: 'https://sandbox.cardscan.ai/v1',
timeout: Duration(seconds: 30),
retryAttempts: 3,
retryDelay: Duration(seconds: 1),
);
// With custom HTTP client
import 'package:dio/dio.dart';
final dio = Dio()
..interceptors.add(LogInterceptor())
..options.headers['X-Custom-Header'] = 'value';
final clientWithCustomHttp = CardScanApi(
apiKey: apiKey,
httpClient: dio,
);
Testing
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:cardscan_client/cardscan_client.dart';
class MockCardScanApi extends Mock implements CardScanApi {}
void main() {
group('CardScan Tests', () {
late MockCardScanApi mockApi;
setUp(() {
mockApi = MockCardScanApi();
});
test('creates card successfully', () async {
final expectedCard = CardApiResponse(
cardId: 'test-id',
state: CardState.pending,
);
when(mockApi.createCard(any)).thenAnswer(
(_) async => expectedCard,
);
final card = await mockApi.createCard(
CreateCardRequest(enableBacksideScan: false),
);
expect(card.cardId, equals('test-id'));
expect(card.state, equals(CardState.pending));
});
});
}
Source Code
View the source code and contribute: GitHub
Last updated
Was this helpful?