# Имитация функций банка # задача: https://stepik.org/lesson/967334/step/7?unit=973876 # решение: https://gitea.b4tman.ru/temp/py_stepik/src/branch/master/bank.py """ Проект онлайн - банк(В стадии разработки) Необходимо создать "онлайн" банк, у которого будут реализованы следующие функции. Разумеется, мы не создаем полноценный онлайн банк, это просто имитация. 1. Регистрация 2. Вход (Необходимо сделать, чтобы у каждого пользователя программы была возможность После входа в случае если это происходит днем нужно вывести Добрый день!, если это происходит вечером, нужно вывести добрый вечер!, если ночью -- то нужно вывести Доброй ночи 3. Открытие счета 4. Пополнение(вводя данные кредитной карты) При вводе кредитной карты необходимо проверять правильность ее ввода. 5. Проверка идентификации пользователя (Пользователь при регистрации вводит свой e-mail, туда отправляется письмо со случайной цифрой, эту цифру необходимо ввести в программу. Если число сгенерированное совпадает, то мы говорим, что человек прошел верификацию 6. Оплатить мобильный телефон (просто с телефона снимается определенная сумма денег) 7. Показать рейтинг 8. Сделать викторину(За каждый правильный ответ пользователю начисляется балл в рейтинг) 9. Вывести на экран """ import json import os.path from getpass import getpass from dataclasses import dataclass, field, asdict import hashlib import uuid import hmac import datetime import random session = {} def get_hello_hour(): now = datetime.datetime.now() return ["Доброй ночи", "Доброе утро", "Добрый день", "Добрый вечер"][now.hour // 6] def validate_credit_card(card_number: str) -> bool: card_number = [int(num) for num in card_number] checkDigit = card_number.pop(-1) card_number.reverse() card_number = [ num * 2 if idx % 2 == 0 else num for idx, num in enumerate(card_number) ] card_number = [ num - 9 if idx % 2 == 0 and num > 9 else num for idx, num in enumerate(card_number) ] card_number.append(checkDigit) checkSum = sum(card_number) return checkSum % 10 == 0 class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class SimpleDataBase(metaclass=Singleton): __FILENAME = "bank.json" def __init__(self): if os.path.isfile(self.__class__.__FILENAME): with open(self.__class__.__FILENAME, "r", encoding="utf-8") as f: self.data = json.load(f) else: self.data = {"users": {}, "accounts": {}, "rating": {}} def commit(self): with open(self.__class__.__FILENAME, "w", encoding="utf-8") as f: json.dump(self.data, f) @staticmethod def _is_data(attr): return "data" == attr def __getattr__(self, attr): if self.__class__._is_data(attr): return super().__getattr__(attr) return self.data[attr] def __setattr__(self, attr, value): if self.__class__._is_data(attr): return super().__setattr__(attr, value) self.data[attr] = value def __delattr__(self, attr): if self.__class__._is_data(attr): return super().__delattr__(attr) del self.data[attr] @dataclass class User: name: str fio: str email: str verification_code: int is_verifyed: bool = field(init=False) salt: str = field(init=False) hash: str = field(init=False) def __post_init__(self): self.hash = "" self.salt = "" self.is_verifyed = False def set_password(self, password: str): self.salt = uuid.uuid4().hex self.hash = hashlib.sha512((password + self.salt).encode("utf-8")).hexdigest() def authenticate(self, password: str) -> bool: hash = hashlib.sha512((password + self.salt).encode("utf-8")).digest() return hmac.compare_digest(hash, bytes.fromhex(self.hash)) def email_verification(self, code: int) -> bool: res = self.verification_code == code if not self.is_verifyed: self.is_verifyed = res return self.is_verifyed def save(self): users = Users() users.save_user(self) @classmethod def from_dict(cls, data: dict): user = cls(*(data[k] for k in "name fio email verification_code".split())) for k in "is_verifyed salt hash".split(): setattr(user, k, data[k]) return user class Users(metaclass=Singleton): def __init__(self): self.db = SimpleDataBase() def get_user(self, name: str) -> User: data = self.db.users.get(name) return data and User.from_dict(data) def save_user(self, user: User): self.db.users[user.name] = asdict(user) def add_user( self, name: str, fio: str, email: str, verification_code: int, password: str ) -> bool: if self.get_user(name) is not None: return False user = User(name, fio, email, verification_code) user.set_password(password) self.save_user(user) return True def authenticate(self, name: str, password: str) -> User | None: user = self.get_user(name) if user is None: return False return user.authenticate(password) and user or None def fake_email_send(email, code): with open(email, "w", encoding="utf-8") as f: print(f"Ваш код: {code}", file=f) def registration(): """Регистрация""" print(f"{get_hello_hour()}! Давайте Вас зарегистрируем!") fio = input("Ваше ФИО: ") name = input("Ваш логин: ") email = input("Ваш email: ") verification_code = random.randint(0, 9) password = getpass("Введите пароль: ") # TODO: валидация данных и доп.вопросы users = Users() ok = users.add_user(name, fio, email, verification_code, password) if ok: print("Регистрация успешна, спасибо!") fake_email_send(email, verification_code) print( "На Ваш email отправлен код идентификации (создан файл в текущей папке с именем=email)" ) users.db.commit() session["user"] = users.get_user(name) else: print("Ошибка, выберите другой логин!") def login() -> User | None: """Вход""" if "user" in session: print(f"Вы уже вошли в систему, {session['user'].fio}!") return session["user"] print(f"{get_hello_hour()}! Добро пожаловать в систему банка!") name = input("Ваш логин: ") password = getpass("Введите пароль: ") # TODO: валидация данных и доп.вопросы users = Users() user = users.authenticate(name, password) if user: print(f"Вы успешно вошли в систему, {user.fio}!") else: print("Ошибка: Неправильный логин или пароль!") return None session["user"] = user return user def logout(): """Выход пользователя""" if "user" not in session: print("Для того чтобы выйти из системы, сначала нужно в неё войти :)") return user = session["user"] del session["user"] print(f"До новых встреч, {user.fio}!") def user_verification(): """Идентификация пользователя""" user = login() if user is None: return if user.is_verifyed: print("Вы уже идентифцированы!") return users = Users() code = int(input("Введите код идентификации: ")) res = user.email_verification(code) if res: print("Вы успешно идентифицированы!") users.db.commit() else: print("К сожалению код не подходит!") if "да" == input("Введите да, чтобы отправить новый код: "): user.verification_code = random.randint(0, 9) fake_email_send(user.email, user.verification_code) user.save() print( f"На Ваш email отправлен код идентификации!\n\t(создан файл в текущей папке с именем={user.email})" ) users.db.commit() else: print("Выход") def open_account(): """Открытие счета""" user = login() if user is None: return db = SimpleDataBase() if user.name in db.accounts: print("Счет уже открыт") return db.accounts[user.name] = 0 db.commit() print("Счет успешно открыт") def print_account_details(): """Информация о пользователе""" user = login() if user is None: return print("\n - - - ") print("Ваше ФИО:", user.fio) print("Ваш email:", user.email) print( "Ваш статус:", user.is_verifyed and "Идентифицирован" or "Ожидает идентификации" ) db = SimpleDataBase() if user.name not in db.accounts: print("Счет не открыт") else: balance = round(db.accounts[user.name] / 100, 2) print(f"На балансе: {balance} руб.") if user.name not in db.rating: print("У Вас ещё нет рейтинга") else: print(f"Ваш рейтинг: {db.rating[user.name]} баллов") print("\n - - - ") def addition_from_card(): """Пополнение баланса с карты""" user = login() if user is None: return db = SimpleDataBase() if user.name not in db.accounts: print("Сначала нужно открыть счет") return is_valid = False while not is_valid: card = input("Введите номер карты: ") is_valid = validate_credit_card(card) if not is_valid: print("Номер не правильный") if "да" != input("Введите да, чтобы попытаться ещё раз: "): break if not is_valid: print("Выход") return amount = float(input("Введите сумму пополнения: ")) # рубли и копейки комбинируем в int, чтобы избежать проблем с float amount = int(amount * 100) db.accounts[user.name] += amount db.commit() print("Баланс пополнен успешно") def payment_for_phone(): """Оплата мобильного телефона""" user = login() if user is None: return db = SimpleDataBase() if user.name not in db.accounts: print("Сначала нужно открыть счет") return phone = input("Введите номер телефона: ") # TODO: Валидация номера телефона amount = float(input("Введите сумму платежа: ")) # рубли и копейки комбинируем в int, чтобы избежать проблем с float amount = int(amount * 100) db.accounts[user.name] -= amount db.commit() print("Платеж выполнен успешно, деньги (типа) отправлены на номер", phone) def quiz(): """Рейтинговая викторина""" print(' (тема: "Финансовая грамотность")') questions = [ ( """Финансовую защиту благосостояния семьи обеспечивает капитал: а) резервный б) текущий в) инвестиционный""", "а", ), ( """В соответствии с законом о страховании вкладчик получит право на возмещение по своим вкладам в банке в случае: а) потери доверия к банку у населения б) отзыва у банка лицензии в) повышения инфляции""", "б", ), ( """Инфляция: а) повышение заработной платы бюджетникам б) повышение покупательной способности денег в) снижение покупательной способности денег""", "в", ), ( """Кредит, выдаваемый под залог объекта, который приобретается (земельный участок, дом, квартира), называется: а) ипотечный б) потребительский в) целевой""", "а", ), ( """Счет до востребования с минимальной процентной ставкой, то есть текущий счет, открывается для карты: а) кредитной б) дебетовой с овердрафтом в) дебетовой""", "в", ), ( """Фондовый рынок — это место, где: а) продаются и покупаются строительные материалы б) продаются и покупаются ценные бумаги в) продаются и покупаются продукты питания""", "б", ), ( """Биржа — это место, где: а) продаются и покупаются автомобили б) продаются и покупаются ценные бумаги в) место заключения сделок между покупателями и продавцами""", "в", ), ( """Страховые выплаты компенсируются в случае: а) материального ущерба б) морального ущерба в) желания страхователя получить прибыль""", "а", ), ( """Выплачиваемая нынешним пенсионерам и формируемая пенсионерам будущим трудовая пенсия по старости, выплачиваемая государством: а) добавочная б) второстепенная в) базовая""", "в", ), ( """Выплачиваемая нынешним пенсионерам и формируемая пенсионерам будущим трудовая пенсия по старости, выплачиваемая государством: а) главная б) накопительная в) дополнительная""", "б", ), ] user = login() if user is None: return db = SimpleDataBase() if user.name in db.rating: print(f"Ваш текущий рейтинг: {db.rating[user.name]} баллов") if "выход" == input("Введите 'выход', для того чтобы выйти из тестирования: "): return score = 0 random.shuffle(questions) for question, q_answer in questions: print("- - - - - - - - - - - -\n") print(f"Вопрос: \n{question}\n") answer = input("Ваш ответ: ") if answer.strip().lower() == q_answer.strip().lower(): print(f"Верно") score += 1 else: print("Не верно") print(f"Результат: {score} балла") old_rating = db.rating.get(user.name, 0) if old_rating < score: db.rating[user.name] = score print(f"Ваш рейтинг обновлен и составляет: {score} балла") db.commit() else: print("Вы набрали меньший балл, и ваш рейтинг оставлен без изменений") def main(): modes = [ registration, login, print_account_details, open_account, user_verification, addition_from_card, payment_for_phone, quiz, logout, ] while True: print("Выберите режим:") for i, func in enumerate(modes, 1): print(f"{i} - {func.__doc__}") print(" [ Для выхода введите любую другую строку или нажмите Ввод ] ") i = input("Ваш выбор: ") if not i.isdigit() or not 0 < int(i) <= len(modes): print("Выход") return mode = modes[int(i) - 1] print(f"Режим: {mode.__doc__}") mode() # не пишу тут if __name__ == "__main__": # чтобы можно было вставить например в ipython или ptpython и т.д. main()