1
- # pan.py - functions for handling Indian Permanent Account number (PAN)
1
+ # pan.py - functions for handling Indian income tax numbers
2
2
#
3
3
# Copyright (C) 2017 Srikanth Lakshmanan
4
+ # Copyright (C) 2021 Gaurav Chauhan
4
5
#
5
6
# This library is free software; you can redistribute it and/or
6
7
# modify it under the terms of the GNU Lesser General Public
20
21
"""PAN (Permanent Account Number, Indian income tax identifier).
21
22
22
23
The Permanent Account Number (PAN) is a 10 digit alphanumeric identifier for
23
- Indian individuals, families and corporates for income tax purposes.
24
+ Indian individuals, families and corporates for income tax purposes. It is
25
+ also issued to foreign nationals subject to a valid visa.
24
26
25
- The number is built up of 5 characters, 4 numbers and 1 character. The fourth
26
- character indicates the type of holder of the number and the last character
27
- is computed by an undocumented checksum algorithm.
27
+ PAN is made up of 5 letters, 4 digits and 1 alphabetic check digit. The 4th
28
+ character indicates the type of holder, the 5th character is either 1st
29
+ letter of the holder's name or holder's surname in case of 'Individual' PAN,
30
+ next 4 digits are serial numbers running from 0001 to 9999 and the last
31
+ character is a check digit computed by an undocumented checksum algorithm.
28
32
29
33
More information:
30
34
31
35
* https://en.wikipedia.org/wiki/Permanent_account_number
36
+ * https://incometaxindia.gov.in/tutorials/1.permanent%20account%20number%20(pan).pdf
32
37
33
38
>>> validate('ACUPA7085R')
34
39
'ACUPA7085R'
35
- >>> validate('234123412347 ')
40
+ >>> validate('ACUPA7085RR ')
36
41
Traceback (most recent call last):
37
42
...
38
43
InvalidLength: ...
44
49
Traceback (most recent call last):
45
50
...
46
51
InvalidComponent: ...
52
+ >>> validate('ACUPA0000R') # serial number should not be '0000'
53
+ Traceback (most recent call last):
54
+ ...
55
+ InvalidComponent: ...
47
56
>>> mask('AAPPV8261K')
48
57
'AAPPVXXXXK'
49
- >>> info('AAPPV8261K')['card_holder_type ']
58
+ >>> info('AAPPV8261K')['holder_type ']
50
59
'Individual'
51
60
"""
52
61
58
67
59
68
_pan_re = re .compile (r'^[A-Z]{5}[0-9]{4}[A-Z]$' )
60
69
70
+ _pan_holder_types = {
71
+ 'A' : 'Association of Persons (AOP)' ,
72
+ 'B' : 'Body of Individuals (BOI)' ,
73
+ 'C' : 'Company' ,
74
+ 'F' : 'Firm/Limited Liability Partnership' ,
75
+ 'G' : 'Government Agency' ,
76
+ 'H' : 'Hindu Undivided Family (HUF)' ,
77
+ 'L' : 'Local Authority' ,
78
+ 'J' : 'Artificial Juridical Person' ,
79
+ 'P' : 'Individual' ,
80
+ 'T' : 'Trust' ,
81
+ 'K' : 'Krish (Trust Krish)' ,
82
+ }
83
+ # Type 'K' may have been discontinued, not listed on Income Text Dept website.
84
+
61
85
62
86
def compact (number ):
63
87
"""Convert the number to the minimal representation. This strips the
@@ -66,54 +90,43 @@ def compact(number):
66
90
67
91
68
92
def validate (number ):
69
- """Check if the number provided is a valid PAN. This checks the
70
- length and formatting."""
93
+ """Check if the number provided is a valid PAN. This checks the length
94
+ and formatting."""
71
95
number = compact (number )
72
96
if len (number ) != 10 :
73
97
raise InvalidLength ()
74
98
if not _pan_re .match (number ):
75
99
raise InvalidFormat ()
76
100
info (number ) # used to check 4th digit
101
+ if number [5 :9 ] == '0000' :
102
+ raise InvalidComponent ()
77
103
return number
78
104
79
105
80
106
def is_valid (number ):
81
- """Check if the number provided is a valid PAN. This checks the
82
- length and formatting."""
107
+ """Check if the number provided is a valid PAN. This checks the length
108
+ and formatting."""
83
109
try :
84
110
return bool (validate (number ))
85
111
except ValidationError :
86
112
return False
87
113
88
114
89
- _card_holder_types = {
90
- 'A' : 'Association of Persons (AOP)' ,
91
- 'B' : 'Body of Individuals (BOI)' ,
92
- 'C' : 'Company' ,
93
- 'F' : 'Firm' ,
94
- 'G' : 'Government' ,
95
- 'H' : 'HUF (Hindu Undivided Family)' ,
96
- 'L' : 'Local Authority' ,
97
- 'J' : 'Artificial Juridical Person' ,
98
- 'P' : 'Individual' ,
99
- 'T' : 'Trust (AOP)' ,
100
- 'K' : 'Krish (Trust Krish)' ,
101
- }
102
-
103
-
104
115
def info (number ):
105
116
"""Provide information that can be decoded from the PAN."""
106
117
number = compact (number )
107
- card_holder_type = _card_holder_types .get (number [3 ])
108
- if not card_holder_type :
118
+ holder_type = _pan_holder_types .get (number [3 ])
119
+ if not holder_type :
109
120
raise InvalidComponent ()
110
121
return {
111
- 'card_holder_type' : card_holder_type ,
122
+ 'holder_type' : holder_type ,
123
+ 'card_holder_type' : holder_type , # for backwards compatibility
112
124
'initial' : number [4 ],
113
125
}
114
126
115
127
116
128
def mask (number ):
117
- """Mask the PAN as per CBDT masking standard."""
129
+ """Mask the PAN as per Central Board of Direct Taxes (CBDT) masking
130
+ standard."""
118
131
number = compact (number )
119
132
return number [:5 ] + 'XXXX' + number [- 1 :]
0 commit comments