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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2121881/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-22 22:50:00  来源:igfitidea点击:

Parse Credit Card input from Magnetic Stripe

asp.netjavascriptparsingcredit-card

提问by Chase Florell

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.

我尝试了一个 JavaScript 解析器,但从来没有让它工作。这就是输入的样子。

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

The N's are the credit card number.

N 是信用卡号。

回答by Alix Axel

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.)

曲目一,格式 B:

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


I hope the data is fake, otherwiseAnyone 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.

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

回答by Mark Hagan

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

我做得更好:我制作了一个视频,展示了如何使用 ASP.Net/c# 做到这一点:

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

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.

完整的代码在我上面链接的那个网站上。

回答by t0mm13b

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.

并非所有信用卡都恰好有两条轨道,有些使用三个轨道。

回答by Kakkerlakkie

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.

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

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.

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

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.

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

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.

只有 track2 用于处理并具有标准化格式。

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.

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

回答by Iannazzi

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" );
}