解析来自磁条的信用卡输入

发布于 2024-08-18 16:44:22 字数 223 浏览 1 评论 0原文

有谁知道如何解析从磁卡刷卡器输入的信用卡字符串?

我尝试过 JavaScript 解析器,但从未让它工作。这就是输入的样子。

%BNNNNNNNNNNNNNNNN^DOE/JOHN
^1210201901000101000100061000000?;NNNNNNNNNNNNNNNN=12102019010106111001?

N 是信用卡号码。

Does anyone know how to parse a credit card string input from a Magnetic Card Swiper?

I tried a JavaScript parser but never got it to work. This is what the input looks like.

%BNNNNNNNNNNNNNNNN^DOE/JOHN
^1210201901000101000100061000000?;NNNNNNNNNNNNNNNN=12102019010106111001?

The N's are the credit card number.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

尹雨沫 2024-08-25 16:44:22

请参阅磁条卡条目 @ Wikipedia


轨道一,格式 B:

  • 起始哨兵 — 一个字符(通常为“%”)
  • 格式代码="B" — 一个字符(仅限字母)
  • 主帐号 (PAN) — 最多 19 个字符。通常,但不是
    始终与信用卡号匹配
    印在卡的正面。
  • 字段分隔符 — 一个字符(通常为“^”)
  • 名称 - 2 到 26 个字符
  • 字段分隔符 — 一个字符(通常为“^”)
  • 到期日期 — 四个字符,格式为 YYMM。
  • 服务代码 - 三个字符
  • 任意数据 - 可能包括 Pin 验证关键指标(PVKI、
    1 个字符),PIN 验证值
    (PVV,4 个字符),卡验证
    价值或卡验证码 (CVV
    或 CVK,3 个字符)
  • 结束标记 — 一个字符(通常为“?”)
  • 纵向冗余校验 (LRC) — 一个字符(大多数读卡器设备
    刷卡时不返回该值
    滑动到表示层,
    并仅用它来验证输入
    内部向读者展示。)

我希望数据是假的,否则任何人都可以得到:

  • 姓名
  • 到期日期
  • CVV

我不确定,但我认为信用卡号(或可能性的数量)可以使用 LRC 计算。

See the Magnetic Stripe Card entry @ Wikipedia:


Track one, Format B:

  • Start sentinel — one character (generally '%')
  • Format code="B" — one character (alpha only)
  • Primary account number (PAN) — up to 19 characters. Usually, but not
    always, matches the credit card number
    printed on the front of the card.
  • Field Separator — one character (generally '^')
  • Name — two to 26 characters
  • Field Separator — one character (generally '^')
  • Expiration date — four characters in the form YYMM.
  • Service code — three characters
  • Discretionary data — may include Pin Verification Key Indicator (PVKI,
    1 character), PIN Verification Value
    (PVV, 4 characters), Card Verification
    Value or Card Verification Code (CVV
    or CVK, 3 characters)
  • End sentinel — one character (generally '?')
  • Longitudinal redundancy check (LRC) — one character (Most reader devices
    do not return this value when the card
    is swiped to the presentation layer,
    and use it only to verify the input
    internally to the reader.)

I hope the data is fake, otherwise Anyone could get the:

  • Name
  • Expiration Date
  • CVV

And I'm not sure but I think the credit card number (or # of possibilities) can be computed using the LRC.

半葬歌 2024-08-25 16:44:22

我做得更好:我制作了一个视频,展示了如何使用 ASP.Net/c# 准确执行此操作:

http: //www.markhagan.me/Samples/CreditCardSwipeMagneticStripProcessing

这是您可能关心的代码部分:

    protected void CardReader_OTC(object sender, EventArgs e)
    {
        bool CaretPresent = false;
        bool EqualPresent = false;

        CaretPresent = CardReader.Text.Contains("^");
        EqualPresent = CardReader.Text.Contains("=");

        if (CaretPresent)
        {
            string[] CardData = CardReader.Text.Split('^');
            //B1234123412341234^CardUser/John^030510100000019301000000877000000?

            PersonName.Text = FormatName(CardData[1]);
            CardNumber.Text = FormatCardNumber(CardData[0]);
            CardExpiration.Text = CardData[2].Substring(2, 2) + "/" + CardData[2].Substring(0, 2);
        }
        else if (EqualPresent)
        {
            string[] CardData = CardReader.Text.Split('=');
            //1234123412341234=0305101193010877?

            CardNumber.Text = FormatCardNumber(CardData[0]);
            CardExpiration.Text = CardData[1].Substring(2, 2) + "/" + CardData[1].Substring(0, 2);
        }
    }

