ITC Card Scanner

A Flutter plugin for scanning credit cards using device camera with OCR capabilities. Automatically extracts card number, expiry date, and cardholder name.

pub package

Features

Card Number Detection - Automatically scans and validates card numbers (13-19 digits)
Expiry Date Extraction - Detects multiple formats (MM/YY, MM/YYYY, MM-YY, etc.)
Cardholder Name Recognition - Extracts name from card (optional)
Multiple Card Types - Supports Visa, MasterCard, Amex, Diners, Discover, JCB
Luhn Validation - Built-in card number validation
Real-time Scanning - Live camera preview with scanning overlay
Cross Platform - Works on both Android and iOS

Supported Card Types

  • Visa (13, 16, 19 digits)
  • MasterCard (16 digits)
  • American Express (15 digits)
  • Diners Club (14 digits)
  • Discover (16 digits)
  • JCB (16 digits)

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  itc_card_scanner: ^0.0.4

Then run:

flutter pub get

Platform Setup

Android Setup

1. Minimum SDK Version

Ensure your android/app/build.gradle has minimum SDK 24:

android {
    defaultConfig {
        minSdkVersion 24  // Required for camera and ML Kit
        targetSdkVersion 34
    }
}

2. Permissions (Automatically Added)

The plugin automatically adds these permissions to your AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FLASHLIGHT" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-feature android:name="android.hardware.camera.autofocus" />

iOS Setup

1. Minimum iOS Version

Ensure your ios/Runner.xcodeproj targets iOS 13.0+:

Open ios/Podfile and set:

platform :ios, '13.0'

2. Camera Permission (Required)

Add camera permission to ios/Runner/Info.plist:

<dict>
    <key>NSCameraUsageDescription</key>
    <string>This app needs camera access to scan credit cards</string>
</dict>

Usage

Basic Usage

import 'package:itc_card_scanner/itc_card_scanner.dart';

// Simple scan with default options
final cardDetails = await CardScanner.scanCard();

if (cardDetails != null) {
  print('Card Number: ${cardDetails.cardNumber}');
  print('Expiry Date: ${cardDetails.expiryDate}');
  print('Cardholder Name: ${cardDetails.cardHolderName}');
  print('Card Issuer: ${cardDetails.cardIssuer}');
} else {
  print('Scan cancelled or failed');
}

With Custom Options

import 'package:itc_card_scanner/itc_card_scanner.dart';

// Configure scan options
final scanOptions = CardScanOptions(
  scanExpiryDate: true,              // Scan expiry date (default: true)
  scanCardHolderName: true,          // Scan cardholder name (default: false)
  enableLuhnCheck: true,             // Validate card number (default: true)
  considerPastDatesInExpiryDateScan: false,  // Allow past dates (default: false)
  enableDebugLogs: false,            // Enable debug logging (default: false)
);

// Scan with custom options
final cardDetails = await CardScanner.scanCard(scanOptions: scanOptions);

if (cardDetails != null) {
  print('Scan successful!');
  print('Card: ${cardDetails.cardNumber}');
  print('Expiry: ${cardDetails.expiryDate}');
  print('Name: ${cardDetails.cardHolderName}');
  print('Type: ${cardDetails.cardIssuer}');
}

Error Handling

try {
  final cardDetails = await CardScanner.scanCard();
  
  if (cardDetails != null) {
    // Handle successful scan
    handleScannedCard(cardDetails);
  } else {
    // Handle cancelled scan
    print('User cancelled the scan');
  }
} catch (e) {
  // Handle errors
  print('Error scanning card: $e');
}

Complete Example

import 'package:flutter/material.dart';
import 'package:itc_card_scanner/itc_card_scanner.dart';

class CardScanPage extends StatefulWidget {
  @override
  _CardScanPageState createState() => _CardScanPageState();
}

class _CardScanPageState extends State<CardScanPage> {
  CardDetails? _cardDetails;
  bool _scanCardHolderName = false;
  bool _enableDebugLogs = false;
  bool _isLoading = false;

  Future<void> _scanCard() async {
    setState(() {
      _isLoading = true;
    });

    try {
      final scanOptions = CardScanOptions(
        scanCardHolderName: _scanCardHolderName,
        enableDebugLogs: _enableDebugLogs,
      );
      
      final cardDetails = await CardScanner.scanCard(scanOptions: scanOptions);
      
      setState(() {
        _cardDetails = cardDetails;
      });
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: $e')),
      );
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ITC Card Scanner'),
        backgroundColor: Colors.blue,
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            ElevatedButton(
              onPressed: _isLoading ? null : _scanCard,
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green,
                padding: EdgeInsets.all(16),
              ),
              child: _isLoading 
                ? CircularProgressIndicator(color: Colors.white)
                : Text(
                    'SCAN CARD',
                    style: TextStyle(fontSize: 18, color: Colors.white),
                  ),
            ),
            SizedBox(height: 20),
            
            // Card Details Display
            Container(
              padding: EdgeInsets.all(16),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                borderRadius: BorderRadius.circular(8),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Card Details:',
                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                  ),
                  SizedBox(height: 10),
                  Text(_cardDetails?.toString() ?? 'No card scanned yet'),
                ],
              ),
            ),
            
            SizedBox(height: 20),
            
            // Options
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Checkbox(
                  value: _scanCardHolderName,
                  onChanged: (bool? value) {
                    setState(() {
                      _scanCardHolderName = value ?? false;
                    });
                  },
                ),
                Text('Scan Card Holder Name'),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Checkbox(
                  value: _enableDebugLogs,
                  onChanged: (bool? value) {
                    setState(() {
                      _enableDebugLogs = value ?? false;
                    });
                  },
                ),
                Text('Enable Debug Logs'),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

API Reference

CardScanner

scanCard({CardScanOptions? scanOptions})

Launches the card scanner with optional configuration.

Parameters:

  • scanOptions (optional): Configuration options for scanning

Returns: Future<CardDetails?>

  • Returns CardDetails object on successful scan
  • Returns null if scan was cancelled or failed

CardScanOptions

Configuration class for customizing scan behavior.

const CardScanOptions({
  this.scanExpiryDate = true,                    // Scan expiry date
  this.scanCardHolderName = false,               // Scan cardholder name
  this.initialScansToDrop = 1,                   // Initial scans to ignore
  this.validCardsToScanBeforeFinishingScan = 6,  // Scans before finishing
  this.considerPastDatesInExpiryDateScan = false, // Allow past dates
  this.maxCardHolderNameLength = 26,             // Max name length
  this.enableLuhnCheck = true,                   // Enable Luhn validation
  this.cardScannerTimeOut = 0,                   // Timeout (0 = no timeout)
  this.enableDebugLogs = false,                  // Enable debug logging
});

CardDetails

Result object containing scanned card information.

class CardDetails {
  final String cardNumber;      // Card number without spaces
  final String cardHolderName;  // Cardholder name (if scanned)
  final String expiryDate;      // Expiry date in MM/YY format
  final String cardIssuer;      // Card type/issuer
  
  // Getters
  String get cardNumber;
  String get cardHolderName;
  String get expiryDate;
  String get cardIssuer;
  
  // Utility
  Map<String, String> get map;
  String toString();
}

Supported Date Formats

The scanner can detect expiry dates in multiple formats:

  • MM/YY (12/25)
  • MM/YYYY (12/2025)
  • MM-YY (12-25)
  • MM-YYYY (12-2025)
  • MMYY (1225)
  • MM YY (12 25)
  • YY/MM (25/12)
  • YYYY/MM (2025/12)

All formats are normalized to MM/YY in the result.

Troubleshooting

Common Issues

Android Issues

  1. Camera Permission Denied

    • Ensure user grants camera permission when prompted
    • Check if device has camera hardware
  2. Build Errors

    • Verify minSdkVersion is 24 or higher
    • Clean and rebuild: flutter clean && flutter pub get

iOS Issues

  1. Camera Permission Denied

    • Verify NSCameraUsageDescription is in Info.plist
    • Check iOS Settings → Privacy → Camera
  2. Build Errors

    • Ensure iOS deployment target is 13.0+
    • Run cd ios && pod install

Performance Tips

  • Good Lighting: Ensure adequate lighting for better OCR accuracy
  • Steady Hand: Hold device steady while scanning
  • Card Position: Position card within the overlay frame
  • Clean Card: Ensure card surface is clean and readable

Requirements

  • Flutter: >= 3.0.0
  • Dart: >= 3.0.0
  • Android: API level 24+ (Android 7.0+)
  • iOS: 13.0+
  • Camera: Device must have camera hardware
  • Permissions: Camera access required

Privacy & Security

  • 🔒 Local Processing: All card data is processed locally on the device
  • 🚫 No Network: No card information is transmitted to external servers
  • 💾 No Storage: Card data is not stored on the device
  • 🛡️ Secure: OCR processing happens in device memory only

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For issues and feature requests, please visit our GitHub repository.