我应该使用CreditCardAttribute来验证信用卡号码吗?

13

我应该使用微软的CreditCardAttribute来验证信用卡号码吗?

[Required, CreditCard]
public string CreditCardNumber { get; set; }

是应该让支付网关处理,还是采取其他措施?我提出这个问题是因为发现有些客户无法使用他们的信用卡信息提交付款。幸运的是,我能够与其中一位客户合作,并发现在删除CreditCardAttribute后,他们的Visa卡可以成功处理。

部分地,这个问题是修辞性的,但我希望从其他开发者的想法和经验中受益,并通过提出这个问题使其他开发者意识到使用CreditCardAttribute所面临的风险。


1
这个问题提供了一些关于CreditCardAttribute如何验证输入的见解。 - Tom
@Tom,感谢你提供更多信息的链接。我所发现的是,即使算法是100%正确的,有些信用卡也无法通过验证,因为它们不符合Luhn算法正在验证的模式,当这意味着损失收入时,这是非常糟糕的。 - Jeremy Cook
令我惊讶的是,我合作的客户使用了 Visa 信用卡。我原本预计他们会使用一张不太典型的信用卡,比如美国运通或Discover。不幸的是,我并不知道更多信息,也许他们的卡是商务卡,因此商务 Visa 卡不符合 Luhn 算法。希望有更多关于信用卡验证的专业人士能够提供意见。 - Jeremy Cook
1
说实话,我一直依赖我的 Gateway 团队来下载 BIN 文件并验证特定交易(FSA/IIAS),否则我们只进行简单的数字计数,让处理器做辛苦的工作。我不相信第三方(在这种情况下是微软)有可靠的检查,因为它可能会很快改变,而且常常如此。我曾经见过几次,在 BIN 文件旧或损坏的情况下,卡品牌被错误地推断或根本没有被推断出来。最近,PayPal 从 Discover 购买了一个 BIN 范围,并最终成为他们自己的品牌,而不再是 Discover 的品牌。 - Ian Link
1
@IanLink 和 PaulG,听起来大家都一致认为像我经历的那种 Luhn 检查失败是非常不太可能的。非常感谢那些有经验的人给出的所有建议!不幸的是,由于保留信用卡号码是一个大忌,这有点像鬼追逐,但你们激发了我更仔细地审视如何使用 CreditCardAttribute 以及它生成的 JavaScript。也许我使用的方式与预期不同。 - Jeremy Cook
显示剩余13条评论
2个回答

6
在信用卡属性代码背后,它只是执行一个Luhn检查。
所有支付卡(*)目前都遵循ISO/IEC/7812标准,其中包含一个Luhn校验位作为最后一位数字。
然而,这个Luhn校验仅仅用于防止转置错误。在提交卡号到支付网关之前进行完整性检查是有用的,但不适合绝对验证是否为有效的卡号。
有效的卡号范围每月都会更改,唯一绝对验证一个卡号的方法是通过支付网关进行验证。如果只是尝试验证一个卡(而不是收费),应该使用零价值的“仅授权”样式检查。
(*) 唯一的例外是中国的银联卡类型
(历史上还有一个Diners Club的'enRoute'品牌,于1992年退出市场)


请提供一些支持你的断言“所有付款卡目前都遵循Luhn算法”的证据。看起来要么是微软错误地实现了一个简单而又众所周知的算法,要么就是你的说法是错误的。 - Jeremy Cook
1
Microsoft的实现没有问题。这是一个有效的Luhn校验算法。但是,如果您提供无效的测试数据,您将得到一个无效的测试结果...正如预期的那样 :) 您最后剩下的无法通过的测试是一个11位数字。这对于有效的卡号来说太短了,通常至少有16位数字。(格式通常为6位IIN,最后一位为校验位,余数为唯一账户号码)。这意味着Dankort卡只有10,000个有效的账户号码?看起来不太可能 :) - PaulG
也许我误解了你的意思,但是似乎你认为我遇到的问题不存在。我很幸运能够及早发现这个问题 :) 在大约20位到达付款页面的客户中,至少有一位客户使用16位数字的Visa卡号,在CreditCardAttribute验证时卡住了。一旦我删除了该属性,客户就能够顺利提交付款。我知道Luhn是一个标准,也相信微软的实现是没问题的,但是发现前20位客户中有一位使用“非标准”的信用卡号码还是令人惊讶的。 - Jeremy Cook
我也觉得这非常令人惊讶。你必须想象(在这里估算)80%的在线商家在授权之前执行luhn检查验证。因此,在80%的商店中,这张“有效”的卡片将无效?这可能吗,还是更可能是有人在卡号中打错了一个数字? :) 我还想象当您删除luhn检查并发送进行授权时,付款服务提供商可能会拒绝它?如果发现具有无效luhn且仍然授权的数字,那将是非常令人惊讶的事情。 - PaulG
我可能错了,但我几乎可以肯定没有拼写错误。当使用CreditCardAttribute时,我与客户一起工作,由于验证无法完成提交表单。一旦移除了它,付款得到授权,然后完成捕获,完成整个过程。这听起来很疯狂,我也感到惊讶。我开始怀疑CreditCardAttribute的JavaScript验证是否是罪魁祸首。我没有看到任何人对其有效性发表评论,并且无法说是客户端还是服务器端的信用卡验证失败。 - Jeremy Cook

