Why PAN Card Verification is Important
- KYC Requirements: As part of the mandatory KYC process, verifying PAN cards ensures that users are authenticated and that financial transactions are legitimate.
- Legal Compliance: The Indian government mandates PAN card verification for transactions exceeding a certain threshold to avoid tax evasion and ensure regulatory compliance.
- Loan Approvals and Investment: In fintech, during the loan application process or when users are making a significant financial investment, PAN card verification is essential to validate the user's identity.
- Form Input Validation: Before making any API call, validating the PAN card format on the client side using PAN regex in Dart saves API costs and provides instant user feedback.
Understanding the PAN Card Format
Before writing any code, you need to understand what makes a PAN number valid. Every PAN follows a specific 10-character structure:
**Format: AAAAA9999A**
| Position | Characters | Meaning |
|----------|------------|---------|
| 1-3 | AAA | Random alphabets (A-Z) |
| 4 | A | Category of PAN holder |
| 5 | A | First letter of surname/name |
| 6-9 | 9999 | Sequential number (0001-9999) |
| 10 | A | Alphabetic check digit |
### PAN Category Codes (4th Character)
| Code | Category |
|------|----------|
| P | Individual / Person |
| C | Company |
| H | Hindu Undivided Family (HUF) |
| A | Association of Persons (AOP) |
| B | Body of Individuals (BOI) |
| G | Government |
| J | Artificial Juridical Person |
| L | Local Authority |
| F | Firm / Partnership |
| T | Trust |
**Example:** In PAN "ABCPK1234A":
- ABC = Random letters
- P = Individual (Person)
- K = First letter of surname (e.g., Kumar)
- 1234 = Sequential number
- A = Check digit
Understanding this structure helps you build accurate validation logic.Step-by-Step: PAN Validation Using Regex in Flutter
Before calling any API, you should validate the PAN format on the client side. This saves API calls and provides instant feedback to users.
### The Regex Pattern
The regex pattern for PAN validation is:
```dart
final panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$');
```
**Breaking it down:**
- `^` — Start of string
- `[A-Z]{5}` — Exactly 5 uppercase letters
- `[0-9]{4}` — Exactly 4 digits
- `[A-Z]{1}` — Exactly 1 uppercase letter
- `$` — End of string
### Creating a PAN Validator Extension
For cleaner code, create a String extension:
```dart
extension PanCardValidator on String {
bool isValidPanCard() {
if (this.isEmpty) return false;
// Convert to uppercase for case-insensitive matching
final pan = this.toUpperCase().trim();
// Check length first
if (pan.length != 10) return false;
// Apply regex pattern
final panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$');
return panRegex.hasMatch(pan);
}
// Get PAN holder category
String? getPanCategory() {
if (!this.isValidPanCard()) return null;
final categories = {
'P': 'Individual',
'C': 'Company',
'H': 'Hindu Undivided Family',
'A': 'Association of Persons',
'B': 'Body of Individuals',
'G': 'Government',
'J': 'Artificial Juridical Person',
'L': 'Local Authority',
'F': 'Firm',
'T': 'Trust',
};
final categoryCode = this.toUpperCase()[3];
return categories[categoryCode];
}
}
```
### Using in TextFormField
```dart
TextFormField(
decoration: InputDecoration(
labelText: 'Enter PAN Number',
hintText: 'e.g., ABCPK1234A',
),
textCapitalization: TextCapitalization.characters,
maxLength: 10,
validator: (value) {
if (value == null || value.isEmpty) {
return 'PAN number is required';
}
if (!value.isValidPanCard()) {
return 'Enter a valid PAN number';
}
return null;
},
onChanged: (value) {
// Real-time validation feedback
if (value.length == 10) {
final category = value.getPanCategory();
print('PAN Category: $category');
}
},
)
```
### Why Regex Validation Alone Isn't Enough
Regex only validates the format. It cannot tell you if:
- The PAN actually exists in NSDL records
- The PAN is active or deactivated
- The name matches the PAN holder
- The PAN is linked to Aadhaar
For these checks, you need API verification with Setu PAN API (covered in the next section).Comparing Validation Approaches
## Regex vs API: When to Use What
| Aspect | Regex Validation | Setu API Verification |
|--------|------------------|----------------------|
| **Speed** | Instant (client-side) | 200-500ms (API call) |
| **Cost** | Free | Per-request pricing |
| **Accuracy** | Format only | Full verification |
| **Use Case** | Form validation | KYC compliance |
| **Offline** | Works offline | Requires internet |
### Recommended Approach
For fintech applications, use both:
1. **First: Regex validation** — Catch format errors instantly
2. **Then: API verification** — Confirm PAN exists in NSDL
```dart
Future<PanVerificationResult> verifyPan(String panNumber) async {
// Step 1: Regex validation
if (!panNumber.isValidPanCard()) {
return PanVerificationResult(
success: false,
error: 'Invalid PAN format',
);
}
// Step 2: API verification
try {
final apiResult = await ApiService.verifyPan(panNumber);
return PanVerificationResult(
success: true,
data: apiResult,
);
} catch (e) {
return PanVerificationResult(
success: false,
error: 'API verification failed: $e',
);
}
}
```
This approach:
- Reduces unnecessary API calls (saves cost)
- Provides instant feedback on format errors
- Only hits the API when the format is validWhat is Setu PAN API?(Flutter Integration)
- x-client-id
- x-client - secret
- x-product-instance-id
Verify PAN Card NumberVerify PAN Card Number in Flutter
- pan is the PAN value. It may belong to different categories like Person, Company, Trust, Government, Firm, etc.
- Consent indicates whether you have collected consent from your customer. To get a successful verification, it must contain Y or y.
- The reason is the explanation of why you are requesting a PAN from your customer. It should be explained in 20 characters or more.
Note: While the implementation of consent and reason cannot be enforced by Setu, we recommend collecting explicit consent from your customers and also explaining to your customers the reason why you are verifying their PAN.
- Use ABCDE1234A for a valid PAN
- Use ABCDE1234B for an invalid PAN, i.e, a PAN number has been found but is invalid. A PAN is considered invalid by NSDL for different reasons.
For example, if it is a blacklisted one, or maybe because it is not linked to an Aadhaar card. - If you use any other values for PAN, you will get a 404 PAN not found error.
Request Body
{
"data": {
"aadhaar_seeding_status": "LINKED", // optional
"category": "Individual",
"full_name": "John Doe"
},
"message": "PAN is valid",
"verification": "success",
"traceId": "1-6346a91a-620cf6cc4f68d2e30316881e"
}
Prerequisites
- A basic understanding of Flutter and Dart before you start.
- Flutter is installed on your system.
- A Setu account is required in order to use the PAN verification API. Here is where you may register. link
Step 1: Configure Your Flutter Project for PAN Validation
flutter create pan_verification_app
cd pan_verification_app
Step 2: Add Dependencies
Step 3: Create the API Service Class
To manage the API requests, create a new file called lib/services/api_service.dart:
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static const String apiUrl = 'https://dg-sandbox.setu.co/api/verify/pan';
static const String clientId = 'YOUR_CLIENT_ID';
static const String clientSecret = 'YOUR_CLIENT_SECRET';
static const String productInstanceId = 'YOUR_PRODUCT_INSTANCE_ID';
static const String bearerToken = 'YOUR_BEARER_TOKEN';
static Future<Map<String, dynamic>> verifyPan(String panNumber) async {
final response = await http.post(
Uri.parse(apiUrl),
headers: {
'x-client-id': clientId,
'x-client-secret': clientSecret,
'x-product-instance-id': productInstanceId,
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $bearerToken',
},
body: json.encode({
'pan': panNumber,
'consent': 'Y',
'reason': 'Reason for verifying PAN set by the developer',
}),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to verify PAN: ${response.body}');
}
}
}
Step 4: Create the Controller
Create a new file lib/controllers/pan_controller.dart to handle the PAN validation business logic using GetX state management. This controller manages the PAN number verification flow.
import 'package:get/get.dart';
import '../services/api_service.dart';
class PanController extends GetxController {
var panNumber = ''.obs;
var result = ''.obs;
var isLoading = false.obs;
void verifyPan() async {
if (panNumber.value.isEmpty) {
result.value = 'Please enter a PAN number.';
return;
}
isLoading.value = true;
try {
final data = await ApiService.verifyPan(panNumber.value);
result.value = 'PAN Verification Successful: ${data['status']}';
} catch (e) {
result.value = 'PAN Verification Failed: $e';
} finally {
isLoading.value = false;
}
}
}
Step 5: Create the UI
In lib/main.dart, create the PAN card validation UI with GetX for state management. This screen allows users to enter their PAN number and see the verification result.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controllers/pan_controller.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: PanVerificationScreen(),
);
}
}
class PanVerificationScreen extends StatelessWidget {
final PanController _panController = Get.put(PanController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PAN Verification'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
onChanged: (value) => _panController.panNumber.value = value,
decoration: InputDecoration(
labelText: 'Enter PAN Number',
),
),
SizedBox(height: 20),
Obx(() => _panController.isLoading.value
? CircularProgressIndicator()
: ElevatedButton(
onPressed: _panController.verifyPan,
child: Text('Verify PAN'),
)),
SizedBox(height: 20),
Obx(() => Text(_panController.result.value)),
],
),
),
);
}
}
Step 6: Run the App
flutter run.
Also Read: Digi-Locker Integration with Flutter: A Comprehensive Guide 2026


