有人知道如何解析磁条卡读卡器输入的信用卡字符串吗?
我尝试了一个JavaScript解析器,但从未使其正常工作。以下是输入的样子:
%BNNNNNNNNNNNNNNNN^DOE/JOHN
^1210201901000101000100061000000?;NNNNNNNNNNNNNNNN=12102019010106111001?
N代表信用卡号码。
有人知道如何解析磁条卡读卡器输入的信用卡字符串吗?
我尝试了一个JavaScript解析器,但从未使其正常工作。以下是输入的样子:
%BNNNNNNNNNNNNNNNN^DOE/JOHN
^1210201901000101000100061000000?;NNNNNNNNNNNNNNNN=12102019010106111001?
N代表信用卡号码。
Track one,格式B:
- 起始哨兵——一个字符(通常为“%”)
- 格式代码="B"——一个字符(仅为字母)
- 主帐号(PAN)——最多19个字符。通常与信用卡正面印刷的卡号相匹配,但不总是
- 字段分隔符——一个字符(通常为“^”)
- 姓名——两到26个字符
- 字段分隔符——一个字符(通常为“^”)
- 过期日期——YYMM格式的4个字符
- 服务代码——三个字符
- 自由数据——可能包括Pin Verification Key Indicator(PVKI,1个字符)、PIN Verification Value(PVV,4个字符)、Card Verification Value或Card Verification Code(CVV或CVK,3个字符)
- 结束哨兵——一个字符(通常为“?”)
- 纵向冗余校验(LRC)——一个字符(大多数读卡器设备在刷卡到表示层时不返回此值,并且仅在内部验证输入时使用它)
我希望数据是假的,否则任何人都可以获取:
我不确定但我认为信用卡号码(或可能性的#)可以使用LRC计算。
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);
}
}
据我所记:
磁条数据有两个轨道-第一轨以%
开头,以?
结尾,第二轨以;
开头,以?
结尾。这些是起始/结束标记。
第一轨是字母数字混合的,第二轨是数字的,还有第三轨也是数字的(如果我的记忆正确的话)。
起始/结束标记之间的数据可以根据磁条的记录密度而变化。密度越高,则可以在一个轨道上记录更多的数据。
使用正则表达式来获取数据可能不是一种可靠的方法来提取所需的信息。
并非所有的信用卡都只有两个轨道,有些使用了三个轨道。
这是我的代码:
首先监听获取数据... 这些数据需要验证,我正在寻求帮助。良好的滑动效果很好,但是不良的滑动会导致解析器出错。
$('#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" );
}
只有track2用于处理,并且具有标准化格式。并非所有信用卡都有两个轨道,有些使用三个轨道。
借记卡通常无法处理(除非它们具有 Visa 借记卡或类似卡)。
P.S. 不应以明文形式存储信用卡数据,因此请尽量将所有内容保存在内存中或进行强加密。