从磁条解析信用卡输入

Posted

技术标签:

【中文标题】从磁条解析信用卡输入【英文标题】:Parse Credit Card input from Magnetic Stripe 【发布时间】:2011-01-08 11:26:41 【问题描述】:

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

我尝试了一个 javascript 解析器,但始终无法正常工作。这就是输入的样子。

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

N 是信用卡号。

【问题讨论】:

也许他想进入信用卡终端业务? 我的妻子是一名摄影师,我是一名开发人员。我们刚刚注册了 PayPal Website Payments Pro,并希望能够在路上处理 CC(贸易展览等)。 我确实找到了一个用于 FireFox 的 GreaseMonkey 插件,它允许我在 PayPal 虚拟终端上向右滑动。虽然不理想,但如果我无法解决这个问题,它会起作用。 【参考方案1】:

见Magnetic Stripe Card entry @ Wikipedia:


曲目一,格式 B:

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

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

姓名 有效期 CVV

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

【讨论】:

如果您正在写入磁条阅读器,并且使用原始写入模式,那么计算 LRC 算法可能会有点混乱,您必须自己进行,如果您使用正常写入模式,刷卡机将为您处理。但是读取时,刷机会自动计算LRC来验证刷机是否成功。 CVV 未显示在曲目中 -- 是否可计算? CVV 不在曲目中,这是故意的。它为网络所知,并用于验证交易发生时卡是否存在。【参考方案2】:

我做得更好:我制作了一个视频,展示了如何使用 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);
        
    

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

【讨论】:

【参考方案3】:

据我所知:

这是一个双磁道磁条数据——第一磁道以%开始,以?结束,第二磁道以;开始,以?结束。这些是开始/结束标记。

第一首曲目是字母数字,第二首曲目是数字,第三首曲目也是数字(如果我没记错的话)。

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

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

并不是所有的信用卡都有两个轨道,有些使用三个轨道。

【讨论】:

【参考方案4】:

一般而言,对于无卡交易(即 MOTO 交易),您需要 cc#、到期时间以及可能的 CVV(又名 CVC2 等)。您可以通过刷卡获得前 2 个,就像在轨道数据中一样。 CVV 印在卡片上。

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

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

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

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

并不是所有的信用卡都有两个轨道,有些使用三个轨道。

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

借记卡通常不能被处理(除非他们有签证借记卡或其他东西)。

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

【讨论】:

【参考方案5】:

试试这个: https://github.com/pdamer/CardReader/blob/master/CardReader.js 或这个: http://blog.cnizz.com/2008/10/16/javascript-snippet-for-handling-credit-card-readers/

我认为你需要什么

【讨论】:

第一个链接是我到处寻找的。它似乎有跟踪数据验证,甚至超时。幸运或不幸的是,这是一段令人印象深刻的代码,这完全超出了我的实现能力。您能否添加一个有关如何使用 CardReader 功能的示例?在我将作为单独帖子添加的轨道阅读代码中,我有一个在 CardReader 中看不到的监听器。 刚刚在下面发布了我的轨道阅读器和解析器代码...需要集成 CardReader 验证【参考方案6】:

这是我的代码:

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

$('#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" );

【讨论】:

我添加了 if(card_data.indexOf('=') === -1 || card_data.indexOf('^')===-1) 来验证刷卡数据,这样就摆脱了刷卡不好,但这与 auth.net 请求的纵向冗余检查并不完全相同:“必须为从 Track 读取的数据计算纵向冗余检查 (LRC),并与从 Track 读取的 LRC 进行比较Track. 当没有检测到字符奇偶校验错误并且计算和读取的 LRC 匹配时,假定读取 Track 数据没有错误。 - 卡片礼物指南

以上是关于从磁条解析信用卡输入的主要内容,如果未能解决你的问题,请参考以下文章

Android:如何读取磁条(信用卡等)数据[关闭]

javascript 非常简单的jQuery实现,可从USB磁条信用卡读卡器读取卡数据。

信用卡号验证 用c++如何解决?

检测损坏的信用卡跟踪数据

招商银行信用卡可靠吗

现代信用卡处理系统是不是允许在持卡人姓名中使用变音符号?