You may need to have a class for each card type, and an abstract class that would be shared between them.
from the Wikipedia page, I can see each card can have Name
, IIN Range
, Active
, Length
and Validation
. Now, from there we can get the common values for each card (what are the common things that most cards shared ?) to have a base line to start from. I can see that card number is mostly between 12 and 19 digits. Which give us a base values for CardNumberMaxLength
and CardNumberMinLength
. Now, the Validation for each card can vary, but from the Wikipedia page it shows that most cards uses Luhn algorithm
validation. This means, we can use it as a base validation process in our abstract class. In which, would make things much easier for us.
We can now construct these into an abstract class something like this :
public abstract class PaymentCard
{
private static readonly Random _random = new Random();
public virtual int CardNumberMaxLength { get; } = 19;
public virtual int CardNumberMinLength { get; } = 12;
public virtual string Name { get; }
public virtual string Number { get; set; }
public virtual bool IsActive { get; } = true;
private int[] InternalGenerateCardNumber(int length, string prefix = null)
{
var startIndex = 0;
var cardNumbers = new int[length];
if(!string.IsNullOrWhiteSpace(prefix))
{
startIndex += prefix.Length;
for(var x = 0; x < prefix.Length; x++)
{
cardNumbers[x] = prefix[x] - '0';
}
}
while(startIndex < length)
{
cardNumbers[startIndex] = _random.Next(0, 10);
startIndex++;
}
return cardNumbers;
}
protected virtual string GenerateNewCardNumber(int length, string prefix = null)
{
string str = null;
do
{
var cardNumbers = InternalGenerateCardNumber(length);
str = string.Concat(cardNumbers);
}
while(!Validate(str));
return str;
}
/// <summary>
/// Validate Credit Card Number using Luhn algorithm
/// </summary>
/// <param name="cardNumber"></param>
/// <returns></returns>
public virtual bool Validate(string cardNumber)
{
if(!string.IsNullOrWhiteSpace(cardNumber) && cardNumber.Length >= CardNumberMinLength && cardNumber.Length <= CardNumberMaxLength)
{
var digits = cardNumber.ToCharArray().Select(x=> Convert.ToInt16(x)).ToArray();
var sum = 0;
for(int x = 0; x < digits.Length; x++)
{
var digit = digits[x];
if(x % 2 != 0)
{
sum += digit;
}
else
{
var doubled = digit * 2;
sum += doubled < 10 ? doubled : ( doubled / 10 ) + ( doubled % 10 );
}
}
return sum % 10 == 0;
}
return false;
}
public abstract string GenerateNewCardNumber();
}
Now, we can use this abstract to implement each card something like this :
public class VisaCard : PaymentCard
{
public override string Name => "Visa";
public override int CardNumberMaxLength => 16;
public override int CardNumberMinLength => 16;
public override string GenerateNewCardNumber() {
return base.GenerateNewCardNumber(16);
}
}
public class MaestroCard : PaymentCard
{
public override string Name => "Maestro";
public override string GenerateNewCardNumber() {
return base.GenerateNewCardNumber(16, "5893");
}
}
this way, you'll have a full control on each card type. for instance, if you need a custom validate for any card, you just override the Validation
method and do your own custom validation. The rest of work should be easy to handle.
usage example :
public string GenerateCardNumber(PaymentCard card) {
return card.GenerateNewCardNumber();
}
now we can create it this way :
var visa = new VisaCard();
var cardNumber = GenerateCardNumber(visa);
// this would return the VisaCard.GenerateNewCardNumber() result.
GenerateVisa
, aGenerateMaestro
, aGenerateSolo
, etc. \$\endgroup\$