Repository: joke2k/faker
Pull request #1999 (2024-02-26 22:56:14)

feat(uk_UA-credit-cards) Add Uk-UA credit card provider
### Add UA Credit card provider
Add tests
Add bank list
Add translit function 


Patch:
diff --git a/faker/providers/bank/uk_UA/__init__.py b/faker/providers/bank/uk_UA/__init__.py
index 3b53dd45..5f5a560f 100644
--- a/faker/providers/bank/uk_UA/__init__.py
+++ b/faker/providers/bank/uk_UA/__init__.py
@@ -5,7 +5,83 @@ class Provider(BankProvider):
     """Implement bank provider for ``uk_UA`` locale.
     Source for rules for bban format:
     https://bank.gov.ua/en/iban
+    Banks list:
+    https://ubanks.com.ua/adr/
     """
 
     bban_format = "#" * 27
     country_code = "UA"
+    banks = (
+        'izibank',
+        'monobank',
+        'O.Bank',
+        'sportbank',
+        'А-Банк',
+        'Агропросперіс Банк',
+        'АкордБанк',
+        'Альтбанк',
+        'Асвіо Банк',
+        'Банк 3/4',
+        'Банк Авангард',
+        'Банк Альянс',
+        'Банк Власний Рахунок',
+        'Банк Восток',
+        'Банк інвестицій та заощаджень',
+        'Банк Кредит Дніпро',
+        'Банк Портал',
+        'Банк Український Капітал',
+        'Банк Фамільний',
+        'БТА Банк',
+        'Глобус',
+        'Грант',
+        'Дойче Банк ДБУ',
+        'Європейський Промисловий Банк',
+        'Ідея Банк',
+        'ІНГ Банк Україна',
+        'Індустріалбанк',
+        'Кліринговий Дім',
+        'Комінбанк',
+        'КомІнвестБанк',
+        'Кредит Європа Банк',
+        'Кредитвест Банк',
+        'Креді Агріколь',
+        'Кредобанк',
+        'Кристалбанк',
+        'Львів',
+        'МетаБанк',
+        'Міжнародний Інвестиційний Банк',
+        'Мотор-Банк',
+        'МТБ Банк',
+        'Національний банк України',
+        'Оксі Банк',
+        'ОТП Банк',
+        'Ощадбанк',
+        'Перший Інвестиційний Банк',
+        'Перший Український Міжнародний Банк',
+        'Південний',
+        'Піреус Банк',
+        'Полікомбанк',
+        'Полтава-Банк',
+        'Правекс Банк',
+        'ПриватБанк',
+        'ПроКредит Банк',
+        'Радабанк',
+        'Райффайзен Банк',
+        'РВС Банк',
+        'СЕБ Корпоративний Банк',
+        'Сенс Банк',
+        'Сітібанк',
+        'Скай Банк',
+        'ТАСкомбанк',
+        'Траст-капітал',
+        'Український банк реконструкції та розвитку',
+        'Укргазбанк',
+        'Укрексімбанк',
+        'УкрСиббанк',
+        'Універсал Банк',
+        'Юнекс Банк',
+    )
+
+    def bank(self) -> str:
+        """Generate a bank name."""
+        return self.random_element(self.banks)
diff --git a/faker/providers/credit_card/uk_UA/__init__.py b/faker/providers/credit_card/uk_UA/__init__.py
new file mode 100644
index 00000000..8eee3afd
--- /dev/null
+++ b/faker/providers/credit_card/uk_UA/__init__.py
@@ -0,0 +1,56 @@
+from collections import OrderedDict
+from typing import Optional
+
+from faker.providers.person.uk_UA import translit
+from .. import CardType, CreditCard
+from .. import Provider as CreditCardProvider
+
+
+class Provider(CreditCardProvider):
+    """Implement credit card provider for ``uk_UA`` locale.
+    https://blog.ipay.ua/uk/sekrety-bankovskix-kart-kak-identificirovat-bank-po-nomeru-karty/
+    """
+    prefix_visa = ["4"]
+    prefix_mastercard = ["51", "52", "53", "54"]
+    prefix_prostir = ["9"]
+    prefix_maestro = ["6762"]
+
+    credit_card_types = OrderedDict(
+        (
+            ("visa", CreditCard("Visa", prefix_visa, security_code="CVV2")),
+            ("mastercard", CreditCard("Mastercard", prefix_mastercard, security_code="CVC2")),
+            ("prostir", CreditCard("ПРОСТІР", prefix_prostir, security_code="CVC2")),
+            ("maestro", CreditCard("Maestro", prefix_maestro, security_code="CVV")),
+        )
+    )
+
+    def credit_card_full(self, card_type: Optional[CardType] = None) -> str:
+        """ Generate UA Credit Card:
+        Supported card types 'visa', 'mastercard', 'prostir', 'maestro'
+
+        :sample:
+        :sample: card_type="prostir"
+        :sample: card_type="mastercard"
+        """
+        card = self._credit_card_type(card_type)
+        tpl = "{provider}\n" "{owner}\n" "{number} {expire_date}\n" "{security}: {security_nb}\n" "{issuer}"
+        tpl = tpl.format(
+            provider=card.name,
+            owner=translit(
+                self.generator.parse(
+                    self.random_element(
+                        [
+                            "{{first_name_male}} {{last_name_male}}",
+                            "{{first_name_female}} {{last_name_female}}",
+                        ]
+                    )
+                )
+            ),
+            number=self.credit_card_number(card),
+            expire_date=self.credit_card_expire(),
+            security=card.security_code,
+            security_nb=self.credit_card_security_code(card),
+            issuer=self.generator.parse("{{bank}}"),
+        )
+
+        return self.generator.parse(tpl)
diff --git a/faker/providers/person/uk_UA/__init__.py b/faker/providers/person/uk_UA/__init__.py
index c991c0ac..56a785cf 100644
--- a/faker/providers/person/uk_UA/__init__.py
+++ b/faker/providers/person/uk_UA/__init__.py
@@ -1,7 +1,8 @@
 from collections import OrderedDict
 from typing import Dict, Optional
 