完整的代码位于我上面链接的网站上。

I did you one better: I made a video showing how to do exactly this with ASP.Net/c#:

http://www.markhagan.me/Samples/CreditCardSwipeMagneticStripProcessing

Here is the section of code that you probably care about:

    protected void CardReader_OTC(object sender, EventArgs e)
    {
        bool CaretPresent = false;
        bool EqualPresent = false;

        CaretPresent = CardReader.Text.Contains("^");
        EqualPresent = CardReader.Text.Contains("=");

        if (CaretPresent)
        {
            string[] CardData = CardReader.Text.Split('^');
            //B1234123412341234^CardUser/John^030510100000019301000000877000000?

            PersonName.Text = FormatName(CardData[1]);
            CardNumber.Text = FormatCardNumber(CardData[0]);
            CardExpiration.Text = CardData[2].Substring(2, 2) + "/" + CardData[2].Substring(0, 2);
        }
        else if (EqualPresent)
        {
            string[] CardData = CardReader.Text.Split('=');
            //1234123412341234=0305101193010877?

            CardNumber.Text = FormatCardNumber(CardData[0]);
            CardExpiration.Text = CardData[1].Substring(2, 2) + "/" + CardData[1].Substring(0, 2);
        }
    }

The complete code is on that website I linked above.

川水往事 2024-08-25 16:44:22

据我所知:

这是一个双轨磁条数据 - 第一磁道以 % 开头,以 ? 结尾,第二磁道以 ;< 开头/code> 并以 ? 结尾。这些是开始/结束标记。

第一个轨道是字母数字,第二个轨道是数字,第三个轨道也是数字(如果我没记错的话)。

开始/结束标记之间的数据可以根据磁条的记录密度而变化。密度越高,一个磁道上可以记录的内容就越多。

使用正则表达式获取数据可能不是挑选所需信息的可靠方法。

并非所有信用卡都有两个磁道,有些信用卡使用三个磁道。

From what I can remember:

That is a two-track magnetic strip data - first track starts with % and ends with ?, the second track starts with ; and ends with ?. These are Start/End markers.

The first track is alphanumeric, the second track is numeric, and there is a third track which is numeric also (if my memory serves correct).

The data between the start/end markers can be variable depending on the recording density of the magnetic strip. The higher the density, the more it can be recorded on one track.

Using a regex to get at the data may not be a reliable method to pick out the information required.

And not all credit cards have exactly two tracks, some uses three tracks.

洒一地阳光 2024-08-25 16:44:22

通常,对于无卡交易(即 MOTO 交易),您将需要 cc#、到期日以及可能的 CVV(又名 CVC2 等)。您可以通过刷卡获取前 2 个,就像在轨迹数据中一样。 CVV 印在卡上。

卡上的名字并不重要。除非您的收单机构和持卡人使用地址验证,但您可以发现 ^^ 之间可能有空白填充,您可以将其删除。

您想要的部分是 track2 NNNNNNNNNNNNNNNN=1210,其中 NNNNN= 卡号 PAN,1210 = 到期日期。

即使 track1 为空(有时是空的,因为它在处理中未使用),您仍然会得到 ;?,因此您可以使用第二个 ; 的索引。作为字符串的开头,= 作为 cc# 字符串的结尾。以 = 后面的 4 个字符作为过期时间。

我建议让持卡人在交易记录中签名,否则他们可能会对卡提出异议并进行退款。

<块引用>

并非所有信用卡都有两个磁道,有些信用卡使用三个磁道。

仅使用 track2 进行处理并具有标准化格式。

借记卡通常无法处理(除非它们有 Visa 借记卡或其他卡)。

PS,您不应该以纯文本形式存储抄送数据,因此请尝试将所有内容保存在内存中或进行强加密。

Generally for a card-not present transaction (i.e. MOTO transactions) you will need cc#, expiry and possibly the CVV (aka CVC2 etc). You can obtain the first 2 from a card-swipe as this in the track data. CVV is printed on the card.

Name on card doesn't matter so much. Unless your acquirer and the cardholder are using address verification, but you can find that between ^^, it may have white space padding which you can remove.

The part you want is track2 NNNNNNNNNNNNNNNN=1210 where NNNNN=card number PAN, and 1210 = Expiry date.

