65.9K
CodeProject 正在变化。 阅读更多。
Home

终极 .NET 信用卡实用工具类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (39投票s)

2007年8月29日

CPOL

6分钟阅读

viewsIcon

218151

downloadIcon

6406

一个功能强大且简单的 .NET 实用工具类,用于在 C# 和 VB 中验证和测试信用卡号。

Screenshot - UltimateCreditCardUtility.jpg

引言

这个终极信用卡实用工具提供了您在 .NET 应用程序中验证和测试信用卡所需的所有功能,包括:

  • IsValidNumber
  • GetCardTypeFromNumber
  • GetCardTestNumber
  • PassesLuhnTest

所有代码均以 C# 和 VB 两种语言提供。除了该实用工具类,本项目还包括完整的 NUnit 测试和一个用于测试代码的示例网站。

背景

如果您曾尝试在 .NET 应用程序中处理信用卡,就会知道这可能是一项挑战。即使您有一个提供 .NET SDK 的优秀支付处理器,仍有许多功能需要您自己处理,例如信用卡号验证和卡类型检测。别再浪费时间试图从互联网上散落的代码片段中拼凑一个实用工具类了。我整合了一个“终极”信用卡实用工具类,它提供了强大的信用卡号测试、简便的信用卡类型检测以及用于测试应用程序中信用卡接口的功能。最重要的是,这个终极信用卡实用工具类还附带 NUnit 测试,以帮助确保代码在您重要的信用卡应用程序中始终按预期工作。

在本文中,我们将逐一探讨这个终极信用卡实用工具类中包含的每个功能,并解释如何使用简单的 .NET 概念来实现强大的功能。

Using the Code

通过直接添加 .cs/.vb 文件,或将信用卡类编译成一个单独的程序集供应用程序引用,可以轻松地将这个终极信用卡实用工具类集成到任何 .NET 项目中。如果您有多个处理信用卡的应用程序,最好的方法是将其构建成一个单独的 DLL,这样您将来所做的任何更新都可以轻松分发到所有 .NET 应用程序中。

这个实用工具类有五个基本功能,我们将以相反的顺序逐一介绍,从更复杂的基础功能开始,逐步构建到简单的验证功能。

PassesLuhnTest

任何信用卡实用工具类的核心都是 Luhn 算法测试。该测试也称为“模 10”(mod 10),由 IBM 工程师 Hans Luhn 在 1950 年代中期创建,是验证一系列数字是否有效而非随机整数集合的最简单方法之一。所有信用卡号都应通过 Luhn 算法测试;如果未通过,就可以轻松判断输入了错误的卡号。

在这个终极信用卡实用工具中,我们实现了一个简单的函数,它接受一个字符串形式的信用卡号,如果该卡号通过 Luhn 算法测试,则返回 true。

C#
public static bool PassesLuhnTest(string cardNumber)
{
    //Clean the card number- remove dashes and spaces
    cardNumber = cardNumber.Replace("-", "").Replace(" ", "");
    
    //Convert card number into digits array
    int[] digits = new int[cardNumber.Length];
    for (int len = 0; len < cardNumber.Length; len++)
    {
        digits[len] = Int32.Parse(cardNumber.Substring(len, 1));
    }

    //Luhn Algorithm
    //Adapted from code availabe on Wikipedia at
    //http://en.wikipedia.org/wiki/Luhn_algorithm
    int sum = 0;
    bool alt = false;
    for (int i = digits.Length - 1; i >= 0; i--)
    {
        int curDigit = digits[i];
        if (alt)
        {
            curDigit *= 2;
            if (curDigit > 9)
            {
                curDigit -= 9;
            }
        }
        sum += curDigit;
        alt = !alt;
    }

    //If Mod 10 equals 0, the number is good and this will return true
    return sum % 10 == 0;
}
VB
Public Shared Function PassesLuhnTest(ByVal cardNumber As String) As Boolean
    'Clean the card number- remove dashes and spaces
    cardNumber = cardNumber.Replace("-", "").Replace(" ", "")

    'Convert card number into digits array
    Dim digits As Integer() = New Integer(cardNumber.Length - 1) {}
    Dim len As Integer = 0
    While len < cardNumber.Length
        digits(len) = Int32.Parse(cardNumber.Substring(len, 1))
        len += 1
    End While

    'Luhn Algorithm
    'Adapted from code availabe on Wikipedia at
    'http://en.wikipedia.org/wiki/Luhn_algorithm
    Dim sum As Integer = 0
    Dim alt As Boolean = False
    Dim i As Integer = digits.Length - 1
    While i >= 0
        Dim curDigit As Integer = digits(i)
        If alt Then
            curDigit *= 2
            If curDigit > 9 Then
                curDigit -= 9
            End If
        End If
        sum += curDigit
        alt = Not alt
        i -= 1
    End While

    'If Mod 10 equals 0, the number is good and this will return true
    Return sum Mod 10 = 0
