Search code examples
c#regex

Issue while masking Credit Card No. before and/or after spaces


I am having an issue when there is a space before/after in CreditCardNo.

For example from INV 2420852290 to SAV 0165487. Here 2420852290 is 10-digits and still it is getting masked.

For a CreditCardNo, the range is 12-19 digits. The reason is the space (before and after the digits) which is taken that extra 11th and12 character i think.

The regex in use is

(?<=(?<![\d-*])(?=(?:-?[\d*\s]){12,19}(?![\d-*\s]))[\d-*\s]*)[\d*](?!(?:-?[\d*\s]){0,3}(?![\d-*\s]))

I tried below code with above regex. Expected is that all the scenarios along with the one which is asked in question should work as it is. The last 4 digits should always be masked with x.

They can be tested using the URL - https://dotnetfiddle.net/Gopzoz. Thanks

        public static string MaskNewCCNo(this string value)
        {
           var a = Regex.Replace(value, @"(?<=(?<![\d-*])(?=(?:-?[\d*\s]){12,19}(?![\d-*\s]))[\d-*\s]*)[\d*](?!(?:-?[\d*\s]){0,3}(?![\d-*\s]))", "x");

            return a;
        }

Solution

  • The reason your current regex did not work for the sample lies in (?<![\d-*]) which purpose is meant to separate the whole number from text but it just checks for one of the listed characters. Together with [\d*\s]){12,19} that could match the specified amount of digits or whitespace.

    Besides I would not use something like [\d-*\s]. In this case (.NET regex) there is no error but it still looks ugly. An unescaped hyphen inside a character class is used to denote a character range. To match a literal hyphen put it at start/end of the character-class or escape it with a backslash.

    As per your follow-up question better don't use one (huge) regex for this. It's certainly smarter to go with Wiktor's idea which can be more easily adapted to changing and growing requirements.


    Credit Card Number (matching the full number)

    The problem lies in distinguishing between the cc- and account-number and separate those from the other text. To be as specific as possible with matching the cc-number - instead of looking for 12-19 digits with spaces or hyphens anywhere in between - better identify the cc-number by starting with 3-4 groups, each containing 4 adjacent digits - optionally followed by 1-3 digits. If the groups are separated by dash or space, capture it and match as far as separator is consistent.

    Have a look if the following pattern fits your needs (regex demo).

    (?<!\d-?)\d{4}([ -]?)\d{4}(?:\1\d{4}){1,2}(?:\1\d{1,3})?(?!-?\d)
    

    Account Number (from your new question)

    You can use the same regex for the account number by just replacing the cc-part with \d{8,9}. Assuming there are no separators in the account-no like in your provided samples (regex demo).

    (?<!\d-?)\d{8,9}(?!-?\d)
    

    Combining both patterns

    If necessary for your application, you can also combine both patterns into one by alternating between the options inside a non-capturing group - more left option is tried first (regex demo).

    (?<!\d-?)(?:\d{4}([ -]?)\d{4}(?:\1\d{4}){1,2}(?:\1\d{1,3})?|\d{8,9})(?!-?\d)
    

    On each resulting match (the full numbers) replace such as \d(?=(?:[ -]?\d){4}) with x to mask all but the last 4 digits with use of the technique that @WiktorStribiżew provides in his answer.

    See your updated demo (the combined pattern is in MaskIfContainCreditCardPanorAcctNumNew).