py_stepik/bank.py

505 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Имитация функций банка
# задача: 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("Введите пароль: ")
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(' (тема: "Финансовая грамотность")')
# источник: https://liketest.ru/finansy/test-s-otvetami-finansovaya-gramotnost.html
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()