End Function

GetCardTypeFromNumber

当您在网上购物时,可能已经注意到有些网站要求您选择卡类型,而有些则不需要。无论您是否明确要求用户提供卡类型,您都可以通过分析卡号轻松确定其类型。为了在我们的实用工具类中实现这一点,我们将利用正则表达式的强大功能及其将匹配项存储在命名组中的能力。学习正则表达式可能具有挑战性,但有很多优秀资源可以帮助您为各种情况生成正则表达式模式,例如 regexlib.com 和 Regex Buddy(软件)。对于我们的实用工具,我们将使用一个来自 RegExLib 的正则表达式,它可以验证所有主流信用卡。

^(?:(?<Visa>4\d{3})|(?<MasterCard>5[1-5]\d{2})|(?<Discover>6011)|
(?<DinersClub>(?:3[68]\d{2})|(?:30[0-5]\d))|(?<Amex>3[47]\d{2}))([ -]?)
(?(DinersClub)(?:\d{6}\1\d{4})|(?(Amex)(?:\d{6}\1\d{5})|
(?:\d{4}\1\d{4}\1\d{4})))$

要全面分析此正则表达式,请访问此外部资源。该分析由 Regex Buddy 生成,用英文描述了正则表达式模式中的每条规则是如何工作的,并链接到解释每条规则的在线帮助文档。

有了正则表达式模式,我们现在可以获取任何信用卡号并确定其类型。

C#
private const string cardRegex = "^(?:(?<Visa>4\\d{3})|
    (?<MasterCard>5[1-5]\\d{2})|(?<Discover>6011)|(?<DinersClub>
    (?:3[68]\\d{2})|(?:30[0-5]\\d))|(?<Amex>3[47]\\d{2}))([ -]?)
    (?(DinersClub)(?:\\d{6}\\1\\d{4})|(?(Amex)(?:\\d{6}\\1\\d{5})
    |(?:\\d{4}\\1\\d{4}\\1\\d{4})))$";

public static CreditCardTypeType? GetCardTypeFromNumber(string cardNum)
{
    //Create new instance of Regex comparer with our
    //credit card regex patter
    Regex cardTest = new Regex(cardRegex);

    //Compare the supplied card number with the regex
    //pattern and get reference regex named groups
    GroupCollection gc = cardTest.Match(cardNum).Groups;
   
    //Compare each card type to the named groups to 
    //determine which card type the number matches
    if (gc[CreditCardTypeType.Amex.ToString()].Success)
    {
        return CreditCardTypeType.Amex;
    }
    else if (gc[CreditCardTypeType.MasterCard.ToString()].Success)
    {
        return CreditCardTypeType.MasterCard;
    }
    else if (gc[CreditCardTypeType.Visa.ToString()].Success)
    {
        return CreditCardTypeType.Visa;
    }
    else if (gc[CreditCardTypeType.Discover.ToString()].Success)
    {
        return CreditCardTypeType.Discover;
    }
    else
    {
        //Card type is not supported by our system, return null
        //(You can modify this code to support more (or less)
        // card types as it pertains to your application)
        return null;
    }
}
VB
Private Const cardRegex As String = "^(?:(?<Visa>4\d{3})|
    (?<MasterCard>5[1-5]\d{2})|(?<Discover>6011)|(?<DinersClub>
    (?:3[68]\d{2})|(?:30[0-5]\d))|(?<Amex>3[47]\d{2}))([ -]?)
    (?(DinersClub)(?:\d{6}\1\d{4})|(?(Amex)(?:\d{6}\1\d{5})|
    (?:\d{4}\1\d{4}\1\d{4})))$"

