Differentiate null from undefined on Symfony PHP - php

For example, I have the following entity Street:
id: 1
name: Street name
house number: 1
house number addition (nullable): 'A'
And the following graphql mutation:
mutation updateStreet ($input: UpdateStreet!) {
updateStreet(input: $input) {
id
name
houseNumber
houseNumberAddition
}
}
And the input:
{
"input": {
"id": 1,
"name": "Changed Name",
}
}
The problem is with the nullable field ("houseNumberAddition") because it will also be changed to null even if I don't want that.
The question is how can I differentiate between 'null' and 'undefined' in Symfony PHP ?
This is the command:
#[GQL\Input('UpdatePartner')]
final class UpdatePartner
{
#[GQL\Field(type: 'Guid!')]
#[Assert\NotBlank(message: 'Partner guid is required')]
#[Assert\Uuid(message: 'Partner guid is not a valid UUID')]
public Uuid $guid;
#[GQL\Field(type: 'String')]
public ?string $street;
#[GQL\Field(type: 'String')]
#[
Assert\Length(
max: 255,
maxMessage: 'House number exceeds limit of 255 characters'
)
]
public ?string $houseNumber;
#[GQL\Field(type: 'String')]
#[
Assert\Length(
max: 255,
maxMessage: 'House number addition exceeds limit of 255 characters'
)
]
public ?string $houseNumberAddition;
public function guid(): Guid
{
return Guid::fromString($this->guid->toRfc4122());
}
}

You can ignore type like this
#[GQL\Field(type: '?String')] OR [GQL\Field(type: 'null|String')]
#[
Assert\Length(
max: 255,
maxMessage: 'House number addition exceeds limit of 255 characters'
)
]
public ?string $houseNumberAddition;

Related

Error asserting a float in JSON with PHPUnit

