Repository: pydantic/pydantic

Issue:
Support for 13/19 digits VISA credit cards in PaymentCardNumber type
# Feature Request

Output of `python -c "import pydantic.utils; print(pydantic.utils.version_info())"`:
```
             pydantic version: 1.5
            pydantic compiled: True
                 install path: /Users/<hidden>/.pyenv/versions/3.7.2/envs/common-utils/lib/python3.7/site-packages/pydantic
               python version: 3.7.2 (default, Jan 22 2020, 18:22:02)  [Clang 10.0.1 (clang-1001.0.46.4)]
                     platform: Darwin-18.7.0-x86_64-i386-64bit
     optional deps. installed: ['typing-extensions']
```

Currently `PaymentCardNumber` type does support 16-digits VISA only:

https://github.com/samuelcolvin/pydantic/blob/3cd8b1ee2d5aac76528dbe627f40fe1c27bf59f6/pydantic/types.py#L677-L679

However as described on wiki page https://en.wikipedia.org/wiki/Payment_card_number:

> While the vast majority of Visa's account ranges describe 16 digit card numbers there are still a few account ranges (forty as of 11 December 2013) dedicated to 13 digit PANs and several (439 as of 11 Dec. 2013) account ranges where the issuer can mix 13 and 16 digit card numbers. Visa's VPay brand can specify PAN lengths from **13** to **19** digits and so card numbers of more than 16 digits are now being seen.

This fact is handler properly in `faker` library:

https://github.com/joke2k/faker/blob/0f86b60178fbbd27d50a184e94c429d4b60d8aef/faker/providers/credit_card/__init__.py#L48-L49

But not handled in pydantic. 


Comments:
happy to accept a PR to fix this.
Just to clarify, is any length between 13 and 19 digits inclusive allowed? Or just 13, 16, or 19 digits?
AFAIU it's not inclusive. Did some research and this formatting [website](https://www.freeformatter.com/credit-card-number-generator-validator.html#cardFormats) came up.

Let me ask you something about implementation. We may create subtypes following faker's approach (`visa13`, `visa19`, `visa16`). In my opinion, we shouldn't replace `visa` in favor of `visa16` as it's going to break. Let's keep the non explicit `visa` brand ? Any thoughts about this ?
I'm opposed to having multiple types. We should have just one card type, people can subclass that or add validators to constrain types more if they wish.

Patch:
diff --git a/pydantic/types.py b/pydantic/types.py
--- a/pydantic/types.py
+++ b/pydantic/types.py
@@ -799,10 +799,13 @@ def validate_length_for_brand(cls, card_number: 'PaymentCardNumber') -> 'Payment
         Validate length based on BIN for major brands:
         https://en.wikipedia.org/wiki/Payment_card_number#Issuer_identification_number_(IIN)
         """
-        required_length: Optional[int] = None
-        if card_number.brand in {PaymentCardBrand.visa, PaymentCardBrand.mastercard}:
+        required_length: Union[None, int, str] = None
+        if card_number.brand in PaymentCardBrand.mastercard:
             required_length = 16
             valid = len(card_number) == required_length
+        elif card_number.brand == PaymentCardBrand.visa:
+            required_length = '13, 16 or 19'
+            valid = len(card_number) in {13, 16, 19}
         elif card_number.brand == PaymentCardBrand.amex:
             required_length = 15
             valid = len(card_number) == required_length


Test patch:
diff --git a/tests/test_types_payment_card_number.py b/tests/test_types_payment_card_number.py
--- a/tests/test_types_payment_card_number.py
+++ b/tests/test_types_payment_card_number.py
@@ -9,7 +9,9 @@
 
 VALID_AMEX = '370000000000002'
 VALID_MC = '5100000000000003'
-VALID_VISA = '4050000000000001'
+VALID_VISA_13 = '4050000000001'
+VALID_VISA_16 = '4050000000000001'
+VALID_VISA_19 = '4050000000000000001'
 VALID_OTHER = '2000000000000000008'
 LUHN_INVALID = '4000000000000000'
 LEN_INVALID = '40000000000000006'
@@ -73,7 +75,9 @@ def test_validate_luhn_check_digit(card_number: str, valid: bool):
 @pytest.mark.parametrize(
     'card_number, brand, valid',
     [
-        (VALID_VISA, PaymentCardBrand.visa, True),
+        (VALID_VISA_13, PaymentCardBrand.visa, True),
+        (VALID_VISA_16, PaymentCardBrand.visa, True),
+        (VALID_VISA_19, PaymentCardBrand.visa, True),
         (VALID_MC, PaymentCardBrand.mastercard, True),
         (VALID_AMEX, PaymentCardBrand.amex, True),
         (VALID_OTHER, PaymentCardBrand.other, True),
@@ -95,7 +99,7 @@ def test_length_for_brand(card_number: str, brand: PaymentCardBrand, valid: bool
     [
         (VALID_AMEX, PaymentCardBrand.amex),
         (VALID_MC, PaymentCardBrand.mastercard),
-        (VALID_VISA, PaymentCardBrand.visa),
+        (VALID_VISA_16, PaymentCardBrand.visa),
         (VALID_OTHER, PaymentCardBrand.other),
     ],
 )
@@ -104,8 +108,8 @@ def test_get_brand(card_number: str, brand: PaymentCardBrand):
 
 
 def test_valid():
-    card = PaymentCard(card_number=VALID_VISA)
-    assert str(card.card_number) == VALID_VISA
+    card = PaymentCard(card_number=VALID_VISA_16)
+    assert str(card.card_number) == VALID_VISA_16
     assert card.card_number.masked == '405000******0001'