Public Shared Function GetCardTypeFromNumber(ByVal cardNum As String) 
    As CreditCardTypeType
    'Create new instance of Regex comparer with our
    'credit card regex patter
    Dim cardTest As New Regex(cardRegex)

    'Compare the supplied card number with the regex
    'pattern and get reference regex named groups
    Dim gc As GroupCollection = cardTest.Match(cardNum).Groups

    'Compare each card type to the named groups to 
    'determine which card type the number matches
    If gc(CreditCardTypeType.Amex.ToString()).Success Then
        Return CreditCardTypeType.Amex
    ElseIf gc(CreditCardTypeType.MasterCard.ToString()).Success Then
        Return CreditCardTypeType.MasterCard
    ElseIf gc(CreditCardTypeType.Visa.ToString()).Success Then
        Return CreditCardTypeType.Visa
    ElseIf gc(CreditCardTypeType.Discover.ToString()).Success Then
        Return CreditCardTypeType.Discover
    Else
        'Card type is not supported by our system, return null
        '(You can modify this code to support more (or less)
        ' card types as it pertains to your application)
        Return Nothing
    End If
End Function

您可能已经注意到,我们在上面的代码中引用了 CreditCardTypeType。这个实用工具的原始版本是为 PayPal 的 Web Payments Pro 设计的,该类型由 PayPal API 提供。在没有 PayPal API 的情况下,我们可以轻松地为我们的实用工具重新创建这个类型。

C#
public enum CreditCardTypeType
{
    Visa,
    MasterCard,
    Discover,
    Amex,
    Switch,
    Solo
}
VB
Public Enum CreditCardTypeType
    Visa
    MasterCard
    Discover
    Amex
    Switch
    Solo
End Enum

IsNumberValid

现在我们已经构建了一些验证信用卡所需的基本组件,我们可以创建一个简单的函数来验证卡号。为什么不直接使用 Luhn 算法测试来验证信用卡呢?虽然 Luhn 公式可以验证数字的排列顺序是否正确,但它无法验证卡是否属于正确的类型。为了确保我们的实用工具类既能验证卡号的正确性,又能验证其属于特定卡类型,我们将使用正则表达式测试和我们的 PassesLuhnTest 函数来确保提供的信用卡号看起来是有效的。

C#
public static bool IsValidNumber(string cardNum, CreditCardTypeType? cardType)
{
    //Create new instance of Regex comparer with our 
    //credit card regex pattern
    Regex cardTest = new Regex(cardRegex);

    //Make sure the supplied number matches the supplied
    //card type
    if (cardTest.Match(cardNum).Groups[cardType.ToString()].Success)
    {
        //If the card type matches the number, then run it
        //through Luhn's test to make sure the number appears correct
        if (PassesLuhnTest(cardNum))
            return true;
        else
            //The card fails Luhn's test
            return false;
    }
    else
        //The card number does not match the card type
        return false;
}
VB
Public Shared Function IsValidNumber(ByVal cardNum As String, _
    ByVal cardType As CreditCardTypeType) As Boolean
    'Create new instance of Regex comparer with our 
    'credit card regex pattern
    Dim cardTest As New Regex(cardRegex)

    'Make sure the supplied number matches the supplied
    'card type
    If cardTest.Match(cardNum).Groups(cardType.ToString()).Success Then
        'If the card type matches the number, then run it
        'through Luhn's test to make sure the number appears correct
        If PassesLuhnTest(cardNum) Then
            Return True
        Else
            Return False
            'The card fails Luhn's test
        End If
    Else
        Return False
        'The card number does not match the card type
    End If
End Function

然而,有时您可能只想验证一个号码,而不提供卡类型。我们已经有了一个可以通过卡号确定卡类型的函数,所以我们只需为 IsValidNumber 创建一个重载版本,它接受单个参数(卡号)。

C#
public static bool IsValidNumber(string cardNum)
{
    Regex cardTest = new Regex(cardRegex);

    //Determine the card type based on the number
    CreditCardTypeType? cardType = GetCardTypeFromNumber(cardNum);

    //Call the base version of IsValidNumber and pass the 
    //number and card type
    if (IsValidNumber(cardNum, cardType))
        return true;
    else
        return false;
}
VB
Public Shared Function IsValidNumber(ByVal cardNum As String) As Boolean
    Dim cardTest As New Regex(cardRegex)

    'Determine the card type based on the number
    Dim cardType As CreditCardTypeType = GetCardTypeFromNumber(cardNum)

    'Call the base version of IsValidNumber and pass the 
    'number and card type
    If IsValidNumber(cardNum, cardType) Then
        Return True
    Else
        Return False
    End If
End Function

GetCardTestNumber