Even if track1 is empty (which sometimes it is as it's not used in processing), you will still get the ;?, so you could use the index of the second ; as start of the string and = as the end of the cc# string. With the 4 characters after the = as the expiry.

I would advise getting the card holder to sign something in record of the transaction otherwise they could dispute the card and do a charge-back.

And not all credit cards have exactly two tracks, some uses three tracks.

Only track2 is used for processing and has a standardized format.

Debit cards can't generally be processed (unless they have a visa-debit card or something).

P.S. you shouldn't store cc data in plain text, so try and keep everything in mem or strong encryption.

养猫人 2024-08-25 16:44:22

这是我的代码:

第一个获取数据的侦听器......该数据需要验证,我正在寻求帮助。良好的滑动效果很好,但不良的滑动会导致解析器出错。

$('#cc-dialog-form').keypress(function(e) 
{

    var charCode = e.which;
    //ie? evt = e || window.event;
    track_start = '%';
    finished = false;
    timeout = 100;
    track_start_code = track_start.charCodeAt(0);
    //console.log('Track_start_code: ' + track_start_code);

    //console.log('keycode ' + e.keycode);


    //console.log('charcode ' + charCode);
    //console.log('track_start_code ' + track_start_code);
    if (charCode == track_start_code)
    {
        collect_track_data = true;
            $('#offline_cc_entry').hide();
            $('#cc_online').hide();
            $('#Manual_CC_DATA').hide();
            $('#cc_loading_image').show();      

    }
    if (collect_track_data)
    {   
        if (charCode == $.ui.keyCode.ENTER) 
        {
            //all done
            //console.log( card_data);
            collect_track_data = false;
            $('#cc_loading_image').hide();
            $('#Manual_CC_DATA').show();
            //console.log("Track Data: " + card_data);


            process_swipe_cc_payment(card_data);
            card_data = '';

        }
        else
        {
            card_data = card_data + String.fromCharCode(charCode);
            console.log(card_data);
            if (e.preventDefault) e.preventDefault();
            e.returnValue=false;
            return false;
        }
    }
    else
    {
        //i am guessing this will be regular input?
        if (charCode == $.ui.keyCode.ENTER) 
        {
             process_keyed_or_offline_CC_payment();
        }
    }
    //console.log("which: " + e.which);
    //console.log("keyCode: " + e.keyCode);
    //track and collect data here?

});

这是解析器......注意,我将其全部放在一个函数中,这样我就可以销毁所有变量,这样它们就不会在浏览器中徘徊。

    parse_data = true;
if (parse_data)
{

var parsed_card_data = {};
parsed_card_data['card_data'] = card_data;
var tracks = card_data.split("?");

//console.log ("tracks");
//console.log (tracks);
parsed_card_data['track1'] = tracks[0];
parsed_card_data['track2'] = tracks[1];
//if there is a third track we might find it under tracks[2]

//splitting the card data OPTION 1

var track1_parsed = tracks[0].split("^");

//console.log (track1_parsed);



//track1 data....
var card_number_track1 = track1_parsed[0].substring(2);


parsed_card_data['card_number_track1'] = card_number_track1;

var details2_1 = tracks[1].split(";");
details2_1 = details2_1[1].split("=");


var exp_date_track_1 = details2_1[1];
exp_date_track_1 = exp_date_track_1.substring(0, exp_date_track_1.length - 1);
exp_date_track_1 = exp_date_track_1.substring(2, 4) + "/" + exp_date_track_1.substring(0,2);
parsed_card_data['exp_track1'] = exp_date_track_1;



//now check if track one matches track 2...

track2_parsed = tracks[1].split("=");


card_number_track_2 = track2_parsed[0].substring(1);



parsed_card_data['card_number_track_2'] = card_number_track_2;
exp_date_track_2 = track2_parsed[1].substring(0,4);
exp_date_track_2 = exp_date_track_2.substring(2, 4) + "/" + exp_date_track_2.substring(0,2);
parsed_card_data['exp_date_track_2'] = exp_date_track_2;


var primary_account_number =  card_number_track1.substring(0,1);


if(card_number_track1 == card_number_track_2 &&  exp_date_track_1 == exp_date_track_2)
{
        //now make a security feature showing the last 4 digits only....
    parsed_card_data['secure_card_number'] = "xxxx " + card_number_track1.substring(card_number_track1.length-4, card_number_track1.length);




    if(card_number_track1.length == 15)
    {
        parsed_card_data['card_type'] = "American Express"; 
    }
    else if(primary_account_number == 4)
    {
        parsed_card_data['card_type'] = "Visa";
    }
    else if(primary_account_number == 5)
    {
        parsed_card_data['card_type'] = "Master Card";
    }
    else if(primary_account_number == 6)
    {
        parsed_card_data['card_type'] = "Discover";
    }
    else
    {
        parsed_card_data['card_type'] = false;
    }

    var names_1 = track1_parsed[1].split("/");
    parsed_card_data['first_name'] = names_1[1].trim();
    parsed_card_data['last_name'] = names_1[0].trim();


    //console.log("return Data");
    //console.log(return_data);

}
else
{
    parsed_card_data = false;
}

    //zero out the variables...

    tracks = '';
    track1_parsed = '';
    card_number_track1 = '';
    details2_1 = '';
    exp_date_track_1 = '';
    track2_parsed = '';
    card_number_track_2 = '';
    exp_date_track_2 = '';
    primary_account_number = '';
}

if(parsed_card_data)
{
    //console.log(parsed_card_data);
    $('#card_type').val(parsed_card_data['card_type']);
    $('#credit_card_number').val(parsed_card_data['secure_card_number']);
    $('#expiration').val(parsed_card_data['exp']);
    $('#card_holder').val(parsed_card_data['first_name']+ " " + parsed_card_data['last_name']);

    //parsed_card_data['track1'] is basically what we want???

    $('#CC_SWIPE_INSTRUCTIONS').hide();
    $('#CC_DATA').hide();
    $('#cc_loading_image').show();



    var post_string = {};
    post_string['ajax_request'] = 'CREDIT_CARD_PAYMENT';
    post_string['amount'] = $('#cc_input').val();
    post_string['card_data'] = parsed_card_data;
    post_string['pos_sales_invoice_id'] = pos_sales_invoice_id;
    post_string['pos_payment_gateway_id'] = $('#pos_payment_gateway_id').val();
    post_string['line'] = 'online';
    post_string['swipe'] = 'swipe';

    card_data = '';
                parsed_card_data = {};
    var url = 'ajax_requests.php';
    $.ajax({
            type: 'POST',
            url: url,
            data: post_string,
            async: true,
            success:    function(response) 
            {
                $('#cc_loading_image').hide();
                console.log(response);
                $('#CC_RESPONSE').show();
                $('#CC_RESPONSE').html(response);
                //here we would update the payment table - currently we will just refresh

                post_string = '';

            }
            });
    post_string = '';
}
else
{
    //error
    alert("Read Error");
    $( "#cc-dialog-form" ).dialog( "close" );
}

here is my code:

1st the listener to get the data.... this data needs validation which i am looking for help on. A good swipe works fine, but a bad swipe will cause an error in the parser.

$('#cc-dialog-form').keypress(function(e) 
{

    var charCode = e.which;
    //ie? evt = e || window.event;
    track_start = '%';
    finished = false;
    timeout = 100;
    track_start_code = track_start.charCodeAt(0);
    //console.log('Track_start_code: ' + track_start_code);

    //console.log('keycode ' + e.keycode);


    //console.log('charcode ' + charCode);
    //console.log('track_start_code ' + track_start_code);
    if (charCode == track_start_code)
    {
        collect_track_data = true;
            $('#offline_cc_entry').hide();
            $('#cc_online').hide();
            $('#Manual_CC_DATA').hide();
            $('#cc_loading_image').show();      

    }
    if (collect_track_data)
    {   
        if (charCode == $.ui.keyCode.ENTER) 
        {
            //all done
            //console.log( card_data);
            collect_track_data = false;
            $('#cc_loading_image').hide();
            $('#Manual_CC_DATA').show();
            //console.log("Track Data: " + card_data);


            process_swipe_cc_payment(card_data);
            card_data = '';

        }
        else
        {
            card_data = card_data + String.fromCharCode(charCode);
            console.log(card_data);
            if (e.preventDefault) e.preventDefault();
            e.returnValue=false;
            return false;
        }
    }
    else
    {
        //i am guessing this will be regular input?
        if (charCode == $.ui.keyCode.ENTER) 
        {
             process_keyed_or_offline_CC_payment();
        }
    }
    //console.log("which: " + e.which);
    //console.log("keyCode: " + e.keyCode);
    //track and collect data here?

});

And here is the parser.... note I put it all in one function so I can destroy all the variables so they are not lingering in a browser.

    parse_data = true;
if (parse_data)
{

var parsed_card_data = {};
parsed_card_data['card_data'] = card_data;
var tracks = card_data.split("?");

//console.log ("tracks");
//console.log (tracks);
parsed_card_data['track1'] = tracks[0];
parsed_card_data['track2'] = tracks[1];
//if there is a third track we might find it under tracks[2]

//splitting the card data OPTION 1

var track1_parsed = tracks[0].split("^");

//console.log (track1_parsed);



//track1 data....
var card_number_track1 = track1_parsed[0].substring(2);


parsed_card_data['card_number_track1'] = card_number_track1;

var details2_1 = tracks[1].split(";");
details2_1 = details2_1[1].split("=");


var exp_date_track_1 = details2_1[1];
exp_date_track_1 = exp_date_track_1.substring(0, exp_date_track_1.length - 1);
exp_date_track_1 = exp_date_track_1.substring(2, 4) + "/" + exp_date_track_1.substring(0,2);
parsed_card_data['exp_track1'] = exp_date_track_1;



//now check if track one matches track 2...

track2_parsed = tracks[1].split("=");


card_number_track_2 = track2_parsed[0].substring(1);



parsed_card_data['card_number_track_2'] = card_number_track_2;
exp_date_track_2 = track2_parsed[1].substring(0,4);
exp_date_track_2 = exp_date_track_2.substring(2, 4) + "/" + exp_date_track_2.substring(0,2);
parsed_card_data['exp_date_track_2'] = exp_date_track_2;


var primary_account_number =  card_number_track1.substring(0,1);


if(card_number_track1 == card_number_track_2 &&  exp_date_track_1 == exp_date_track_2)
{
        //now make a security feature showing the last 4 digits only....
    parsed_card_data['secure_card_number'] = "xxxx " + card_number_track1.substring(card_number_track1.length-4, card_number_track1.length);




    if(card_number_track1.length == 15)
    {
        parsed_card_data['card_type'] = "American Express"; 
    }
    else if(primary_account_number == 4)
    {
        parsed_card_data['card_type'] = "Visa";
    }
    else if(primary_account_number == 5)
    {
        parsed_card_data['card_type'] = "Master Card";
    }
    else if(primary_account_number == 6)
    {
        parsed_card_data['card_type'] = "Discover";
    }
    else
    {
        parsed_card_data['card_type'] = false;
    }

    var names_1 = track1_parsed[1].split("/");
    parsed_card_data['first_name'] = names_1[1].trim();
    parsed_card_data['last_name'] = names_1[0].trim();


    //console.log("return Data");
    //console.log(return_data);

}
else
{
    parsed_card_data = false;
}

    //zero out the variables...

    tracks = '';
    track1_parsed = '';
    card_number_track1 = '';
    details2_1 = '';
    exp_date_track_1 = '';
    track2_parsed = '';
    card_number_track_2 = '';
    exp_date_track_2 = '';
    primary_account_number = '';
}

if(parsed_card_data)
{
    //console.log(parsed_card_data);
    $('#card_type').val(parsed_card_data['card_type']);
    $('#credit_card_number').val(parsed_card_data['secure_card_number']);
    $('#expiration').val(parsed_card_data['exp']);
    $('#card_holder').val(parsed_card_data['first_name']+ " " + parsed_card_data['last_name']);

    //parsed_card_data['track1'] is basically what we want???

    $('#CC_SWIPE_INSTRUCTIONS').hide();
    $('#CC_DATA').hide();
    $('#cc_loading_image').show();



    var post_string = {};
    post_string['ajax_request'] = 'CREDIT_CARD_PAYMENT';
    post_string['amount'] = $('#cc_input').val();
    post_string['card_data'] = parsed_card_data;
    post_string['pos_sales_invoice_id'] = pos_sales_invoice_id;
    post_string['pos_payment_gateway_id'] = $('#pos_payment_gateway_id').val();
    post_string['line'] = 'online';
    post_string['swipe'] = 'swipe';

    card_data = '';
                parsed_card_data = {};
    var url = 'ajax_requests.php';
    $.ajax({
            type: 'POST',
            url: url,
            data: post_string,
            async: true,
            success:    function(response) 
            {
                $('#cc_loading_image').hide();
                console.log(response);
                $('#CC_RESPONSE').show();
                $('#CC_RESPONSE').html(response);
                //here we would update the payment table - currently we will just refresh

                post_string = '';

            }
            });
    post_string = '';
}
else
{
    //error
    alert("Read Error");
    $( "#cc-dialog-form" ).dialog( "close" );
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文