クレジットカード番号は、各種ブランド(VISA/MASTERCARD/JCB/AMEXなど)によって異なる番号が付与されます。クレジットカード番号には、チェックデジットが含まれていて、正当性を確認することができます。
■ クレジットカード番号の体系
クレジットカード番号の体系は、国際標準規格(ISO/IEC 7812)で決められています。
最後の桁には、チャックデジットと呼ばれる数字が付与され、クレジットカード番号の正当性を確認できます。
・BIN(6桁)
発行者識別番号であり、ブランド(VISA/MASTERCARD/JCB/AMEXなど)毎に決められている値
・口座番号(最大12桁)
ブランド(VISA/MASTERCARD/JCB/AMEXなど)が付与する値
・チェックデジット(1桁)
決められたアルゴリズム(Luhnアルゴリズム)でチェックが正常となるために付与される値
ちなみに、クレジットのICカードの規格であるEMVでは、TLVのTAG 0x5Aがクレジットカード番号(PAN)となっています。
https://emvlab.org/emvtags/?number=5A
Name | Description | Source | Format | Template | Tag | Length | P/C |
---|---|---|---|---|---|---|---|
Application Primary Account Number (PAN) | Valid cardholder account number | ICC | cn var. up to 19 | 70 or 77 | 5A | 0–10 | primitive |
■ クレジットカード番号のチェックアルゴリズム
Luhnアルゴリズムを以下を参考にPythonでプログラミングしてみました。
https://ja.wikipedia.org/wiki/Luhn%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0
クレジットカード番号からブランド名を取得するようにしています。
以下のクレジットカード番号のBINを判定しています。
テストのクレジットカード番号は、以下のPAYJPのものを使いました。
(ただし、一部、上記のBINの定義があとで追加されているようで、ブランド名が取得できないものもあります)
# -*- coding: utf-8 -*-
import sys
import os
#
# ブランド名取得関数
#
def GetBrand(pan):
name = ''
# ブランド毎のBINは、以下を参考とした
# https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AC%E3%82%B8%E3%83%83%E3%83%88%E3%82%AB%E3%83%BC%E3%83%89%E3%81%AE%E7%95%AA%E5%8F%B7
brand_list = [['VISA',[[4]]],\
['JCB',[[3528,3589]]],\
['MASTERCARD',[[510000,559999],[222100,272099]]],\
['DISCOVER',[[60110],[60112,60114],[601174,601179],[601186,601199],[644,649],[65]]],\
['DINARS',[[300,303574],[3095],[36],[38,39]]],\
['AMEX',[[34],[37]]]]
for brand in brand_list:
name=brand[0]
id_list = brand[1]
for id in id_list:
if len(id) > 1:
lower = id[0]
upper = id[1]
size = max(len(str(lower)),len(str(upper)))
pan_head=pan[:size]
lower_str=str(lower).ljust(size, '0')
upper_str=str(upper).ljust(size, '0')
if int(pan_head) >= int(lower_str) and int(pan_head) <= int(upper_str):
return name
else:
lower = id[0]
size = len(str(lower))
pan_head=pan[:size]
if int(pan_head) == lower:
return name
return ''
#
# クレジットカード番号チェック関数
# ⇒ 判定結果とブランド名を返す
#
def CheckPan(pan):
sum = 0
name = ''
# PAN文字列が数字で、19桁以下かをチェック
if not pan.isdecimal() or len(pan) > 19:
return False
# PAN文字列を逆にして1桁づつ取得
i = 1
for degit in reversed(pan):
degit = int(degit)
# 桁が偶数番かをチェック
if i%2 == 0:
# 偶数番のとき2倍する
degit *= 2
# 10以上(桁あふれ)した場合
if degit > 9:
# 各桁を足し合わせる
sum_tmp = 0
for part in str(degit):
part = int(part)
sum_tmp += part
degit = sum_tmp
# 加算
sum += degit
i+=1
# 10割った余りが0なら正常
if (sum % 10) == 0:
# ブランド名を取得
name=GetBrand(pan)
return True,name
else:
return False,name
if __name__ == '__main__':
# テストカード番号
# PAYJPのテストカード番号を使う
# https://pay.jp/docs/testcard
print(CheckPan('4242424242424242'))
print(CheckPan('4012888888881881'))
print(CheckPan('5555555555554444'))
print(CheckPan('5105105105105100'))
print(CheckPan('3530111333300000'))
print(CheckPan('3566002020360505'))
print(CheckPan('378282246310005'))
print(CheckPan('371449635398431'))
print(CheckPan('38520000023237'))
# ブランド名(Dinars)が判定できない(BINが追加されている思われる)
print(CheckPan('30569309025904'))
# ブランド名(Discover)が判定できない(BINが追加されている思われる)
print(CheckPan('6011111111111117'))
print(CheckPan('6011000990139424'))
■ まとめ
チェックデジットがあるからといって、クレジットカード番号を安易にチェックすると、逆に、弊害となる場合もあり得ます。
100%、クレジットカード番号の最後に下位1桁をチェックデジットとして扱っているとは言えないからです。
確実に、チェックデジットを付与していると特定できるブランドだけで実施できるのであればよいですが、そうではない場合、チェックデジットを使ったクレジットカード番号のチェックは行わないのが無難です。
実際には、カード発行者側のシステムにおいて決済を実行する際に、 チェックデジットを使ったクレジットカード番号のチェック が実施されているものと考えられます。