我们已经构建了一个全面的测试,可以检查卡号的类型和准确性,所以现在我们需要一种简单的方法来生成“伪造的”测试卡号,以便在开发期间使用。如果您在应用程序中添加代码来检测是否处于“开发”模式(例如,在配置文件中添加一个键或读取当前 URL),您可以编写代码让您的界面在运行测试时自动填充一个伪造的信用卡号。这将为您省去在开发应用程序时重复手动输入有效测试数据的麻烦。当您的应用程序在生产环境中运行时,此测试功能应被关闭,并且只应接受客户的信用卡号。

C#
public static string GetCardTestNumber(CreditCardTypeType cardType)
{
    //Return bogus CC number that passes Luhn and format tests
    switch (cardType)
    {
        case CreditCardTypeType.Amex:
            return "3782 822463 10005";
        case CreditCardTypeType.Discover:
            return "6011 1111 1111 1117";
        case CreditCardTypeType.MasterCard:
            return "5105 1051 0510 5100";
        case CreditCardTypeType.Visa:
            return "4111 1111 1111 1111";
        default:
            return null;
    }
}
VB
Public Shared Function GetCardTestNumber(ByVal cardType As CreditCardTypeType)
    As String
    'Return bogus CC number that passes Luhn and format tests
    Select Case cardType
        Case CreditCardTypeType.Amex
            Return "3782 822463 10005"
        Case CreditCardTypeType.Discover
            Return "6011 1111 1111 1117"
        Case CreditCardTypeType.MasterCard
            Return "5105 1051 0510 5100"
        Case CreditCardTypeType.Visa
            Return "4111 1111 1111 1111"
        Case Else
            Return Nothing
    End Select
End Function

测试实用工具

任何用于支持像处理信用卡这样重要交易的代码,都应该有一套完整的测试来确保其按预期工作。因此,这个终极信用卡实用工具附带了一套全面的 NUnit 测试,可以添加到您的常规测试流程中。

介绍如何创建 NUnit 测试的细节不在本文的讨论范围之内,但这里有一个该实用工具类的 NUnit 测试示例,用于测试 IsValidNumber 函数。

C#
[Test()]
public void IsValidNumberTypeTest()
{
    //Bogus Visa numbers in various formats
    //The IsValidNumber function should accept dashes,
    //spaces, and no separators. It should fail on period 
    //separators and bad numbers.
    string numDash = "4111-1111-1111-1111";
    string numSpace = "4111 1111 1111 1111";
    string numNoSpace = "4111111111111111";
    string numBadSep = "4111.1111.1111.1111";
    string numBadLen = "4111-1111-1111-111";

    Assert.IsTrue(CreditCardUtility.IsValidNumber(numDash), 
    "IsValidNumber should allow numbers with dashes");
    Assert.IsTrue(CreditCardUtility.IsValidNumber(numSpace), 
    "IsValidNumber should allow numbers with spaces");
    Assert.IsTrue(CreditCardUtility.IsValidNumber(numNoSpace), 
    "IsValidNumber should allow numbers with no spaces");
    Assert.IsFalse(CreditCardUtility.IsValidNumber(numBadLen), 
    "IsValidNumber should not allow numbers with too few numbers");
    Assert.IsFalse(CreditCardUtility.IsValidNumber(numBadSep), 
    "IsValidNumber should not allow numbers with dot separators");
}

演示网站

在终极信用卡实用工具的下载包中,包含一个演示网站,让您可以在浏览器中快速测试任何信用卡号。该网站的 CSS 针对 Firefox 进行了优化,但在任何现代浏览器中都可以使用。为了给演示应用增加额外的交互性,我们使用了 Telerik RadControls 的试用版,以实现掩码输入和 AJAX 功能。该控件的试用版可以免费用于演示应用,但页面加载时会随机出现周期性的试用信息。有关 RadControls 商业版的更多信息,请访问 http://www.telerik.com

结论

就是这样!这个终极信用卡实用工具类提供了在将信用卡号传递给支付处理器之前进行验证所需的所有基本功能。该实用工具无法在处理前绝对保证一个号码是真实的信用卡号,但它可以消除大多数卡号输入错误,并帮助您减少与支付处理器的交互次数。希望您会觉得这个实用工具很有帮助,并能在您的下一个信用卡应用程序开发中节省时间。

历史

版本 日期 注释
1.1
2007年8月30日 使用 PayPal 推荐的测试卡号更新了测试信用卡号(感谢 Scott)。
1.0 2007年8月29日 发布初始版本。
© . All rights reserved.