2 This file is part of MagRead.
4 MagRead is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 MagRead is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with MagRead. If not, see <http://www.gnu.org/licenses/>.
17 Written by Jeffrey Malone <ieatlint@tehinterweb.com>
18 http://blog.tehinterweb.com
20 #include "carddetect.h"
22 CardDetect::CardDetect( MagCard *_card ) {
28 void CardDetect::setCard( MagCard *_card ) {
33 void CardDetect::processCard() {
37 card->type = MagCard::CARD_UNKNOWN;
39 /* Fill in the fields.. */
42 if( card->encoding == ABA && card->charStream.startsWith( ';' ) ) {
43 acctLen = card->charStream.indexOf( '=' ) - 1;
45 card->accountNumber = card->charStream.mid( 1, acctLen );
47 expDate = card->charStream.mid( acctLen + 2, 4 );
48 card->miscData = card->charStream.mid( acctLen + 6 ).remove( '?' );
50 } else if( card->encoding == IATA && card->charStream.startsWith( "%B" ) ) {
51 acctLen = card->charStream.indexOf( '^', 1 ) - 2;
53 card->accountNumber = card->charStream.mid( 2, acctLen );
55 card->miscData = card->charStream.section( '^', 2 ).remove( '?' );
56 expDate = card->miscData.left( 4 );
57 card->miscData.remove( 0, 4 );
63 //if the above didn't yield us an account number, just copy the charStream */
64 if( card->accountNumber.isEmpty() ) {
65 card->accountNumber = card->charStream;
66 card->charStream.remove( ';' );
67 card->charStream.remove( '%' );
68 card->charStream.remove( '?' );
74 if( card->accountValid )
77 /* This code allows partial credit card swipes to be treated as valid, as long at least 3 digits
78 * of extra data that passes checksum are present */
79 if( card->accountValid && !card->swipeValid && card->type & MagCard::CARD_CC ) {
80 if( 3 > card->miscData.length() || card->miscData.left( 3 ).contains( BAD_CHAR ) ) {
81 card->accountValid = false;
85 if( card->swipeValid && card->type == MagCard::CARD_UNKNOWN )
86 aamvaCardCheck( expDate );
88 if( !expDate.isEmpty() && card->type != MagCard::CARD_AAMVA ) {
89 card->expirationDate = QDate::fromString( expDate, "yyMM" );
90 if( card->expirationDate.year() < 1980 )//qdate defaults to 19nn. Adjust for this.
91 card->expirationDate = card->expirationDate.addYears( 100 );
93 if( card->expirationDate.isValid() ) {//Make it so it expires on the last day of the given month
94 card->expirationDate = card->expirationDate.addDays( card->expirationDate.daysInMonth() - 1);
96 card->type = MagCard::CARD_UNKNOWN; // must contain a valid date to be known
101 void CardDetect::aamvaIssuerList() {
102 issuerList[ "636026" ] = (struct issuer) { "Arizona", "AZ", "L" };
103 issuerList[ "0636021" ] = (struct issuer) { "Arkansas", "AR", "" };
104 issuerList[ "636014" ] = (struct issuer) { "California", "CA", "L" };
105 issuerList[ "636020" ] = (struct issuer) { "Colorado", "CO", "NN-NNN-NNNN" };
106 issuerList[ "636010" ] = (struct issuer) { "Florida", "FL", "LNNN-NNN-NN-NNN-N" };
107 issuerList[ "636018" ] = (struct issuer) { "Iowa", "IA", "NNNLLNNNN" };
108 issuerList[ "636022" ] = (struct issuer) { "Kansas", "KS", "KNN-NN-NNNN" };
109 issuerList[ "636007" ] = (struct issuer) { "Louisiana", "LA", "" };
110 issuerList[ "636003" ] = (struct issuer) { "Maryland", "MD", "L-NNN-NNN-NNN-NNN" };
111 issuerList[ "636032" ] = (struct issuer) { "Michigan", "MI", "L NNN NNN NNN NNN" };
112 issuerList[ "636038" ] = (struct issuer) { "Minnesota", "MN", "L" };
113 issuerList[ "636030" ] = (struct issuer) { "Missouri", "MO", "L" };
114 issuerList[ "636039" ] = (struct issuer) { "New Hampshire", "NH", "NNLLLNNNN" };
115 issuerList[ "636009" ] = (struct issuer) { "New Mexico", "NM", "" };
116 issuerList[ "636023" ] = (struct issuer) { "Ohio", "OH", "LLNNNNNN" };
117 issuerList[ "636025" ] = (struct issuer) { "Pennsylvania", "PA", "NN NNN NNN" };
118 issuerList[ "636005" ] = (struct issuer) { "South Carolina", "SC", "" };
119 issuerList[ "636015" ] = (struct issuer) { "Texas", "TX", "" };
120 issuerList[ "636024" ] = (struct issuer) { "Vermont", "VT", "NNNNNNNL" };
121 issuerList[ "636031" ] = (struct issuer) { "Wisconsin", "WI", "LNNN-NNNN-NNNN-NN" };
122 issuerList[ "636027" ] = (struct issuer) { "State Dept (USA)", "US-DoS", "" };
125 * Arkansas -- Inexplicably, they put a leading 0 on their IIN. This
126 * is handled below in the lookup.
127 * Vermont -- I'm told they have additional format to the one listed
128 * above that is 8 numbers (no letter). As the two
129 * formats have different field widths, I automatically
130 * handle this in the formatting algorithm.
133 //Formatting information is not available for Canada right now
134 issuerList[ "636028" ] = (struct issuer) { "British Columbia", "BC", "" };
135 issuerList[ "636017" ] = (struct issuer) { "New Brunswick", "NB", "" };
136 issuerList[ "636016" ] = (struct issuer) { "Newfoundland", "NL", "" };
137 issuerList[ "636013" ] = (struct issuer) { "Nova Scotia", "NS", "" };
138 issuerList[ "636012" ] = (struct issuer) { "Ontario", "ON", "" };
139 issuerList[ "636044" ] = (struct issuer) { "Saskatchewan", "SK", "" };
141 //These may or may not have magstripes. I'm including them in case they do
142 issuerList[ "604427" ] = (struct issuer) { "American Samoa", "AS", "" };
143 issuerList[ "636019" ] = (struct issuer) { "Guam", "GU", "" };
144 issuerList[ "636062" ] = (struct issuer) { "US Virgin Islands", "US-VI", "" };
145 issuerList[ "636056" ] = (struct issuer) { "Coahuila", "MX-COA", "" };
146 issuerList[ "636057" ] = (struct issuer) { "Hidalgo", "MX-HID", "" };
149 void CardDetect::aamvaCardCheck( QString expDate ) {
150 if( card->encoding == IATA )
151 return; //we're only going to support ABA for now
152 struct issuer issuerInfo;
154 QString iin = card->accountNumber.left( 6 );
156 issuerInfo = issuerList.value( iin );
157 if( issuerInfo.name.isEmpty() ) {
158 iin = card->accountNumber.mid( 1, 6 );
159 issuerInfo = issuerList.value( iin );
160 if( issuerInfo.name.isEmpty() )
161 return; // this is not a known AAMVA card, abort
164 card->type = MagCard::CARD_AAMVA;
166 card->aamvaIssuer = iin;
167 card->aamvaIssuerName = issuerInfo.name;
168 card->aamvaIssuerAbr = issuerInfo.abbreviation;
170 card->accountNumber.remove( iin );
171 if( card->miscData.length() > 8 )
172 card->accountNumber.append( card->miscData.mid( 8 ) );
174 //format the id number if applicable
175 if( !issuerInfo.format.isEmpty() ) {
176 for( int i = 0; i < issuerInfo.format.length(); i++ ) {
177 if( issuerInfo.format.at( i ) == 'L' ) {
178 if( card->accountNumber.length() - i < 2 )
180 QChar letter = card->accountNumber.mid( i, 2 ).toInt() + 64;
181 if( letter.isLetter() )
182 card->accountNumber.replace( i, 2, letter );
183 } else if( issuerInfo.format.at( i ) != 'N' ) {
184 card->accountNumber.insert( i, issuerInfo.format.at( i ) );
190 QString bday = card->miscData.left( 8 );
191 if( bday.mid( 4, 2 ) > "12" ) { //some (Calif) violate AAMVA standard and switch the exp and bday month values
192 QString exp = bday.mid( 4, 2 );
193 bday.replace( 4, 2, expDate.right( 2 ) );
194 expDate.replace( 2, 2, exp );
196 card->aamvaBirthday = QDate::fromString( bday, "yyyyMMdd" );
198 //set the expiration date
199 if( expDate.endsWith( "99" ) ) { // expires on the birth day and month in the given year
200 expDate.replace( 2, 4, bday.mid( 4, 4 ) );
201 expDate.prepend( "20" );
203 card->expirationDate = QDate::fromString( expDate, "yyyyMMdd" );
204 } else if( !expDate.endsWith( "77" ) ) {
205 if( expDate.endsWith( "88" ) )// expires on last day of the month of birth in given year
206 expDate.replace( 2, 2, bday.mid( 4, 2 ) );
208 expDate.prepend( "20" );
209 card->expirationDate = QDate::fromString( expDate, "yyyyMM" );
210 card->expirationDate.addDays( card->expirationDate.daysInMonth() - 1 );
215 void CardDetect::creditCardCheck() {
216 int acctLen = card->accountNumber.length();
217 if( acctLen == 16 ) {
218 if( card->accountNumber.startsWith( '4' ) ) {
219 card->type = MagCard::CARD_VISA;
220 card->accountIssuer = "Visa";
221 } else if( card->accountNumber.left( 2 ) >= "51" && card->accountNumber.left( 2 ) <= "55" ) {
222 card->type = MagCard::CARD_MC;
223 card->accountIssuer = "MasterCard";
224 } else if( card->accountNumber.startsWith( "6011" ) || card->accountNumber.startsWith( "65" ) ) {
225 card->type = MagCard::CARD_DISC;
226 card->accountIssuer = "Discover";
228 } else if( acctLen == 15 ) {
229 if( card->accountNumber.startsWith( "34" ) || card->accountNumber.startsWith( "37" ) ) {
230 card->type = MagCard::CARD_CC | MagCard::CARD_AMEX;
231 card->accountIssuer = "American Express";
235 if( card->encoding == IATA && card->type & MagCard::CARD_CC ) {
236 card->accountHolder = card->charStream.section( '^', 1, 1 ).trimmed();
237 if( card->accountHolder.contains( '/' ) ) { // fix the formatting from "LAST/FIRST" on some cards
238 card->accountHolder = card->accountHolder.section( '/', 1, 1 ) + ' ' + card->accountHolder.section( '/', 0, 0 );
243 /* verifies the card->accountNumber against the luhn algorithm and sets card->accountValid */
244 void CardDetect::luhnCheck() {
246 for( int i = 0; i < card->accountNumber.length(); i++ ) {
247 if( i & 1 ) { // if odd
248 total += card->accountNumber.at( i ).digitValue();
250 int x = card->accountNumber.at( i ).digitValue() * 2;
257 card->accountValid = !( total % 10 );