Context : I created an app with Symfony and API Platform and I'm writing tests for the API
I have a property name "cost" which is a float in my entity:
#[ApiResource()]
class MyEntity {
...
#[ORM\Column(nullable: true)]
private ?float $cost = null;
..
}
This property is stored as "double precision" in my Postgres DB (which is managed by Doctrine).
This entity as an API endpoint generated by API Platform.
I wrote test to check if the values are correct:
public function testGetSingleMyEntity(): void
{
...
$client->request('GET', '/api/my_entity/'.$myentity->getId());
$this->assertJsonContains([
"cost" => $myentity->getCost()
]);
...
}
But when I run the test, I have this error:
3) App\Tests\Api\MyEntityTest::testGetSingleMyEntity
Failed asserting that an array has the subset Array &0 (
'cost' => 25.0
--- Expected
+++ Actual
## ##
- 'cost' => 25.0,
+ 'cost' => 25,
I tried casting the value of cost with (float) or floatval but I doesn't change anything as it's already a float.
I don't understand if it's a type formatting error from API Platform or because I made an error?
I would really appreciate if someone could tell me what's the issue here.
Thanks
To fix the issue I had to change the type of the property cost to "decimal". It's now stored as a numeric(10,2) into the DB and handled as a string in PHP :
#[ApiResource()]
class MyEntity {
...
#[ORM\Column(type: "decimal", precision: 10, scale: 2, nullable: true)]
private ?string $cost = null;
public function getCost(): ?string
{
return $this->cost;
}
public function setCost(?string $cost): self
{
$this->cost = $cost;
return $this;
}
...
}

Why does formatting variable wont work as indented when being sent to PayPal API with PHP

I am working on a wordpress website of a restoraunt that has Gift Cards bought in form via PayPal API in PHP, I'm not the one that wrote code on rediricting URL but it's only calling API using data sent to it with $_SESSION variable.
class GCS_Card_Action extends \ElementorPro\Modules\Forms\Classes\Action_Base {
public function get_name() {
return "gift_card";
}
public function get_label() {
return __('Gift Card', 'gift-cards-spending');
}
public function run($record, $ajax_handler) {
#session_start();
$_SESSION["giftCardFormData"]=json_encode($_POST);
$form_data = json_decode($_SESSION['giftCardFormData'], true);
$currency_symbol = $form_data['form_fields']['currency'];
if ($currency_symbol == '€') {
$currency = 'EUR';
} elseif ($currency_symbol == '£') {
$currency = 'GBP';
} elseif ($currency_symbol == '$') {
$currency = 'USD';
}
$form_data['form_fields']['currency'] = $currency;
$amount = floatval($form_data['form_fields']['amount']);
$price = abs($amount);
if ($currency != "JPY") {
$price = number_format($price, 2, ".", "");
} else {
$price = number_format($price, 0, ".", "");
}
$paypal_request = [
"intent" => "sale",
"payer" => [
"payment_method" => "paypal"
],
"transactions" => [
[
"amount" => [
"total" => $price,
"currency" => $currency
],
"item_list" => [
"items" => [
[
"name" => $form_data['form_fields']['name'],
"price" => $price,
"currency" => $currency,
"quantity" => 1
]
]
]
]
],
"redirect_urls" => [
"return_url" => "X",
"cancel_url" => "X"
]
];
//$request = array_merge($paypal_request, $form_data);
$request = $paypal_request;
$_SESSION['paypal_request'] = json_encode($request);
$redirUrl="/ig-payment";
$ajax_handler->add_response_data('redirect_url', $redirUrl);
}
public function register_settings_section($widget) {
}
public function on_export($element) {
}
}
This code is ran after submitting the form, sends data to PayPal API that prompts user to login with account and process payment.
I get these three errors
{
"name": "VALIDATION_ERROR",
"message": "Invalid request - see details",
"debug_id": "14a52a47cf143",
"information_link": "https://developer.paypal.com/docs/api/payments/#errors",
"details": [
{
"field": "transactions[0].amount.total",
"location": "body",
"issue": "Currency amount must be non-negative number, contain exactly 2 decimal places separated by '.' (JPY contains 0 decimal places), optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code"
},
{
"field": "transactions[0].item_list.items[0].price",
"location": "body",
"issue": "Currency amount must be non-negative number, contain exactly 2 decimal places separated by '.' (JPY contains 0 decimal places), optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code"
},
{
"field": "transactions[0].amount.currency",
"location": "body",
"issue": "Required field missing"
}
]
}
As you can see transactions[0].amount.currency is there in a structure but it throws Required field missing error
And for transactions[0].amount.total and transactions[0].item_list.items[0].price i get
Currency amount must be non-negative number, contain exactly 2 decimal places separated by '.' (JPY contains 0 decimal places), optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code
Even if i formatted price variable using
if ($currency != "JPY") {
$price = number_format($price, 2, ".", "");
} else {
$price = number_format($price, 0, ".", "");
}
If i try to echo $_SESSION['paypal_request'] out i get this output
{
"intent": "sale",
"payer": { "payment_method": "paypal" },
"transactions": [
{
"amount": { "total": "25.00", "currency": "GBP" },
"item_list": {
"items": [
{
"name": "EMAIL",
"price": "25.00",
"currency": "GBP",
"quantity": 1
}
]
}
}
],
"redirect_urls": {
"return_url": "X",
"cancel_url": "X"
}
}
I tried searching for this error and could only found examples of Vue and React, tried translating it to PHP but it just wont work.
It looks like the price is actually becoming an invalid number like zero for some reason. You can probably find PayPal's logs in the developer dashboard: https://developer.paypal.com/dashboard/dashboard/sandbox
You are using the old v1/payments API, which is deprecated. I would recommend changing it to use the current v2/checkout/orders API.
For payer approval, rather than redirecting away from your site and back you can pair that API with the JS SDK: https://developer.paypal.com/demo/checkout/#/pattern/server
Turns out that code on the receiving page was changed by someone to do everything with data and I didn't need to change symbols or anything, just send $_POST to that page, which was the state of code when I first started working on it.
But also, when someone edited that code, they made $_SESSION read the wrong variable and it only returned an empty array.

Create an object / model out of JSON data, that is not an exact copy of the wanted model-object

How would one in an OOP-way "hydrate", "map", "create" (or whatever it's called) an object or model representation of this JSON data. The JSON string I've included below is the raw data received from Google's AdWords API and is not an exact copy of the structure I want and need, and I may therefor not just cast it to an object and similar.
I've been looking at various mapping libraries and hydration libraries but I cannot seem to wrap my head around how to get it to work properly.
{
"report-name": {
"#name": "Criteria performance report #5b3e67189ac85",
},
"date-range": {
"#date": "Jun 5, 2018-Jul 4, 2018",
},
"table": {
"columns": {
"column": [
{
"#name": "adGroup",
"#display": "Ad group",
},
{
"#name": "impressions",
"#display": "Impressions",
},
{
"#name": "clicks",
"#display": "Clicks",
},
{
"#name": "cost",
"#display": "Cost",
}
]
},
"row": [
{
"#adGroup": "Ad Group 1 of Another Campaign",
"#impressions": 0,
"#clicks": 0,
"#cost": 0,
},
{
"#adGroup": "Ad group 1 of Initial Campaign",
"#impressions": 0,
"#clicks": 0,
"#cost": 0,
}
]
}
}
I would imagine the object or model looking kind of like this.
There are different types of reports, but the structure look exactly the same, just the items are not containing the same attributes. The posted JSON structure represents an "AdGroup", meaning the "rows" would need to get de-serialized into different models. Basically calling "getRowItems" would need to return AdGroup-object's for the posted JSON, but may need to return other types of objects for different types of report data.
class ReportModelExample {
private $reportName;
private $reportDate;
private $reportItems;
public function getReportName()
{
return $this->reportName;
}
public function setReportName($reportName): void
{
$this->reportName = $reportName;
}
public function getReportDate()
{
return $this->reportDate;
}
public function setReportDate($reportDate): void
{
$this->reportDate = $reportDate;
}
public function getReportItems()
{
return $this->reportItems;
}
public function setReportItems($reportItems): void
{
$this->reportItems = $reportItems;
}
}

Creating instance from mySQL data using php

How do I propagate variables of an instance of an object from mySQL data using php?
Here is my object in pseudo code:
exam:{
questions:[
question:{
questionID: string
questionTest: string
categoryID: string
correctAnswerID: string
chosenAnswerID: string
answers:[
answer:{
answerID = string
answerText = string
isTrue = bool
}
answer:{}
]
}
question:{}
]
categoryID: string
}
Here are the corespondant clases (there is a big chance that the syntax is wrong, I am new to php):
class ExamClass
{
// property declaration
public $questions = 'a default value';
public $categoryID = 'a default value';
}
class QuestionClass
{
// property declaration
public $questionID = 'a default value';
public $questionTest = 'a default value';
public $categoryID = 'a default value';
public $correctAnswerID = 'a default value';
public $chosenAnswerID = 'a default value';
public $answers = 'a default value';
}
class AnswersClass
{
// property declaration
public $answerID = 'a default value';
public $answerText = 'a default value';
public $isTrue = 'a default value';
}
And here is the php code that extracts the data from the data base:
<html>
<body>
<?php
/*
exam:{
questions:[
question:{
questionID: string
questionTest: string
categoryID: string
correctAnswerID: string
chosenAnswerID: string
answers:[
answer:{
answerID = string
answerText = string
isTrue = bool
}
answer:{}
]
}
question:{}
]
categoryID: string
}
*/
class ExamClass
{
// property declaration
public $questions = 'a default value';
public $categoryID = 'a default value';
}
class QuestionClass
{
// property declaration
public $questionID = 'a default value';
public $questionTest = 'a default value';
public $categoryID = 'a default value';
public $correctAnswerID = 'a default value';
public $chosenAnswerID = 'a default value';
public $answers = 'a default value';
}
class AnswersClass
{
// property declaration
public $answerID = 'a default value';
public $answerText = 'a default value';
public $isTrue = 'a default value';
}
header('Content-Type: text/html; charset=utf-8');
$con=mysqli_connect("localhost","root","root","Theory");
// Check connection
if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
$result = mysqli_query($con,"SELECT `questions`.`questionID` AS questionID,
`questions`.`questionText` AS questionText,
`questions`.`categoryID` AS categoryID,
`answers`.`answerID` AS answerID,
`answers`.`answerText` AS answerText,
`answers`.`isTrue` AS isTrue
FROM `questions`,`answers`
WHERE `questions`.`questionID` = `answers`.`questionID`
AND `questions`.`categoryID` = 2");
if (!$result)
{
die('Error: ' . mysqli_error($con));
}
$rows = array();
while($r = mysqli_fetch_assoc($result)) {
$rows[] = $r;
}
print json_encode($rows);
mysqli_close($con);
?>
</body>
</head>
And this is how the currently extracted data looks:
[
{
"questionID": "2",
"questionText": "question text 2",
"categoryID": "2",
"answerID": "1",
"answerText": "answer text 1",
"isTrue": "0"
},
{
"questionID": "2",
"questionText": "question text 2",
"categoryID": "2",
"answerID": "2",
"answerText": "answer text 2",
"isTrue": "1"
},
{
"questionID": "2",
"questionText": "question text 2",
"categoryID": "2",
"answerID": "3",
"answerText": "answer text 3",
"isTrue": "0"
},
{
"questionID": "2",
"questionText": "question text 2",
"categoryID": "2",
"answerID": "4",
"answerText": "answer text 4",
"isTrue": "0"
}
]
From your comments, it seems as though you asking how to hydrate these objects with data with the database. Instead of going to from the database, to JSON, into an object, take a look at mysqli_fetch_object.
With mysqli_fetch_object() you can specify a class to initialize with data from the query. In your case:
$result = mysqli_query('SELECT * FROM Answers;');
while ($answer = mysqli_fetch_object($result, 'AnswersClass')) {
var_dump($answer);
}
Note: I answered one of your other questions already today and it seems like you are starting out with some of these things. I would encourage you to read about Data Mapper and Active Record Patterns.
This should give you some ideas on how to map your data source to your classes.
class Exam
{
// property declaration
public $questions = array();
public $categoryID;
}
class Question
{
// property declaration
public $questionID;
public $questionTest;
public $categoryID;
public $correctAnswerID;
public $chosenAnswerID;
public $answers = array();
}
class Answer
{
// property declaration
public $answerID;
public $answerText;
public $isTrue;
}
class ExamBuilder{
public function buildExam($data){
$e = new Exam();
$e->categoryID = $data->categoryID;
$this->addQuestions($data->questions, $e);
return $e;
}
protected function addQuestions ($questions, Exam $exam){
foreach ($questions as $question){
$q = new Question();
$q->questionID = $question->questionID;
$q->questionTest = $question->questionTest;
$q->categoryID = $question->categoryID;
$q->correctAnswerID = $question->correctAnswerID;
$q->chosenAnswerID = $question->chosenAnswerID;
$this->addAnswers($question->answers, $q);
$exam->questions[] = $q;
}
}
protected function addAnswer($answer, Question $question){
$a = new Answer();
$a->answerID = $a->anwerID;
$a->answerText = $answer->answerText;
$a->isTrue = $answer->isTrue;
}
}
To use
$builder = new ExamBuilder();
$exam = $builder->buildExam($data);

Credit Card validation in Zend Framework [duplicate]

I'm trying to figure out how to detect the type of credit card based purely on its number. Does anyone know of a definitive, reliable way to find this?
The credit/debit card number is referred to as a PAN, or Primary Account Number. The first six digits of the PAN are taken from the IIN, or Issuer Identification Number, belonging to the issuing bank (IINs were previously known as BIN — Bank Identification Numbers — so you may see references to that terminology in some documents). These six digits are subject to an international standard, ISO/IEC 7812, and can be used to determine the type of card from the number.
Unfortunately the actual ISO/IEC 7812 database is not publicly available, however, there are unofficial lists, both commercial and free, including on Wikipedia.
Anyway, to detect the type from the number, you can use a regular expression like the ones below: Credit for original expressions
Visa: ^4[0-9]{6,}$ Visa card numbers start with a 4.
MasterCard: ^5[1-5][0-9]{5,}|222[1-9][0-9]{3,}|22[3-9][0-9]{4,}|2[3-6][0-9]{5,}|27[01][0-9]{4,}|2720[0-9]{3,}$ Before 2016, MasterCard numbers start with the numbers 51 through 55, but this will only detect MasterCard credit cards; there are other cards issued using the MasterCard system that do not fall into this IIN range. In 2016, they will add numbers in the range (222100-272099).
American Express: ^3[47][0-9]{5,}$ American Express card numbers start with 34 or 37.
Diners Club: ^3(?:0[0-5]|[68][0-9])[0-9]{4,}$ Diners Club card numbers begin with 300 through 305, 36 or 38. There are Diners Club cards that begin with 5 and have 16 digits. These are a joint venture between Diners Club and MasterCard and should be processed like a MasterCard.
Discover: ^6(?:011|5[0-9]{2})[0-9]{3,}$ Discover card numbers begin with 6011 or 65.
JCB: ^(?:2131|1800|35[0-9]{3})[0-9]{3,}$ JCB cards begin with 2131, 1800 or 35.
Unfortunately, there are a number of card types processed with the MasterCard system that do not live in MasterCard’s IIN range (numbers starting 51...55); the most important case is that of Maestro cards, many of which have been issued from other banks’ IIN ranges and so are located all over the number space. As a result, it may be best to assume that any card that is not of some other type you accept must be a MasterCard.
Important: card numbers do vary in length; for instance, Visa has in the past issued cards with 13 digit PANs and cards with 16 digit PANs. Visa’s documentation currently indicates that it may issue or may have issued numbers with between 12 and 19 digits. Therefore, you should not check the length of the card number, other than to verify that it has at least 7 digits (for a complete IIN plus one check digit, which should match the value predicted by the Luhn algorithm).
One further hint: before processing a cardholder PAN, strip any whitespace and punctuation characters from the input. Why? Because it’s typically much easier to enter the digits in groups, similar to how they’re displayed on the front of an actual credit card, i.e.
4444 4444 4444 4444
is much easier to enter correctly than
4444444444444444
There’s really no benefit in chastising the user because they’ve entered characters you don't expect here.
This also implies making sure that your entry fields have room for at least 24 characters, otherwise users who enter spaces will run out of room. I’d recommend that you make the field wide enough to display 32 characters and allow up to 64; that gives plenty of headroom for expansion.
Here's an image that gives a little more insight:
UPDATE (2016): Mastercard is to implement new BIN ranges starting Ach Payment.
In javascript:
function detectCardType(number) {
var re = {
electron: /^(4026|417500|4405|4508|4844|4913|4917)\d+$/,
maestro: /^(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\d+$/,
dankort: /^(5019)\d+$/,
interpayment: /^(636)\d+$/,
unionpay: /^(62|88)\d+$/,
visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
mastercard: /^5[1-5][0-9]{14}$/,
amex: /^3[47][0-9]{13}$/,
diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
jcb: /^(?:2131|1800|35\d{3})\d{11}$/
}
for(var key in re) {
if(re[key].test(number)) {
return key
}
}
}
Unit test:
describe('CreditCard', function() {
describe('#detectCardType', function() {
var cards = {
'8800000000000000': 'UNIONPAY',
'4026000000000000': 'ELECTRON',
'4175000000000000': 'ELECTRON',
'4405000000000000': 'ELECTRON',
'4508000000000000': 'ELECTRON',
'4844000000000000': 'ELECTRON',
'4913000000000000': 'ELECTRON',
'4917000000000000': 'ELECTRON',
'5019000000000000': 'DANKORT',
'5018000000000000': 'MAESTRO',
'5020000000000000': 'MAESTRO',
'5038000000000000': 'MAESTRO',
'5612000000000000': 'MAESTRO',
'5893000000000000': 'MAESTRO',
'6304000000000000': 'MAESTRO',
'6759000000000000': 'MAESTRO',
'6761000000000000': 'MAESTRO',
'6762000000000000': 'MAESTRO',
'6763000000000000': 'MAESTRO',
'0604000000000000': 'MAESTRO',
'6390000000000000': 'MAESTRO',
'3528000000000000': 'JCB',
'3589000000000000': 'JCB',
'3529000000000000': 'JCB',
'6360000000000000': 'INTERPAYMENT',
'4916338506082832': 'VISA',
'4556015886206505': 'VISA',
'4539048040151731': 'VISA',
'4024007198964305': 'VISA',
'4716175187624512': 'VISA',
'5280934283171080': 'MASTERCARD',
'5456060454627409': 'MASTERCARD',
'5331113404316994': 'MASTERCARD',
'5259474113320034': 'MASTERCARD',
'5442179619690834': 'MASTERCARD',
'6011894492395579': 'DISCOVER',
'6011388644154687': 'DISCOVER',
'6011880085013612': 'DISCOVER',
'6011652795433988': 'DISCOVER',
'6011375973328347': 'DISCOVER',
'345936346788903': 'AMEX',
'377669501013152': 'AMEX',
'373083634595479': 'AMEX',
'370710819865268': 'AMEX',
'371095063560404': 'AMEX'
};
Object.keys(cards).forEach(function(number) {
it('should detect card ' + number + ' as ' + cards[number], function() {
Basket.detectCardType(number).should.equal(cards[number]);
});
});
});
});
Updated: 15th June 2016 (as an ultimate solution currently)
Please note that I even give vote up for the one is top voted, but to make it clear these are the regexps actually works i tested it with thousands of real BIN codes. The most important is to use start strings (^) otherwise it will give false results in real world!
JCB ^(?:2131|1800|35)[0-9]{0,}$ Start with: 2131, 1800, 35 (3528-3589)
American Express ^3[47][0-9]{0,}$ Start with: 34, 37
Diners Club ^3(?:0[0-59]{1}|[689])[0-9]{0,}$ Start with: 300-305, 309, 36, 38-39
Visa ^4[0-9]{0,}$ Start with: 4
MasterCard ^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$ Start with: 2221-2720, 51-55
Maestro ^(5[06789]|6)[0-9]{0,}$ Maestro always growing in the range: 60-69, started with / not something else, but starting 5 must be encoded as mastercard anyway. Maestro cards must be detected in the end of the code because some others has in the range of 60-69. Please look at the code.
Discover ^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$ Discover quite difficult to code, start with: 6011, 622126-622925, 644-649, 65
In javascript I use this function. This is good when u assign it to an onkeyup event and it give result as soon as possible.
function cc_brand_id(cur_val) {
// the regular expressions check for possible matches as you type, hence the OR operators based on the number of chars
// regexp string length {0} provided for soonest detection of beginning of the card numbers this way it could be used for BIN CODE detection also
//JCB
jcb_regex = new RegExp('^(?:2131|1800|35)[0-9]{0,}$'); //2131, 1800, 35 (3528-3589)
// American Express
amex_regex = new RegExp('^3[47][0-9]{0,}$'); //34, 37
// Diners Club
diners_regex = new RegExp('^3(?:0[0-59]{1}|[689])[0-9]{0,}$'); //300-305, 309, 36, 38-39
// Visa
visa_regex = new RegExp('^4[0-9]{0,}$'); //4
// MasterCard
mastercard_regex = new RegExp('^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$'); //2221-2720, 51-55
maestro_regex = new RegExp('^(5[06789]|6)[0-9]{0,}$'); //always growing in the range: 60-69, started with / not something else, but starting 5 must be encoded as mastercard anyway
//Discover
discover_regex = new RegExp('^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$');
////6011, 622126-622925, 644-649, 65
// get rid of anything but numbers
cur_val = cur_val.replace(/\D/g, '');
// checks per each, as their could be multiple hits
//fix: ordering matter in detection, otherwise can give false results in rare cases
var sel_brand = "unknown";
if (cur_val.match(jcb_regex)) {
sel_brand = "jcb";
} else if (cur_val.match(amex_regex)) {
sel_brand = "amex";
} else if (cur_val.match(diners_regex)) {
sel_brand = "diners_club";
} else if (cur_val.match(visa_regex)) {
sel_brand = "visa";
} else if (cur_val.match(mastercard_regex)) {
sel_brand = "mastercard";
} else if (cur_val.match(discover_regex)) {
sel_brand = "discover";
} else if (cur_val.match(maestro_regex)) {
if (cur_val[0] == '5') { //started 5 must be mastercard
sel_brand = "mastercard";
} else {
sel_brand = "maestro"; //maestro is all 60-69 which is not something else, thats why this condition in the end
}
}
return sel_brand;
}
Here you can play with it:
http://jsfiddle.net/upN3L/69/
For PHP use this function, this detects some sub VISA/MC cards too:
/**
* Obtain a brand constant from a PAN
*
* #param string $pan Credit card number
* #param bool $include_sub_types Include detection of sub visa brands
* #return string
*/
public static function getCardBrand($pan, $include_sub_types = false)
{
//maximum length is not fixed now, there are growing number of CCs has more numbers in length, limiting can give false negatives atm
//these regexps accept not whole cc numbers too
//visa
$visa_regex = "/^4[0-9]{0,}$/";
$vpreca_regex = "/^428485[0-9]{0,}$/";
$postepay_regex = "/^(402360|402361|403035|417631|529948){0,}$/";
$cartasi_regex = "/^(432917|432930|453998)[0-9]{0,}$/";
$entropay_regex = "/^(406742|410162|431380|459061|533844|522093)[0-9]{0,}$/";
$o2money_regex = "/^(422793|475743)[0-9]{0,}$/";
// MasterCard
$mastercard_regex = "/^(5[1-5]|222[1-9]|22[3-9]|2[3-6]|27[01]|2720)[0-9]{0,}$/";
$maestro_regex = "/^(5[06789]|6)[0-9]{0,}$/";
$kukuruza_regex = "/^525477[0-9]{0,}$/";
$yunacard_regex = "/^541275[0-9]{0,}$/";
// American Express
$amex_regex = "/^3[47][0-9]{0,}$/";
// Diners Club
$diners_regex = "/^3(?:0[0-59]{1}|[689])[0-9]{0,}$/";
//Discover
$discover_regex = "/^(6011|65|64[4-9]|62212[6-9]|6221[3-9]|622[2-8]|6229[01]|62292[0-5])[0-9]{0,}$/";
//JCB
$jcb_regex = "/^(?:2131|1800|35)[0-9]{0,}$/";
//ordering matter in detection, otherwise can give false results in rare cases
if (preg_match($jcb_regex, $pan)) {
return "jcb";
}
if (preg_match($amex_regex, $pan)) {
return "amex";
}
if (preg_match($diners_regex, $pan)) {
return "diners_club";
}
//sub visa/mastercard cards
if ($include_sub_types) {
if (preg_match($vpreca_regex, $pan)) {
return "v-preca";
}
if (preg_match($postepay_regex, $pan)) {
return "postepay";
}
if (preg_match($cartasi_regex, $pan)) {
return "cartasi";
}
if (preg_match($entropay_regex, $pan)) {
return "entropay";
}
if (preg_match($o2money_regex, $pan)) {
return "o2money";
}
if (preg_match($kukuruza_regex, $pan)) {
return "kukuruza";
}
if (preg_match($yunacard_regex, $pan)) {
return "yunacard";
}
}
if (preg_match($visa_regex, $pan)) {
return "visa";
}
if (preg_match($mastercard_regex, $pan)) {
return "mastercard";
}
if (preg_match($discover_regex, $pan)) {
return "discover";
}
if (preg_match($maestro_regex, $pan)) {
if ($pan[0] == '5') { //started 5 must be mastercard
return "mastercard";
}
return "maestro"; //maestro is all 60-69 which is not something else, thats why this condition in the end
}
return "unknown"; //unknown for this system
}
public string GetCreditCardType(string CreditCardNumber)
{
Regex regVisa = new Regex("^4[0-9]{12}(?:[0-9]{3})?$");
Regex regMaster = new Regex("^5[1-5][0-9]{14}$");
Regex regExpress = new Regex("^3[47][0-9]{13}$");
Regex regDiners = new Regex("^3(?:0[0-5]|[68][0-9])[0-9]{11}$");
Regex regDiscover = new Regex("^6(?:011|5[0-9]{2})[0-9]{12}$");
Regex regJCB = new Regex("^(?:2131|1800|35\\d{3})\\d{11}$");
if (regVisa.IsMatch(CreditCardNumber))
return "VISA";
else if (regMaster.IsMatch(CreditCardNumber))
return "MASTER";
else if (regExpress.IsMatch(CreditCardNumber))
return "AEXPRESS";
else if (regDiners.IsMatch(CreditCardNumber))
return "DINERS";
else if (regDiscover.IsMatch(CreditCardNumber))
return "DISCOVERS";
else if (regJCB.IsMatch(CreditCardNumber))
return "JCB";
else
return "invalid";
}
Here is the function to check Credit card type using Regex , c#
Check this out:
http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256CC70060A01B
function isValidCreditCard(type, ccnum) {
/* Visa: length 16, prefix 4, dashes optional.
Mastercard: length 16, prefix 51-55, dashes optional.
Discover: length 16, prefix 6011, dashes optional.
American Express: length 15, prefix 34 or 37.
Diners: length 14, prefix 30, 36, or 38. */
var re = new Regex({
"visa": "/^4\d{3}-?\d{4}-?\d{4}-?\d",
"mc": "/^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/",
"disc": "/^6011-?\d{4}-?\d{4}-?\d{4}$/",
"amex": "/^3[47]\d{13}$/",
"diners": "/^3[068]\d{12}$/"
}[type.toLowerCase()])
if (!re.test(ccnum)) return false;
// Remove all dashes for the checksum checks to eliminate negative numbers
ccnum = ccnum.split("-").join("");
// Checksum ("Mod 10")
// Add even digits in even length strings or odd digits in odd length strings.
var checksum = 0;
for (var i = (2 - (ccnum.length % 2)); i <= ccnum.length; i += 2) {
checksum += parseInt(ccnum.charAt(i - 1));
}
// Analyze odd digits in even length strings or even digits in odd length strings.
for (var i = (ccnum.length % 2) + 1; i < ccnum.length; i += 2) {
var digit = parseInt(ccnum.charAt(i - 1)) * 2;
if (digit < 10) { checksum += digit; } else { checksum += (digit - 9); }
}
if ((checksum % 10) == 0) return true;
else return false;
}
recently I needed such functionality, I was porting Zend Framework Credit Card Validator to ruby.
ruby gem: https://github.com/Fivell/credit_card_validations
zend framework: https://github.com/zendframework/zf2/blob/master/library/Zend/Validator/CreditCard.php
They both use INN ranges for detecting type. Here you can read about INN
According to this you can detect credit card alternatively (without regexps,but declaring some rules about prefixes and possible length)
So we have next rules for most used cards
######## most used brands #########
visa: [
{length: [13, 16], prefixes: ['4']}
],
mastercard: [
{length: [16], prefixes: ['51', '52', '53', '54', '55']}
],
amex: [
{length: [15], prefixes: ['34', '37']}
],
######## other brands ########
diners: [
{length: [14], prefixes: ['300', '301', '302', '303', '304', '305', '36', '38']},
],
#There are Diners Club (North America) cards that begin with 5. These are a joint venture between Diners Club and MasterCard, and are processed like a MasterCard
# will be removed in next major version
diners_us: [
{length: [16], prefixes: ['54', '55']}
],
discover: [
{length: [16], prefixes: ['6011', '644', '645', '646', '647', '648',
'649', '65']}
],
jcb: [
{length: [16], prefixes: ['3528', '3529', '353', '354', '355', '356', '357', '358', '1800', '2131']}
],
laser: [
{length: [16, 17, 18, 19], prefixes: ['6304', '6706', '6771']}
],
solo: [
{length: [16, 18, 19], prefixes: ['6334', '6767']}
],
switch: [
{length: [16, 18, 19], prefixes: ['633110', '633312', '633304', '633303', '633301', '633300']}
],
maestro: [
{length: [12, 13, 14, 15, 16, 17, 18, 19], prefixes: ['5010', '5011', '5012', '5013', '5014', '5015', '5016', '5017', '5018',
'502', '503', '504', '505', '506', '507', '508',
'6012', '6013', '6014', '6015', '6016', '6017', '6018', '6019',
'602', '603', '604', '605', '6060',
'677', '675', '674', '673', '672', '671', '670',
'6760', '6761', '6762', '6763', '6764', '6765', '6766', '6768', '6769']}
],
# Luhn validation are skipped for union pay cards because they have unknown generation algoritm
unionpay: [
{length: [16, 17, 18, 19], prefixes: ['622', '624', '625', '626', '628'], skip_luhn: true}
],
dankrot: [
{length: [16], prefixes: ['5019']}
],
rupay: [
{length: [16], prefixes: ['6061', '6062', '6063', '6064', '6065', '6066', '6067', '6068', '6069', '607', '608'], skip_luhn: true}
]
}
Then by searching prefix and comparing length you can detect credit card brand. Also don't forget about luhn algoritm (it is descibed here http://en.wikipedia.org/wiki/Luhn).
UPDATE
updated list of rules can be found here https://raw.githubusercontent.com/Fivell/credit_card_validations/master/lib/data/brands.yaml
Here's Complete C# or VB code for all kinds of CC related things on codeproject.
IsValidNumber
GetCardTypeFromNumber
GetCardTestNumber
PassesLuhnTest
This article has been up for a couple years with no negative comments.
Compact javascript version
var getCardType = function (number) {
var cards = {
visa: /^4[0-9]{12}(?:[0-9]{3})?$/,
mastercard: /^5[1-5][0-9]{14}$/,
amex: /^3[47][0-9]{13}$/,
diners: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
discover: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
jcb: /^(?:2131|1800|35\d{3})\d{11}$/
};
for (var card in cards) {
if (cards[card].test(number)) {
return card;
}
}
};
Anatoliy's answer in PHP:
public static function detectCardType($num)
{
$re = array(
"visa" => "/^4[0-9]{12}(?:[0-9]{3})?$/",
"mastercard" => "/^5[1-5][0-9]{14}$/",
"amex" => "/^3[47][0-9]{13}$/",
"discover" => "/^6(?:011|5[0-9]{2})[0-9]{12}$/",
);
if (preg_match($re['visa'],$num))
{
return 'visa';
}
else if (preg_match($re['mastercard'],$num))
{
return 'mastercard';
}
else if (preg_match($re['amex'],$num))
{
return 'amex';
}
else if (preg_match($re['discover'],$num))
{
return 'discover';
}
else
{
return false;
}
}
Here is a php class function returns CCtype by CCnumber.
This code not validates the card or not runs Luhn algorithm only try to find credit card type based on table in this page. basicly uses CCnumber length and CCcard prefix to determine CCcard type.
<?php
class CreditcardType
{
public static $creditcardTypes = [
[
'Name' => 'American Express',
'cardLength' => [15],
'cardPrefix' => ['34', '37'],
], [
'Name' => 'Maestro',
'cardLength' => [12, 13, 14, 15, 16, 17, 18, 19],
'cardPrefix' => ['5018', '5020', '5038', '6304', '6759', '6761', '6763'],
], [
'Name' => 'Mastercard',
'cardLength' => [16],
'cardPrefix' => ['51', '52', '53', '54', '55'],
], [
'Name' => 'Visa',
'cardLength' => [13, 16],
'cardPrefix' => ['4'],
], [
'Name' => 'JCB',
'cardLength' => [16],
'cardPrefix' => ['3528', '3529', '353', '354', '355', '356', '357', '358'],
], [
'Name' => 'Discover',
'cardLength' => [16],
'cardPrefix' => ['6011', '622126', '622127', '622128', '622129', '62213','62214', '62215', '62216', '62217', '62218', '62219','6222', '6223', '6224', '6225', '6226', '6227', '6228','62290', '62291', '622920', '622921', '622922', '622923','622924', '622925', '644', '645', '646', '647', '648','649', '65'],
], [
'Name' => 'Solo',
'cardLength' => [16, 18, 19],
'cardPrefix' => ['6334', '6767'],
], [
'Name' => 'Unionpay',
'cardLength' => [16, 17, 18, 19],
'cardPrefix' => ['622126', '622127', '622128', '622129', '62213', '62214','62215', '62216', '62217', '62218', '62219', '6222', '6223','6224', '6225', '6226', '6227', '6228', '62290', '62291','622920', '622921', '622922', '622923', '622924', '622925'],
], [
'Name' => 'Diners Club',
'cardLength' => [14],
'cardPrefix' => ['300', '301', '302', '303', '304', '305', '36'],
], [
'Name' => 'Diners Club US',
'cardLength' => [16],
'cardPrefix' => ['54', '55'],
], [
'Name' => 'Diners Club Carte Blanche',
'cardLength' => [14],
'cardPrefix' => ['300', '305'],
], [
'Name' => 'Laser',
'cardLength' => [16, 17, 18, 19],
'cardPrefix' => ['6304', '6706', '6771', '6709'],
],
];
public static function getType($CCNumber)
{
$CCNumber = trim($CCNumber);
$type = 'Unknown';
foreach (CreditcardType::$creditcardTypes as $card) {
if (! in_array(strlen($CCNumber), $card['cardLength'])) {
continue;
}
$prefixes = '/^(' . implode('|', $card['cardPrefix']) . ')/';
if (preg_match($prefixes, $CCNumber) == 1) {
$type = $card['Name'];
break;
}
}
return $type;
}
}
The first numbers of the credit card can be used to approximate the vendor:
Visa: 49,44 or 47
Visa electron: 42, 45, 48, 49
MasterCard: 51
Amex:34
Diners: 30, 36, 38
JCB: 35
In Card Range Recognition (CRR), a drawback with algorithms that use a series of regex or other hard-coded ranges, is that the BINs/IINs do change over time in my experience. The co-branding of cards is an ongoing complication. Different Card Acquirers / merchants may need you treat the same card differently, depending on e.g. geolocation.
Additionally, in the last few years with e.g. UnionPay cards in wider circulation, existing models do not cope with new ranges that sometimes interleave with broader ranges that they supersede.
Knowing the geography your system needs to cover may help, as some ranges are restricted to use in particular countries. For example, ranges 62 include some AAA sub-ranges in the US, but if your merchant base is outside the US, you may be able to treat all 62 as UnionPay.
You may be also asked to treat a card differently based on merchant location. E.g. to treat certain UK cards as debit domestically, but as credit internationally.
There are very useful set of rules maintained by one major Acquiring Bank. E.g. https://www.barclaycard.co.uk/business/files/BIN-Rules-EIRE.pdf and https://www.barclaycard.co.uk/business/files/BIN-Rules-UK.pdf. (Valid links as of June 2017, thanks to the user who provided a link to updated reference.) But be aware of the caveat that, while these CRR rules may represent the Card Issuing universe as it applies to the merchants acquired by that entity, it does not include e.g. ranges identified as CUP/UPI.
These comments apply to magnetic stripe (MagStripe) or PKE (Pan Key Entry) scenarios. The situation is different again in the ICC/EMV world.
Update: Other answers on this page (and also the linked WikiPedia page) have JCB as always 16 long. However, in my company we have a dedicated team of engineers who certify our POS devices and software across multiple acquiring banks and geographies. The most recent Certification Pack of cards this team have from JCB, had a pass case for a 19 long PAN.
Do not try to detect credit card type as part of processing a payment. You are risking of declining valid transactions.
If you need to provide information to your payment processor (e.g. PayPal credit card object requires to name the card type), then guess it from the least information available, e.g.
$credit_card['pan'] = preg_replace('/[^0-9]/', '', $credit_card['pan']);
$inn = (int) mb_substr($credit_card['pan'], 0, 2);
// #see http://en.wikipedia.org/wiki/List_of_Bank_Identification_Numbers#Overview
if ($inn >= 40 && $inn <= 49) {
$type = 'visa';
} else if ($inn >= 51 && $inn <= 55) {
$type = 'mastercard';
} else if ($inn >= 60 && $inn <= 65) {
$type = 'discover';
} else if ($inn >= 34 && $inn <= 37) {
$type = 'amex';
} else {
throw new \UnexpectedValueException('Unsupported card type.');
}
This implementation (using only the first two digits) is enough to identify all of the major (and in PayPal's case all of the supported) card schemes. In fact, you might want to skip the exception altogether and default to the most popular card type. Let the payment gateway/processor tell you if there is a validation error in response to your request.
The reality is that your payment gateway does not care about the value you provide.
Swift 2.1 Version of Usman Y's answer.
Use a print statement to verify so call by some string value
print(self.validateCardType(self.creditCardField.text!))
func validateCardType(testCard: String) -> String {
let regVisa = "^4[0-9]{12}(?:[0-9]{3})?$"
let regMaster = "^5[1-5][0-9]{14}$"
let regExpress = "^3[47][0-9]{13}$"
let regDiners = "^3(?:0[0-5]|[68][0-9])[0-9]{11}$"
let regDiscover = "^6(?:011|5[0-9]{2})[0-9]{12}$"
let regJCB = "^(?:2131|1800|35\\d{3})\\d{11}$"
let regVisaTest = NSPredicate(format: "SELF MATCHES %#", regVisa)
let regMasterTest = NSPredicate(format: "SELF MATCHES %#", regMaster)
let regExpressTest = NSPredicate(format: "SELF MATCHES %#", regExpress)
let regDinersTest = NSPredicate(format: "SELF MATCHES %#", regDiners)
let regDiscoverTest = NSPredicate(format: "SELF MATCHES %#", regDiscover)
let regJCBTest = NSPredicate(format: "SELF MATCHES %#", regJCB)
if regVisaTest.evaluateWithObject(testCard){
return "Visa"
}
else if regMasterTest.evaluateWithObject(testCard){
return "MasterCard"
}
else if regExpressTest.evaluateWithObject(testCard){
return "American Express"
}
else if regDinersTest.evaluateWithObject(testCard){
return "Diners Club"
}
else if regDiscoverTest.evaluateWithObject(testCard){
return "Discover"
}
else if regJCBTest.evaluateWithObject(testCard){
return "JCB"
}
return ""
}
Stripe has provided this fantastic javascript library for card scheme detection. Let me add few code snippets and show you how to use it.
Firstly Include it to your web page as
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.payment/1.2.3/jquery.payment.js " ></script>
Secondly use the function cardType for detecting the card scheme.
$(document).ready(function() {
var type = $.payment.cardType("4242 4242 4242 4242"); //test card number
console.log(type);
});
Here are the reference links for more examples and demos.
Stripe blog for jquery.payment.js
Github repository
In swift you can create an enum to detect the credit card type.
enum CreditCardType: Int { // Enum which encapsulates different card types and method to find the type of card.
case Visa
case Master
case Amex
case Discover
func validationRegex() -> String {
var regex = ""
switch self {
case .Visa:
regex = "^4[0-9]{6,}$"
case .Master:
regex = "^5[1-5][0-9]{5,}$"
case .Amex:
regex = "^3[47][0-9]{13}$"
case .Discover:
regex = "^6(?:011|5[0-9]{2})[0-9]{12}$"
}
return regex
}
func validate(cardNumber: String) -> Bool {
let predicate = NSPredicate(format: "SELF MATCHES %#", validationRegex())
return predicate.evaluateWithObject(cardNumber)
}
// Method returns the credit card type for given card number
static func cardTypeForCreditCardNumber(cardNumber: String) -> CreditCardType? {
var creditCardType: CreditCardType?
var index = 0
while let cardType = CreditCardType(rawValue: index) {
if cardType.validate(cardNumber) {
creditCardType = cardType
break
} else {
index++
}
}
return creditCardType
}
}
Call the method CreditCardType.cardTypeForCreditCardNumber("#card number") which returns CreditCardType enum value.
My solution with jQuery:
function detectCreditCardType() {
var type = new Array;
type[1] = '^4[0-9]{12}(?:[0-9]{3})?$'; // visa
type[2] = '^5[1-5][0-9]{14}$'; // mastercard
type[3] = '^6(?:011|5[0-9]{2})[0-9]{12}$'; // discover
type[4] = '^3[47][0-9]{13}$'; // amex
var ccnum = $('.creditcard').val().replace(/[^\d.]/g, '');
var returntype = 0;
$.each(type, function(idx, re) {
var regex = new RegExp(re);
if(regex.test(ccnum) && idx>0) {
returntype = idx;
}
});
return returntype;
}
In case 0 is returned, credit card type is undetected.
"creditcard" class should be added to the credit card input field.
I searched around quite a bit for credit card formatting and phone number formatting. Found lots of good tips but nothing really suited my exact desires so I created this bit of code. You use it like this:
var sf = smartForm.formatCC(myInputString);
var cardType = sf.cardType;
A javascript improve of #Anatoliy answer
function getCardType (number) {
const numberFormated = number.replace(/\D/g, '')
var patterns = {
VISA: /^4[0-9]{12}(?:[0-9]{3})?$/,
MASTER: /^5[1-5][0-9]{14}$/,
AMEX: /^3[47][0-9]{13}$/,
ELO: /^((((636368)|(438935)|(504175)|(451416)|(636297))\d{0,10})|((5067)|(4576)|(4011))\d{0,12})$/,
AURA: /^(5078\d{2})(\d{2})(\d{11})$/,
JCB: /^(?:2131|1800|35\d{3})\d{11}$/,
DINERS: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/,
DISCOVERY: /^6(?:011|5[0-9]{2})[0-9]{12}$/,
HIPERCARD: /^(606282\d{10}(\d{3})?)|(3841\d{15})$/,
ELECTRON: /^(4026|417500|4405|4508|4844|4913|4917)\d+$/,
MAESTRO: /^(5018|5020|5038|5612|5893|6304|6759|6761|6762|6763|0604|6390)\d+$/,
DANKORT: /^(5019)\d+$/,
INTERPAYMENT: /^(636)\d+$/,
UNIONPAY: /^(62|88)\d+$/,
}
for (var key in patterns) {
if (patterns[key].test(numberFormated)) {
return key
}
}
}
console.log(getCardType("4539 5684 7526 2091"))
Swift 5+
extension String {
func isMatch(_ Regex: String) -> Bool {
do {
let regex = try NSRegularExpression(pattern: Regex)
let results = regex.matches(in: self, range: NSRange(self.startIndex..., in: self))
return results.map {
String(self[Range($0.range, in: self)!])
}.count > 0
} catch {
return false
}
}
func getCreditCardType() -> String? {
let VISA_Regex = "^4[0-9]{6,}$"
let MasterCard_Regex = "^5[1-5][0-9]{5,}|222[1-9][0-9]{3,}|22[3-9][0-9]{4,}|2[3-6][0-9]{5,}|27[01][0-9]{4,}|2720[0-9]{3,}$"
let AmericanExpress_Regex = "^3[47][0-9]{5,}$"
let DinersClub_Regex = "^3(?:0[0-5]|[68][0-9])[0-9]{4,}$"
let Discover_Regex = "^6(?:011|5[0-9]{2})[0-9]{3,}$"
let JCB_Regex = "^(?:2131|1800|35[0-9]{3})[0-9]{3,}$"
if self.isMatch(VISA_Regex) {
return "VISA"
} else if self.isMatch(MasterCard_Regex) {
return "MasterCard"
} else if self.isMatch(AmericanExpress_Regex) {
return "AmericanExpress"
} else if self.isMatch(DinersClub_Regex) {
return "DinersClub"
} else if self.isMatch(Discover_Regex) {
return "Discover"
} else if self.isMatch(JCB_Regex) {
return "JCB"
} else {
return nil
}
}
}
Use.
"1234123412341234".getCreditCardType()
// abobjects.com, parvez ahmad ab bulk mailer
use below script
function isValidCreditCard2(type, ccnum) {
if (type == "Visa") {
// Visa: length 16, prefix 4, dashes optional.
var re = /^4\d{3}?\d{4}?\d{4}?\d{4}$/;
} else if (type == "MasterCard") {
// Mastercard: length 16, prefix 51-55, dashes optional.
var re = /^5[1-5]\d{2}?\d{4}?\d{4}?\d{4}$/;
} else if (type == "Discover") {
// Discover: length 16, prefix 6011, dashes optional.
var re = /^6011?\d{4}?\d{4}?\d{4}$/;
} else if (type == "AmEx") {
// American Express: length 15, prefix 34 or 37.
var re = /^3[4,7]\d{13}$/;
} else if (type == "Diners") {
// Diners: length 14, prefix 30, 36, or 38.
var re = /^3[0,6,8]\d{12}$/;
}
if (!re.test(ccnum)) return false;
return true;
/*
// Remove all dashes for the checksum checks to eliminate negative numbers
ccnum = ccnum.split("-").join("");
// Checksum ("Mod 10")
// Add even digits in even length strings or odd digits in odd length strings.
var checksum = 0;
for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) {
checksum += parseInt(ccnum.charAt(i-1));
}
// Analyze odd digits in even length strings or even digits in odd length strings.
for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) {
var digit = parseInt(ccnum.charAt(i-1)) * 2;
if (digit < 10) { checksum += digit; } else { checksum += (digit-9); }
}
if ((checksum % 10) == 0) return true; else return false;
*/
}
jQuery.validator.addMethod("isValidCreditCard", function(postalcode, element) {
return isValidCreditCard2($("#cardType").val(), $("#cardNum").val());
}, "<br>credit card is invalid");
Type</td>
<td class="text"> <form:select path="cardType" cssclass="fields" style="border: 1px solid #D5D5D5;padding: 0px 0px 0px 0px;width: 130px;height: 22px;">
<option value="SELECT">SELECT</option>
<option value="MasterCard">Mastercard</option>
<option value="Visa">Visa</option>
<option value="AmEx">American Express</option>
<option value="Discover">Discover</option>
</form:select> <font color="#FF0000">*</font>
$("#signupForm").validate({
rules:{
companyName:{required: true},
address1:{required: true},
city:{required: true},
state:{required: true},
zip:{required: true},
country:{required: true},
chkAgree:{required: true},
confPassword:{required: true},
lastName:{required: true},
firstName:{required: true},
ccAddress1:{required: true},
ccZip:{
postalcode : true
},
phone:{required: true},
email:{
required: true,
email: true
},
userName:{
required: true,
minlength: 6
},
password:{
required: true,
minlength: 6
},
cardNum:{
isValidCreditCard : true
},
Just a little spoon feeding:
$("#CreditCardNumber").focusout(function () {
var regVisa = /^4[0-9]{12}(?:[0-9]{3})?$/;
var regMasterCard = /^5[1-5][0-9]{14}$/;
var regAmex = /^3[47][0-9]{13}$/;
var regDiscover = /^6(?:011|5[0-9]{2})[0-9]{12}$/;
if (regVisa.test($(this).val())) {
$("#CCImage").html("<img height='40px' src='#Url.Content("~/images/visa.png")'>");
}
else if (regMasterCard.test($(this).val())) {
$("#CCImage").html("<img height='40px' src='#Url.Content("~/images/mastercard.png")'>");
}
else if (regAmex.test($(this).val())) {
$("#CCImage").html("<img height='40px' src='#Url.Content("~/images/amex.png")'>");
}
else if (regDiscover.test($(this).val())) {
$("#CCImage").html("<img height='40px' src='#Url.Content("~/images/discover.png")'>");
}
else {
$("#CCImage").html("NA");
}
});
Here is an example of some boolean functions written in Python that return True if the card is detected as per the function name.
def is_american_express(cc_number):
"""Checks if the card is an american express. If us billing address country code, & is_amex, use vpos
https://en.wikipedia.org/wiki/Bank_card_number#cite_note-GenCardFeatures-3
:param cc_number: unicode card number
"""
return bool(re.match(r'^3[47][0-9]{13}$', cc_number))
def is_visa(cc_number):
"""Checks if the card is a visa, begins with 4 and 12 or 15 additional digits.
:param cc_number: unicode card number
"""
# Standard Visa is 13 or 16, debit can be 19
if bool(re.match(r'^4', cc_number)) and len(cc_number) in [13, 16, 19]:
return True
return False
def is_mastercard(cc_number):
"""Checks if the card is a mastercard. Begins with 51-55 or 2221-2720 and 16 in length.
:param cc_number: unicode card number
"""
if len(cc_number) == 16 and cc_number.isdigit(): # Check digit, before cast to int
return bool(re.match(r'^5[1-5]', cc_number)) or int(cc_number[:4]) in range(2221, 2721)
return False
def is_discover(cc_number):
"""Checks if the card is discover, re would be too hard to maintain. Not a supported card.
:param cc_number: unicode card number
"""
if len(cc_number) == 16:
try:
# return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or cc_number[:6] in range(622126, 622926))
return bool(cc_number[:4] == '6011' or cc_number[:2] == '65' or 622126 <= int(cc_number[:6]) <= 622925)
except ValueError:
return False
return False
def is_jcb(cc_number):
"""Checks if the card is a jcb. Not a supported card.
:param cc_number: unicode card number
"""
# return bool(re.match(r'^(?:2131|1800|35\d{3})\d{11}$', cc_number)) # wikipedia
return bool(re.match(r'^35(2[89]|[3-8][0-9])[0-9]{12}$', cc_number)) # PawelDecowski
def is_diners_club(cc_number):
"""Checks if the card is a diners club. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^3(?:0[0-6]|[68][0-9])[0-9]{11}$', cc_number)) # 0-5 = carte blance, 6 = international
def is_laser(cc_number):
"""Checks if the card is laser. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^(6304|670[69]|6771)', cc_number))
def is_maestro(cc_number):
"""Checks if the card is maestro. Not a supported card.
:param cc_number: unicode card number
"""
possible_lengths = [12, 13, 14, 15, 16, 17, 18, 19]
return bool(re.match(r'^(50|5[6-9]|6[0-9])', cc_number)) and len(cc_number) in possible_lengths
# Child cards
def is_visa_electron(cc_number):
"""Child of visa. Checks if the card is a visa electron. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^(4026|417500|4508|4844|491(3|7))', cc_number)) and len(cc_number) == 16
def is_total_rewards_visa(cc_number):
"""Child of visa. Checks if the card is a Total Rewards Visa. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^41277777[0-9]{8}$', cc_number))
def is_diners_club_carte_blanche(cc_number):
"""Child card of diners. Checks if the card is a diners club carte blance. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^30[0-5][0-9]{11}$', cc_number)) # github PawelDecowski, jquery-creditcardvalidator
def is_diners_club_carte_international(cc_number):
"""Child card of diners. Checks if the card is a diners club international. Not a supported card.
:param cc_number: unicode card number
"""
return bool(re.match(r'^36[0-9]{12}$', cc_number)) # jquery-creditcardvalidator
The first six digits of a card number (including the initial MII
digit) are known as the issuer identification number (IIN). These
identify the card issuing institution that issued the card to the card
holder. The rest of the number is allocated by the card issuer. The
card number's length is its number of digits. Many card issuers print
the entire IIN and account number on their card.
Based on the above facts I would like to keep a snippet of JAVA code to identify card brand.
Sample card types
public static final String AMERICAN_EXPRESS = "American Express";
public static final String DISCOVER = "Discover";
public static final String JCB = "JCB";
public static final String DINERS_CLUB = "Diners Club";
public static final String VISA = "Visa";
public static final String MASTERCARD = "MasterCard";
public static final String UNKNOWN = "Unknown";
Card Prefixes
// Based on http://en.wikipedia.org/wiki/Bank_card_number#Issuer_identification_number_.28IIN.29
public static final String[] PREFIXES_AMERICAN_EXPRESS = {"34", "37"};
public static final String[] PREFIXES_DISCOVER = {"60", "62", "64", "65"};
public static final String[] PREFIXES_JCB = {"35"};
public static final String[] PREFIXES_DINERS_CLUB = {"300", "301", "302", "303", "304", "305", "309", "36", "38", "39"};
public static final String[] PREFIXES_VISA = {"4"};
public static final String[] PREFIXES_MASTERCARD = {
"2221", "2222", "2223", "2224", "2225", "2226", "2227", "2228", "2229",
"223", "224", "225", "226", "227", "228", "229",
"23", "24", "25", "26",
"270", "271", "2720",
"50", "51", "52", "53", "54", "55"
};
Check to see if the input number has any of the given prefixes.
public String getBrand(String number) {
String evaluatedType;
if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_AMERICAN_EXPRESS)) {
evaluatedType = AMERICAN_EXPRESS;
} else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_DISCOVER)) {
evaluatedType = DISCOVER;
} else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_JCB)) {
evaluatedType = JCB;
} else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_DINERS_CLUB)) {
evaluatedType = DINERS_CLUB;
} else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_VISA)) {
evaluatedType = VISA;
} else if (StripeTextUtils.hasAnyPrefix(number, PREFIXES_MASTERCARD)) {
evaluatedType = MASTERCARD;
} else {
evaluatedType = UNKNOWN;
}
return evaluatedType;
}
Finally, The Utility method
/**
* Check to see if the input number has any of the given prefixes.
*
* #param number the number to test
* #param prefixes the prefixes to test against
* #return {#code true} if number begins with any of the input prefixes
*/
public static boolean hasAnyPrefix(String number, String... prefixes) {
if (number == null) {
return false;
}
for (String prefix : prefixes) {
if (number.startsWith(prefix)) {
return true;
}
}
return false;
}
Reference
Stripe Card Builder
Try this for kotlin. Add Regex and add to the when statement.
private fun getCardType(number: String): String {
val visa = Regex("^4[0-9]{12}(?:[0-9]{3})?$")
val mastercard = Regex("^5[1-5][0-9]{14}$")
val amx = Regex("^3[47][0-9]{13}$")
return when {
visa.matches(number) -> "Visa"
mastercard.matches(number) -> "Mastercard"
amx.matches(number) -> "American Express"
else -> "Unknown"
}
}
The regular expression rules that match the respective card vendors:
(4\d{12}(?:\d{3})?) for VISA.
(5[1-5]\d{14}) for MasterCard.
(3[47]\d{13}) for AMEX.
((?:5020|5038|6304|6579|6761)\d{12}(?:\d\d)?) for Maestro.
(3(?:0[0-5]|[68][0-9])[0-9]{11}) for Diners Club.
(6(?:011|5[0-9]{2})[0-9]{12}) for Discover.
(35[2-8][89]\d\d\d{10}) for JCB.
follow Luhn’s algorithm
private boolean validateCreditCardNumber(String str) {
int[] ints = new int[str.length()];
for (int i = 0; i < str.length(); i++) {
ints[i] = Integer.parseInt(str.substring(i, i + 1));
}
for (int i = ints.length - 2; i >= 0; i = i - 2) {
int j = ints[i];
j = j * 2;
if (j > 9) {
j = j % 10 + 1;
}
ints[i] = j;
}
int sum = 0;
for (int i = 0; i < ints.length; i++) {
sum += ints[i];
}
if (sum % 10 == 0) {
return true;
} else {
return false;
}
}
then call this method
Edittext mCreditCardNumberEt;
mCreditCardNumberEt.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int cardcount= s.toString().length();
if(cardcount>=16) {
boolean cardnumbervalid= validateCreditCardNumber(s.toString());
if(cardnumbervalid) {
cardvalidtesting.setText("Valid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.green));
}
else {
cardvalidtesting.setText("Invalid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.red));
}
}
else if(cardcount>0 &&cardcount<16) {
cardvalidtesting.setText("Invalid Card");
cardvalidtesting.setTextColor(ContextCompat.getColor(context,R.color.red));
}
else {
cardvalidtesting.setText("");
}
}
#Override
public void afterTextChanged(Editable s) {
}
});
Another api solution at rapidapi Bank Card Bin Num Check there are 250K+ issued card type.
Only one GET rest api request and get card issuer info like:
{ "bin_number": 535177, "bank": "Finansbank A.S.", "scheme": "MASTERCARD", "type": "Debit", "country": "Turkey" }

Categories