from datetime import date, datetime, timezone from flask.views import MethodView from flask_smorest import Blueprint from marshmallow import Schema, fields import re from ..decorators import ensure_logged_in from ..db_utils import get_transactions, get_account, get_accounts, insert_transaction, whose_account, insert_notification, get_forex_rate from ..models import Account, Notification, Transaction from ..utils.iban import check_iban from .. import decorators, returns bp = Blueprint('transactions', __name__, description='Bank transfers and other transactions') @bp.route('/') class TransactionsList(MethodView): class TransactionsParams(Schema): account_id = fields.Int(min=1) class TransactionsGetResponse(returns.SuccessSchema): transactions = fields.List(fields.Nested(Transaction.TransactionSchema)) @ensure_logged_in @bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') @bp.doc(security=[{'Token': []}]) @bp.arguments(TransactionsParams, as_kwargs=True, location='query') @bp.response(200, TransactionsGetResponse) def get(self, account_id: int): """Get transactions for a certain account""" if whose_account(account_id) != decorators.user_id: return returns.abort(returns.UNAUTHORIZED) # return returns.success( # transactions=[t.to_json() for t in get_transactions(account_id)] # ) return returns.success( transactions=get_transactions(account_id) ) class TransactionsCreateParams(Schema): account_id = fields.Int(min=1) destination_iban = fields.Str() amount = fields.Int(min=1) description = fields.Str(default='') class TransactionsCreateResponse(returns.SuccessSchema): transaction = fields.Nested(Transaction.TransactionSchema) @ensure_logged_in @bp.response(401, returns.ErrorSchema, description='Login failure or not allowed') @bp.response(404, returns.ErrorSchema, description='Destination account not found') @bp.response(422, returns.ErrorSchema, description='Invalid account') @bp.doc(security=[{'Token': []}]) @bp.arguments(TransactionsCreateParams, as_kwargs=True) @bp.response(200, TransactionsCreateResponse) def post(self, account_id: int, destination_iban: str, amount: int, description: str = ''): """Create a send_transfer transaction""" if whose_account(account_id) != decorators.user_id: return returns.abort(returns.UNAUTHORIZED) account: Account = get_account(account_id=account_id) if account is None: return returns.abort(returns.invalid_argument('account_id')) amount = -1 * abs(amount) if account.balance + amount < 0: return returns.abort(returns.NO_BALANCE) # Check if IBAN is valid destination_iban = re.sub(r'\s', '', destination_iban) if not check_iban(destination_iban): return returns.abort(returns.INVALID_IBAN) date = datetime.now(timezone.utc).astimezone() # Check if transaction is to another FoxBank account reverse_transaction = None if destination_iban[4:8] == 'FOXB': for acc in get_accounts(): if destination_iban == acc.iban: rate = get_forex_rate(account.currency, acc.currency) reverse_transaction = Transaction.new_transaction( date_time=date, transaction_type='receive_transfer', status='processed', other_party={'iban': account.iban,}, extra={ 'currency': acc.currency, 'amount': int(-amount * rate), 'description': description, 'originalAmount': -amount, 'originalCurrency': account.currency, }, ) insert_transaction(acc.id, reverse_transaction) formatted_iban = re.sub(r'(.{4})', r'\1 ', account.iban).strip() notification = Notification.new_notification( body=f'Transfer of {-amount // 100}.{-amount % 100:0>2} {account.currency} received from {formatted_iban} in your {acc.custom_name or acc.account_type} account.', date_time=date, read=False, ) insert_notification(whose_account(acc.id), notification) break else: return returns.abort(returns.NOT_FOUND) transaction = Transaction.new_transaction( date_time=date, transaction_type='send_transfer', status=('processed' if reverse_transaction is not None else 'pending'), other_party={'iban': destination_iban,}, extra={ 'currency': account.currency, 'amount': amount, 'description': description, }, ) insert_transaction(account_id, transaction) return returns.success(transaction=transaction)