5

我认为找出答案最好的方法就是进行简单测试:

using System;
using System.Linq;
using System.Text;
using System.IO;

namespace SO
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] cards = new string[] {
                //http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm
                "378282246310005",  // American Express
                "4012888888881881", // Visa
                "6011111111111117", // Discover
                "4222222222222", // Visa
                "76009244561", // Dankort (PBS)
                "5019717010103742", // Dakort (PBS) 
                "6331101999990016", // Switch/Solo (Paymentech)
                "30569309025904", // Diners Club 
                //http://www.getcreditcardnumbers.com/
                "5147004213414803", // Mastercard
                "6011491706918120", // Discover
                "379616680189541", // American Express
                "4916111026621797", // Visa
            };

            foreach (string card in cards)
            {
                Console.WriteLine(IsValid(card));
            }

            Console.ReadLine();
        }

        public static bool IsValid(object value)
        {
            if (value == null)
            {
                return true;
            }

            string ccValue = value as string;
            if (ccValue == null)
            {
                return false;
            }
            ccValue = ccValue.Replace("-", "");
            ccValue = ccValue.Replace(" ", "");

            int checksum = 0;
            bool evenDigit = false;

            // http://www.beachnet.com/~hstiles/cardtype.html
            foreach (char digit in ccValue.Reverse())
            {
                if (digit < '0' || digit > '9')
                {
                    return false;
                }

                int digitValue = (digit - '0') * (evenDigit ? 2 : 1);
                evenDigit = !evenDigit;

                while (digitValue > 0)
                {
                    checksum += digitValue % 10;
                    digitValue /= 10;
                }
            }

            return (checksum % 10) == 0;
        }
    }
}

IsValid方法来自于原始的C#CreditCardAttribute类。 12个数字中有1个失败:

        True
        True
        True
        True
        False //"76009244561", // Dankort (PBS)
        True
        True
        True
        True
        True
        True
        True

所以,您应该使用它吗?不,显然它不能检测所有数字。尽管您可以采用他们的代码并进行改进!

非常好的解决问题的方法!我希望它能给我使用“改进”的验证带来信心。不幸的是,它似乎正是我所担心的。信用卡号码列表似乎不完整,因为我被告知这是Visa卡。我期望其中一个失败是Visa。客户可能是错的,但我对此表示怀疑。在我看来,即使您有很多虚假信用卡号码进行测试,以这种方式使用信用卡验证仍然存在风险。毕竟,Visa明天就可以发布新的号码,而我的自定义、完全更新的验证可能会失败。 - Jeremy Cook
的确,现在网络上已经多次讨论了Luhn算法的使用,并不能覆盖所有现有的信用卡。我猜唯一确定的方法是通过实际执行交易来验证它。 - Jevgeni Geurtsen
话虽如此,如果我因某种原因被迫在我的代码中进行验证,那么像这样的单元测试绝对是正确的方法。除此之外,我还想要记录(不包括使用的信用卡号!)验证失败的数量,以便我可以考虑我的验证器是否产生了错误的负面影响。 - Jeremy Cook
我希望在发布我的应用之前能想到寻找那些信用卡验证讨论。我从未预料到验证器会过于严格。我害怕我假定微软的验证器完全可信,最坏的情况是不够严格。我的错误在于没有测试。感谢您展示了代码的工作和不工作的情况! - Jeremy Cook
不确定你从哪里获取了这些卡号,但是我的AMEX“EBG”文档指出Amex卡号始终为15位数字,以34或37开头。它给出了一个例子:'3742 5103 2182 013'。 - PaulG
链接在数组项上面的注释中。只是注意到微软的文章日期是'04,因此我已经从代码/结果中删除了它,因为它们提供的卡号不再可用。 - Jevgeni Geurtsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接