-from ....typing import SexLiteral
+from faker.typing import SexLiteral
+
 from .. import ElementsType
 from .. import Provider as PersonProvider
 


Test patch:
diff --git a/tests/providers/test_credit_card.py b/tests/providers/test_credit_card.py
index 6cd37934..a0c8fab3 100644
--- a/tests/providers/test_credit_card.py
+++ b/tests/providers/test_credit_card.py
@@ -4,6 +4,7 @@ from typing import Pattern
 
 from faker.providers.bank.ru_RU import Provider as RuRuBankProvider
 from faker.providers.credit_card import Provider as CreditCardProvider
+from faker.providers.bank.uk_UA import Provider as UkUaBankProvider
 
 
 class TestCreditCardProvider:
@@ -152,3 +153,39 @@ class TestPtPt:
         for _ in range(num_samples):
             number = faker.credit_card_number("maestro")
             assert self.maestro_pattern.fullmatch(number)
+
+
+class TestUkUa:
+    mastercard_pattern: Pattern = re.compile(
+        r"(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}",
+    )
+    visa_pattern: Pattern = re.compile(r"4[0-9]{12}([0-9]{3}){0,2}")
+    maestro_pattern: Pattern = re.compile(r"(67)[0-9]{14}")
+    prostir_pattern: Pattern = re.compile(r"(9)[0-9]{15}")
+
+    def test_mastercard(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number("mastercard")
+            assert self.mastercard_pattern.fullmatch(number)
+
+    def test_visa(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number("visa")
+            assert self.visa_pattern.fullmatch(number)
+
+    def test_maestro(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number("maestro")
+            assert self.maestro_pattern.fullmatch(number)
+
+    def test_prostir(self, faker, num_samples):
+        for _ in range(num_samples):
+            number = faker.credit_card_number("prostir")
+            assert self.prostir_pattern.fullmatch(number)
+
+    def test_credit_card_full(self, faker, num_samples):
+        for _ in range(num_samples):
+            card_data = faker.credit_card_full('prostir').split("\n")
+            assert re.match("[A-Za-z]+", card_data[1])
+            assert card_data[4] in UkUaBankProvider.banks
+            assert card_data[0] == 'ПРОСТІР'