Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AAMVA (Driver's License) Parsing #19

Open
rickjedi opened this issue Jul 9, 2016 · 2 comments
Open

AAMVA (Driver's License) Parsing #19

rickjedi opened this issue Jul 9, 2016 · 2 comments

Comments

@rickjedi
Copy link

rickjedi commented Jul 9, 2016

I cannot seem to get the parser to recognize the AAMVA format. There are other plugins that do that, but I'm already using your plugin to parse payment card data so I need a modification that will parse driver's licenses.

Here's the configuration I'm using:

$.cardswipe({
        firstLineOnly: false,
        success: cardSwipeComplete,
        parsers: [aamva, "generic"],
        debug: true,
        error: errorCallback,
        prefixCharacter: '%',
    });

I write to the console whenever any one of the states is entered, but they never fire. In debug mode I get into the PENDING state, since I've changed the prefix from the standard "%B" to simply "%", but success, start, end, failure, none of those states gets entered.

I have not yet written the aamva parser, so I was hoping you could help. Here's the GIT that another developer published for that purpose: https://github.com/winfinit/aamvajs

Here's the standard for AAMVA (its actually in a table, but all the fields are there in three tracks):

AAMVA Driver’s License Format

TRACK 1
Field ID Character Contents Length*
a Start Sentinel (%) 1
b State or Province 2
c City 13
d Name 35
e Address 29
f End Sentinel (?) 1
g Linear Redundancy Check
(LRC) Character 1

TRACK 2
Field ID Characte r Contents Length
a Start Sentinel (;) 1
b ANSI User Code 1
c ANSI User ID 5
d Jurisdiction ID/DL 14
e Expiration date 4
f Birthdate 8
g Remainder of Jurisdiction ID/DL 5
h End Sentinel (?) 1
i Linear Redundancy Check
(LRC) Character 1

TRACK 3
Field ID Character Contents Length
a Start Sentinel (%) 1
b Template Version # 1
c Security Version # 1
d Postal Code 11
e Class 2
f Restrictions 10
g Endorsements 4
h Sex 1
i Height 3
j Weight 3
k Hair Color 3
l Eye Color 3
m ID # 10
n Reserved Space 16
o Error Correction 6
p Security 5
q End Sentinel (?) 1
r Linear Redundancy Check
(LRC) Character 1

@user00265
Copy link

My card reader doesn't have a track 3 reader on the head, so the code from @winfinit was adapted to use tracks 1 and 2. I had success with a MN and WI driver's licenses as tests. It obviously returns more data than a credit card. There original library outputs Last/First/Middle as an object, this was changed to output entire field as a string in common US format of First/Middle/Last, but can easily be changed. Please note that to get ALL data, you cannot have firstLineOnly set to true -- it will break the parser.

I don't claim any ownership as this code is 99% from @winfinit and his aamvajs project. Hat off to him for this code!

@CarlRaymond, what do you think?

      // AAMVA parser. US Driver's License
      aamva: function(rawData) {
          data = rawData.replace(/\n/, "");

          // replace spaces with regular space
          data = data.replace(/\s/g, " ");

          var track = data.match(/(.*?\?)(.*?\?)/);
          var res1 = track[1].match(/(\!)([A-Z]{2})([^\^]{0,13})\^?([^\^]{0,35})\^?([^\^]{0,29})\^?\s*?\?/);
          var res2 = track[2].match(/(;)(\d{6})(\d{0,13})(\=)(\d{4})(\d{8})(\d{0,5})\=?\?/);
          var state = res1[2];

          // Return AAMVA data
          return {
              "state": res1[2],
              "city": res1[3],
              "name": function() {
                  var name = '';
                  var res = res1[4].match(/([^\$]{0,35})\$?([^\$]{0,35})?\$?([^\$]{0,35})?/);
                  if (!res) return;
                  if (res[2]) {
                      name = name + res[2];
                  }
                  if (res[3]) {
                      name = name + res[3];
                  }
                  if (res[1]) {
                      name = name + res[1];
                  }
                  return name;
              }(),
              "address": res1[5],
              "iso_iin": res2[2],
              "dl": res2[3],
              "expiration_date": function() {
                  date = res2[5];
                  if (!date) return;
                  var start = parseInt(date[0] + date[1]);
                  if (start < 13) {
                      return date[4] + date[5] + date[6] + date[7] + date[0] + date[1] + date[2] + date[3];
                  }
                  return date;
              }(),
              "birthday": function() {
                  var dob = res2[6].match(/(\d{4})(\d{2})(\d{2})/);
                  if (!dob) return;

                  if (dob[2] === '99') {
                      /* FL decided to reverse 2012 aamva spec, 99 means here
                          that dob month === to expiration month, it should be
                          opposite
                          */
                      var exp_dt = res2[5].match(/(\d{2})(\d{2})/);
                      dob[2] = exp_dt[2];
                  }
                  //dob[2]--; what was this for?
                  return dob[1] + dob[2] + dob[3];
              }(),
              "dl_overflow": res2[7],
              "id": function() {
                  var id;
                  switch (state) {
                      case "FL":
                          var res = res2[3].match(/(\d{2})(.*)/);
                          if (!res) return;
                          id = (String.fromCharCode(Number(res[1]) + 64) + res[2] + res2[7]);
                          break;
                      default:
                          id = res2[3];
                          break;
                  }
                  return id;
              }()
          };
      },

@CarlRaymond
Copy link
Owner

Thanks for the contribution. However, it doesn't look like your parser will return null when the data doesn't fit the format. That's necessary so that multiple parsers can be tried in succession until one succeeds.

If you can add that check, then it's looking good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants