[JavaScript] クレジットカード番号が正しいか検証する

決済画面などで入力されたクレジットカード番号が正しい物であるか検証するコードを書いてみます。

npmでモジュールを探すとcard-validatorなどいくつか見つかるので、実際にはこれらを使うのが楽ちんですね。メンテナンスされていればカード番号の仕様が変わったときもアップデートされることが期待できますし。

ここでは興味本位でスクラッチで書いてみたいと思いますw

クレジットカード番号の仕様

開始番号と桁数

カード番号の冒頭数桁でカード会社を判定できるようになっています。例えば「4」でカード番号が始まっていればVISAであることがわかります。またカード会社毎に桁数は異なりますが13〜16桁の整数である部分は共通です。

国際ブランド 開始番号 桁数 セキュリティコード
VISA 4 13, 16 CVV(3桁)
MASTER Card 510000〜559999, 222100〜272099 16 CVC(3桁)
JCB 3528〜3589 16 CVV(3桁)
American Express 34, 37 15 CID(4桁)
Diners Club 300〜303574, 3095, 36, 38〜39 14 CVV(3桁)
  • この情報は主にWikipediaを参考にしています。誤りや古くなっている箇所などあればご指摘ください。

チェックサム

クレジットカード番号の大半はLuhn(ルーン)アルゴリズムが用いられており、完璧ではありませんがある程度の入力ミスを検出することができます。

具体的なロジックは以下の通り。

  1. カード番号を「逆順」にする 41111111111111111111111111111114
  2. 偶数番目の桁を2倍にする 11111111111111141212121212121218
    • 2倍にした結果2桁になった場合は1桁目と2桁目を足す。例えば8 * 2 = 16となった場合は1 + 6 = 7とする
  3. すべての桁を合算する 1+2+1+2+1+2+1+2+1+2+1+2+1+2+1+8 = 30
  4. 合算した値が 10 で割り切れればカード番号は正しい 30 % 10 = 0

ちなみに2倍にした結果2桁になった場合は各桁を足すのが本来の仕様でありますが、9を引いても同じ結果になる法則があります。コードを書くときはこちらのほうが楽ですね。

元の数 2倍 1桁目+2桁目 2倍-9
0 0*2=0
1 1*2=2
2 2*2=4
3 3*2=6
4 4*2=8
5 5*2=10 1+0=1 10-9=1
6 6*2=12 1+2=3 12-9=3
7 7*2=14 1+4=5 14-9=5
8 8*2=16 1+6=7 16-9=7
9 9*2=18 1+8=9 18-9=9

JavaScriptで検証する

チェックサムで入力ミスを検出する

まずは単純に入力ミスをチェックサムで検証します。先ほどの仕様を素直にコードにしたものです。

// true or false
const result = checksum('4111111111111111')
console.log(result)

/**
 * カード番号の入力ミスを検出する 
 * 
 * @param {string} number カード番号
 * @return {boolean}
 */
function checksum(number){
  const temp = (typeof number === 'string')? number:String(number)
  const n = temp
               .split('')              // 1文字ずつ分割し配列に
               .map( a => Number(a) )  // 配列の各要素を文字列型→数値型に変換
               .reverse()              // 配列を逆順にする
  let total = 0

  for(let i=0; i<n.length; i++){
    if( ((i + 1) % 2) === 0 ){
      const value = n[i] * 2
      total += (value > 9)? value - 9 : value
    }
    else{
      total += n[i]
    }
  }

  return( total % 10 === 0 )
}

カード番号の桁数をチェック

クレジットカード番号の仕様が全世界共通なら良かったのですが、カード会社毎に違うのでまずはどの会社か判定した上で文字列長のチェックを行います。

// 正しければブランド名の文字列、誤りならfalse
const result = checkLength('4111111111111111')  // 'visa'
console.log(result)

/**
 * カード番号の桁数をチェックする 
 * 
 * @param {string} number カード番号
 * @return {mixed} 正=[visa|mastercard|jcb|americanexpress|dinersclub], 誤=false 
 */
function checkLength(number){
  const n = (typeof number === 'string')? number:String(number)
  const between = (value, min, max) => ((min <= Number(value)) && (Number(value) <= max))

  // 13〜16桁の整数で無いならfalseを返して終了
  if( ! n.match(/^[0-9]{13,16}$/) ){
    return(false)
  }

  //--------------------------------------
  // カード会社を判定しつつチェック
  //--------------------------------------
  // VISA
  // (13桁はすでに存在しない説があるため後ろに持ってくる)
  if( (n.substr(0,1) === '4') && ((n.length === 16) || (n.length === 13)) ){
    return('visa')
  }
  // MASTER Card
  else if( (n.match(/^5[1-5]/) || between(n.substr(0,6), 222100, 272099)) && n.length === 16){
    return('mastercard')
  }
  // JCB
  else if( between(n.substr(0,4), 3528, 3589) && n.length === 16 ){
    return('jcb')
  }
  // American Express
  else if( n.match(/^3[47]/) && n.length === 15 ){
    return('americanexpress')
  }
  // Diners Club
  else if( (between(n.substr(0,6), 300000, 303574) || n.substr(0,4) === '3095' || n.match(/^3[689]/)) && (n.length === 14) ){
    return('dinersclub')
  }

  return(false)
}

あわせ技

というわけで上記2つの関数を組み合わせることでチェックサムと文字列長の簡単なチェックができますね。

const number = '4111111111111111'

if( checksum(number) && checkLength(number) !== false ){
  console.log('valid')
}
else{
  console.log('invalid')
}

モジュールを利用する

このページえ書いたコードを利用したモジュールをnpmで公開しています。GitHubで中身も公開しているので気になる方は以下からどうぞ。 github.com

インストール

適当なディレクトリを作ったら、package.jsonを生成します。

$ mkdir foo; cd foo
$ npm init

npmコマンドでインストールします。

$ npm install creditcard-checkerjs

利用方法

チェックサムの検証は以下の通りです。

const creditcard = require('creditcard-checkerjs')
const number = '4111111111111111'

if( creditcard.verify(number) ){
  console.log('valid')
}
else{
  console.log('invalid')
}

カードブランドの検出はこちら。

const creditcard = require('creditcard-checkerjs')
const number = '4111111111111111'

const type = creditcard.cardtype(number)
switch( type ){
  case creditcard.type.VISA:   console.log('Visa'); break;
  case creditcard.type.MASTER: console.log('Mastercard'); break;
  case creditcard.type.JCB:    console.log('JCB'); break;
  case creditcard.type.AMEX:   console.log('American Express'); break;
  case creditcard.type.DINERS: console.log('Diners'); break;

  case creditcard.type.UNKNOWN:
  default:
      console.log('Unknown'); break;
}

よろしければご利用ください!プルリクもお待ちしております。

参考ページ