diff --git a/backend/payments/.env.example b/backend/payments/.env.example index 859d5c80a..fff3428ab 100644 --- a/backend/payments/.env.example +++ b/backend/payments/.env.example @@ -2,7 +2,7 @@ SECRET_KEY='django' DEBUG=True ALLOWED_HOSTS='*' JWT_SECRET_KEY='Token' -SHOP_AŠ”COUNT_ID=111111 +SHOP_ACCOUNT_ID=111111 SHOP_SECRET_KEY='test_1111111111' ROLLBAR_ACCESS_TOKEN='1111111111' POSTGRES_PASSWORD=postgres diff --git a/backend/payments/.gitignore b/backend/payments/.gitignore index 007baeb58..3fc6b2efa 100644 --- a/backend/payments/.gitignore +++ b/backend/payments/.gitignore @@ -1 +1,2 @@ /payment_response_example.py +/static \ No newline at end of file diff --git a/backend/payments/.pre-commit-config.yaml b/backend/payments/.pre-commit-config.yaml index 461effb32..d8d729f3d 100644 --- a/backend/payments/.pre-commit-config.yaml +++ b/backend/payments/.pre-commit-config.yaml @@ -1,25 +1,34 @@ +files: 'backend/payments*' repos: -- repo: https://github.com/pre-commit/mirrors-isort - rev: "v5.9.3" - hooks: - - id: isort - args: [--profile=black] - -- repo: https://github.com/pycqa/flake8.git - rev: 6.0.0 - hooks: - - id: flake8 - args: [ - "--ignore=E501" - ] - additional_dependencies: [ - "flake8-bugbear", - "flake8-builtins", - "pep8-naming", - "flake8-commas", - "flake8-quotes", - "flake8-todo", - "flake8-django", - "flake8-cognitive-complexity", - "flake8-isort", - ] + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + args: [--skip-string-normalization, --line-length=100] + language_version: python3.11 + - repo: https://github.com/asottile/add-trailing-comma + rev: v2.4.0 + hooks: + - id: add-trailing-comma + - repo: https://github.com/timothycrosley/isort + rev: 5.12.0 + hooks: + - id: isort + args: [--profile=black] + - repo: https://github.com/pycqa/flake8.git + rev: 6.0.0 + hooks: + - id: flake8 + args: [ + "--max-line-length=100" + ] + additional_dependencies: [ + "flake8-bugbear", + "flake8-builtins", + "pep8-naming", + "flake8-commas", + "flake8-quotes", + "flake8-todo", + "flake8-django", + "flake8-cognitive-complexity", + ] \ No newline at end of file diff --git a/backend/payments/Dockerfiles/scripts/entrypoint.sh b/backend/payments/Dockerfiles/scripts/entrypoint.sh index 0e62333fc..c04f2ac38 100644 --- a/backend/payments/Dockerfiles/scripts/entrypoint.sh +++ b/backend/payments/Dockerfiles/scripts/entrypoint.sh @@ -48,7 +48,6 @@ done >&2 echo "Redis is available" python manage.py collectstatic --noinput -python manage.py makemigrations --noinput python manage.py migrate gunicorn config.wsgi:application --bind :8000 -k gevent diff --git a/backend/payments/README.md b/backend/payments/README.md index e19275393..1d6012f67 100644 --- a/backend/payments/README.md +++ b/backend/payments/README.md @@ -79,7 +79,7 @@ Subsequently, all indexed files will be checked with `Flake8` before committing. To check with `Flake8` the files indexed for the commit run: ```shell -pre-commit run +pre-commit run --all-files ``` If it's necessary to skip using `pre-commit hook`, the commit should run with the `-n` or `--no-verify` flag: ```shell diff --git a/backend/payments/apps/base/fields.py b/backend/payments/apps/base/fields.py index 91ca6071d..abba83940 100644 --- a/backend/payments/apps/base/fields.py +++ b/backend/payments/apps/base/fields.py @@ -6,14 +6,18 @@ class MoneyField(DecimalField): def __init__( self, - verbose_name=None, - name=None, - max_digits=settings.MAX_BALANCE_DIGITS, - decimal_places=2, - **kwargs, + verbose_name=None, + name=None, + max_digits=settings.MAX_BALANCE_DIGITS, + decimal_places=2, + **kwargs, ): super().__init__( - verbose_name, name, max_digits, decimal_places, **kwargs, + verbose_name, + name, + max_digits, + decimal_places, + **kwargs, ) @@ -26,5 +30,8 @@ def __init__( **kwargs, ): super().__init__( - max_digits, decimal_places, min_value, **kwargs, + max_digits, + decimal_places, + min_value, + **kwargs, ) diff --git a/backend/payments/apps/base/utils.py b/backend/payments/apps/base/utils.py index e62fddd17..a1ffc5a6b 100644 --- a/backend/payments/apps/base/utils.py +++ b/backend/payments/apps/base/utils.py @@ -2,21 +2,20 @@ import rollbar from _decimal import Decimal +from apps.payment_accounts.models import Account, BalanceChange from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db import transaction from django.db.models import Model -from apps.payment_accounts.models import Account, BalanceChange - DjangoModel = TypeVar('DjangoModel', bound=Model) def parse_model_instance( - *, - django_model: type[DjangoModel], - error_message: str, - pk: int, + *, + django_model: type[DjangoModel], + error_message: str, + pk: int, ) -> DjangoModel | None: try: django_model_instance = django_model.objects.get(pk=pk) @@ -28,9 +27,9 @@ def parse_model_instance( def increase_user_balance( - *, - balance_change_object: BalanceChange, - amount: Decimal, + *, + balance_change_object: BalanceChange, + amount: Decimal, ) -> None: with transaction.atomic(): balance_change_object.is_accepted = True @@ -41,10 +40,11 @@ def increase_user_balance( pk=balance_change_object.account_id.pk, amount=Decimal(amount), ) - rollbar.report_message(( - f'Deposit {balance_change_object.amount} {settings.DEFAULT_CURRENCY} ' - f'to user account {balance_change_object.account_id}' - ), + rollbar.report_message( + ( + f'Deposit {balance_change_object.amount} {settings.DEFAULT_CURRENCY} ' + f'to user account {balance_change_object.account_id}' + ), 'info', ) @@ -62,9 +62,10 @@ def decrease_user_balance(*, account: Account, amount: Decimal): pk=account.pk, amount=Decimal(amount), ) - rollbar.report_message(( - f'Withdraw {balance_change_object.amount} {settings.DEFAULT_CURRENCY} ' - f'from user account {balance_change_object.account_id}' - ), + rollbar.report_message( + ( + f'Withdraw {balance_change_object.amount} {settings.DEFAULT_CURRENCY} ' + f'from user account {balance_change_object.account_id}' + ), 'info', ) diff --git a/backend/payments/apps/external_payments/schemas.py b/backend/payments/apps/external_payments/schemas.py index 3fd9b88e6..04126ebde 100644 --- a/backend/payments/apps/external_payments/schemas.py +++ b/backend/payments/apps/external_payments/schemas.py @@ -3,11 +3,10 @@ from decimal import Decimal from uuid import UUID +from apps.base.schemas import URL from dataclasses_json import config, dataclass_json from django.conf import settings -from apps.base.schemas import URL - class PaymentResponseStatuses(enum.Enum): succeeded = 'payment.succeeded' diff --git a/backend/payments/apps/external_payments/serializers.py b/backend/payments/apps/external_payments/serializers.py index 6ca8bcc3d..5668ec805 100644 --- a/backend/payments/apps/external_payments/serializers.py +++ b/backend/payments/apps/external_payments/serializers.py @@ -1,8 +1,7 @@ +from apps.base.fields import MoneySerializerField from rest_enumfield import EnumField from rest_framework import serializers -from apps.base.fields import MoneySerializerField - from .schemas import PaymentResponseStatuses, PaymentTypes @@ -18,6 +17,7 @@ def rename_builtin_names(self, old_data: dict, postfix: str = '_'): given postfix. e.g. field name is `id` and postfix is `_yookassa`: id -> id_yookassa """ + def generate_new_key(): builtin_names = ('id', 'type', 'object') if key in builtin_names: diff --git a/backend/payments/apps/external_payments/services/accept_payment.py b/backend/payments/apps/external_payments/services/accept_payment.py index 3b8cb07e7..9a5f15297 100644 --- a/backend/payments/apps/external_payments/services/accept_payment.py +++ b/backend/payments/apps/external_payments/services/accept_payment.py @@ -1,13 +1,15 @@ from decimal import Decimal import rollbar - from apps.base import utils -from apps.external_payments.schemas import (PaymentResponseStatuses, - YookassaPaymentResponse) +from apps.external_payments.schemas import ( + PaymentResponseStatuses, + YookassaPaymentResponse, +) from apps.payment_accounts.models import Account, BalanceChange -from apps.payment_accounts.services.payment_commission import \ - calculate_payment_without_commission +from apps.payment_accounts.services.payment_commission import ( + calculate_payment_without_commission, +) from apps.transactions.models import Invoice from . import invoice_execution as pay_proc @@ -64,9 +66,7 @@ def _run_payment_acceptance(self): def parse_user_account(self): return utils.parse_model_instance( django_model=Account, - error_message=( - f"Can't get user account instance for user id {self.account_id}" - ), + error_message=(f"Can't get user account instance for user id {self.account_id}"), pk=self.account_id, ) @@ -79,9 +79,7 @@ def __init__(self, yookassa_response: YookassaPaymentResponse): def _parse_balance_object(self) -> BalanceChange | None: return utils.parse_model_instance( django_model=BalanceChange, - error_message=( - f"Can't get payment instance for payment id {self.payment_body.id_}" - ), + error_message=(f"Can't get payment instance for payment id {self.payment_body.id_}"), pk=int(self.payment_body.metadata['balance_change_id']), ) @@ -154,9 +152,10 @@ def _is_commission_correct(self): def execute_invoice_operations( - *, invoice_instance: Invoice, - payer_account: Account, - decrease_amount: Decimal, + *, + invoice_instance: Invoice, + payer_account: Account, + decrease_amount: Decimal, ): invoice_executioner = pay_proc.InvoiceExecution(invoice_instance) invoice_executioner.process_invoice_transactions() diff --git a/backend/payments/apps/external_payments/services/create_payment.py b/backend/payments/apps/external_payments/services/create_payment.py index ee3410c02..9878d12b5 100644 --- a/backend/payments/apps/external_payments/services/create_payment.py +++ b/backend/payments/apps/external_payments/services/create_payment.py @@ -1,15 +1,13 @@ -from yookassa import Payment - from apps.base.schemas import URL +from yookassa import Payment from .. import schemas def get_yookassa_payment_url( - payment_data: schemas.PaymentCreateDataClass, - metadata: dict, + payment_data: schemas.PaymentCreateDataClass, + metadata: dict, ) -> URL: - yookassa_payment_info = schemas.YookassaPaymentCreate( amount=schemas.AmountDataClass( value=payment_data.payment_amount, diff --git a/backend/payments/apps/external_payments/services/invoice_execution.py b/backend/payments/apps/external_payments/services/invoice_execution.py index 75bcc5f22..b74fc818c 100644 --- a/backend/payments/apps/external_payments/services/invoice_execution.py +++ b/backend/payments/apps/external_payments/services/invoice_execution.py @@ -1,8 +1,7 @@ from datetime import datetime -from django.conf import settings - from apps.transactions.models import Invoice, Transaction, TransactionHistory +from django.conf import settings from ..exceptions import ExtraTransactionHistoriesError from ..tasks import get_item_for_self_user, gift_item_to_other_user @@ -33,7 +32,7 @@ def process_transaction(self, invoice_transaction: Transaction) -> None: @staticmethod def get_transaction_execution_date_time( - invoice_transaction: Transaction, + invoice_transaction: Transaction, ) -> datetime: transactions_history = invoice_transaction.transactions_history.all() if len(transactions_history) > 1: diff --git a/backend/payments/apps/payment_accounts/migrations/0001_initial.py b/backend/payments/apps/payment_accounts/migrations/0001_initial.py index 11b217674..b76bbb409 100644 --- a/backend/payments/apps/payment_accounts/migrations/0001_initial.py +++ b/backend/payments/apps/payment_accounts/migrations/0001_initial.py @@ -6,30 +6,83 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( name='Account', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ('user_uid', models.UUIDField(db_index=True, editable=False, unique=True)), - ('balance', models.DecimalField(decimal_places=2, default=0, max_digits=11, validators=[django.core.validators.MinValueValidator(0, message='Insufficient Funds')])), + ( + 'balance', + models.DecimalField( + decimal_places=2, + default=0, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator( + 0, + message='Insufficient Funds', + ), + ], + ), + ), ], ), migrations.CreateModel( name='BalanceChange', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, editable=False, max_digits=11, validators=[django.core.validators.MinValueValidator(0, message='Should be positive value')])), + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + editable=False, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator( + 0, + message='Should be positive value', + ), + ], + ), + ), ('date_time_creation', models.DateTimeField(auto_now_add=True, db_index=True)), ('is_accepted', models.BooleanField(default=False)), - ('operation_type', models.CharField(choices=[('WD', 'WITHDRAW'), ('DT', 'DEPOSIT')], max_length=20)), - ('account_id', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='balance_changes', to='payment_accounts.account')), + ( + 'operation_type', + models.CharField( + choices=[('WD', 'WITHDRAW'), ('DT', 'DEPOSIT')], + max_length=20, + ), + ), + ( + 'account_id', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='balance_changes', + to='payment_accounts.account', + ), + ), ], options={ 'ordering': ['-date_time_creation'], diff --git a/backend/payments/apps/payment_accounts/migrations/0002_rename_user_uid_account_user_uuid_and_more.py b/backend/payments/apps/payment_accounts/migrations/0002_rename_user_uid_account_user_uuid_and_more.py new file mode 100644 index 000000000..f55cd8e11 --- /dev/null +++ b/backend/payments/apps/payment_accounts/migrations/0002_rename_user_uid_account_user_uuid_and_more.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.7 on 2023-04-09 13:17 + +import apps.base.fields +import django.core.validators +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('payment_accounts', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='account', + old_name='user_uid', + new_name='user_uuid', + ), + migrations.AlterField( + model_name='account', + name='balance', + field=apps.base.fields.MoneyField( + decimal_places=2, + default=0, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator(0, message='Insufficient Funds'), + ], + ), + ), + migrations.AlterField( + model_name='balancechange', + name='amount', + field=apps.base.fields.MoneyField( + decimal_places=2, + default=0, + editable=False, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator(0, message='Should be positive value'), + ], + ), + ), + ] diff --git a/backend/payments/apps/payment_accounts/models.py b/backend/payments/apps/payment_accounts/models.py index 24492528a..4f02e092c 100644 --- a/backend/payments/apps/payment_accounts/models.py +++ b/backend/payments/apps/payment_accounts/models.py @@ -2,12 +2,11 @@ from decimal import Decimal +from apps.base.fields import MoneyField from django.core.validators import MinValueValidator from django.db import models, transaction from django.shortcuts import get_object_or_404 -from apps.base.fields import MoneyField - def is_amount_positive(method): def wrapper(cls, *args, **kwargs): @@ -15,6 +14,7 @@ def wrapper(cls, *args, **kwargs): if amount < 0: raise ValueError('Should be positive value') return method(cls, *args, **kwargs) + return wrapper diff --git a/backend/payments/apps/payment_accounts/serializers.py b/backend/payments/apps/payment_accounts/serializers.py index df5b57797..aff0cc103 100644 --- a/backend/payments/apps/payment_accounts/serializers.py +++ b/backend/payments/apps/payment_accounts/serializers.py @@ -1,10 +1,9 @@ +from apps.external_payments.schemas import PaymentTypes from django.conf import settings from django.core.validators import MinValueValidator from rest_enumfield import EnumField from rest_framework import serializers -from apps.external_payments.schemas import PaymentTypes - class PaymentCommissionSerializer(serializers.Serializer): payment_amount = serializers.DecimalField( diff --git a/backend/payments/apps/payment_accounts/services/balance_change.py b/backend/payments/apps/payment_accounts/services/balance_change.py index aff80fb65..d2347c97a 100644 --- a/backend/payments/apps/payment_accounts/services/balance_change.py +++ b/backend/payments/apps/payment_accounts/services/balance_change.py @@ -1,13 +1,12 @@ from apps.base.schemas import URL from apps.external_payments.schemas import PaymentCreateDataClass -from apps.external_payments.services.create_payment import \ - get_yookassa_payment_url +from apps.external_payments.services.create_payment import get_yookassa_payment_url from ..models import Account, BalanceChange def request_balance_deposit_url( - payment_data: PaymentCreateDataClass, + payment_data: PaymentCreateDataClass, ) -> URL: user_account, _ = Account.objects.get_or_create( user_uuid=payment_data.user_uuid, diff --git a/backend/payments/apps/payment_accounts/services/payment_commission.py b/backend/payments/apps/payment_accounts/services/payment_commission.py index dc4d1e7b8..7bf4f4e47 100644 --- a/backend/payments/apps/payment_accounts/services/payment_commission.py +++ b/backend/payments/apps/payment_accounts/services/payment_commission.py @@ -17,8 +17,8 @@ def get_commission_percent(payment_type: PaymentTypes) -> Decimal: def calculate_payment_with_commission( - payment_type: PaymentTypes, - payment_amount: Decimal, + payment_type: PaymentTypes, + payment_amount: Decimal, ) -> Decimal: commission = get_commission_percent(payment_type) return (payment_amount * (1 / (1 - commission / 100))).quantize( @@ -28,8 +28,8 @@ def calculate_payment_with_commission( def calculate_payment_without_commission( - payment_type: PaymentTypes, - payment_amount: Decimal, + payment_type: PaymentTypes, + payment_amount: Decimal, ) -> Decimal: commission = get_commission_percent(payment_type) return (payment_amount * ((100 - commission) / 100)).quantize( diff --git a/backend/payments/apps/payment_accounts/views.py b/backend/payments/apps/payment_accounts/views.py index b37fbae75..240f196c3 100644 --- a/backend/payments/apps/payment_accounts/views.py +++ b/backend/payments/apps/payment_accounts/views.py @@ -1,11 +1,9 @@ import rollbar +from apps.external_payments.schemas import PaymentCreateDataClass, YookassaPaymentInfo from rest_framework import status from rest_framework.generics import CreateAPIView from rest_framework.response import Response -from apps.external_payments.schemas import (PaymentCreateDataClass, - YookassaPaymentInfo) - from . import serializers from .services.balance_change import request_balance_deposit_url from .services.payment_commission import calculate_payment_with_commission diff --git a/backend/payments/apps/transactions/migrations/0001_initial.py b/backend/payments/apps/transactions/migrations/0001_initial.py index bcbf1fbf3..094b66053 100644 --- a/backend/payments/apps/transactions/migrations/0001_initial.py +++ b/backend/payments/apps/transactions/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -17,23 +16,96 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Transaction', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('item_price', models.DecimalField(decimal_places=2, max_digits=11, validators=[django.core.validators.MinValueValidator(0, message='Should be positive value'), django.core.validators.MaxValueValidator(10000, message='Should be not greater than 10000')])), + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'item_price', + models.DecimalField( + decimal_places=2, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator( + 0, + message='Should be positive value', + ), + django.core.validators.MaxValueValidator( + 10000, + message='Should be not greater than 10000', + ), + ], + ), + ), ('item_uid', models.UUIDField(db_index=True, editable=False)), ('is_frozen', models.BooleanField(default=False)), ('is_accepted', models.BooleanField(default=False)), - ('account_from', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions_account_from', to='payment_accounts.account')), - ('account_to', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions_account_to', to='payment_accounts.account')), + ( + 'account_from', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='transactions_account_from', + to='payment_accounts.account', + ), + ), + ( + 'account_to', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='transactions_account_to', + to='payment_accounts.account', + ), + ), ], ), migrations.CreateModel( name='TransferHistory', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, editable=False, max_digits=11, validators=[django.core.validators.MinValueValidator(0, message='Should be positive value')])), + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + editable=False, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator( + 0, + message='Should be positive value', + ), + ], + ), + ), ('date_time_creation', models.DateTimeField(auto_now_add=True, db_index=True)), - ('account_from', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='history_accounts_from', to='payment_accounts.account')), - ('account_to', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='history_accounts_to', to='payment_accounts.account')), + ( + 'account_from', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='history_accounts_from', + to='payment_accounts.account', + ), + ), + ( + 'account_to', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='history_accounts_to', + to='payment_accounts.account', + ), + ), ], options={ 'ordering': ['-date_time_creation'], @@ -42,10 +114,32 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TransactionHistory', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ('date_time_creation', models.DateTimeField(auto_now_add=True, db_index=True)), - ('operation_type', models.CharField(choices=[('CT', 'CREATED'), ('CD', 'COMPLETED')], max_length=50)), - ('transaction_id', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='transactions_history', to='transactions.transaction')), + ( + 'operation_type', + models.CharField( + choices=[('CT', 'CREATED'), ('CD', 'COMPLETED')], + max_length=50, + ), + ), + ( + 'transaction_id', + models.ForeignKey( + editable=False, + on_delete=django.db.models.deletion.PROTECT, + related_name='transactions_history', + to='transactions.transaction', + ), + ), ], options={ 'ordering': ['-date_time_creation'], diff --git a/backend/payments/apps/transactions/migrations/0002_rename_item_uid_transaction_item_uuid_and_more.py b/backend/payments/apps/transactions/migrations/0002_rename_item_uid_transaction_item_uuid_and_more.py new file mode 100644 index 000000000..bf586098a --- /dev/null +++ b/backend/payments/apps/transactions/migrations/0002_rename_item_uid_transaction_item_uuid_and_more.py @@ -0,0 +1,75 @@ +# Generated by Django 4.1.7 on 2023-04-09 13:17 + +import uuid + +import apps.base.fields +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('payment_accounts', '0002_rename_user_uid_account_user_uuid_and_more'), + ('transactions', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='transaction', + old_name='item_uid', + new_name='item_uuid', + ), + migrations.AddField( + model_name='transaction', + name='developer_account', + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='transactions', + to='payment_accounts.account', + ), + ), + migrations.AlterField( + model_name='transaction', + name='item_price', + field=apps.base.fields.MoneyField( + decimal_places=2, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator(0, message='Should be positive value'), + django.core.validators.MaxValueValidator( + 10000, + message='Should be not greater than 10000', + ), + ], + ), + ), + migrations.AlterField( + model_name='transferhistory', + name='amount', + field=apps.base.fields.MoneyField( + decimal_places=2, + editable=False, + max_digits=11, + validators=[ + django.core.validators.MinValueValidator(0, message='Should be positive value'), + ], + ), + ), + migrations.CreateModel( + name='Invoice', + fields=[ + ( + 'invoice_id', + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ('transactions', models.ManyToManyField(to='transactions.transaction')), + ], + ), + ] diff --git a/backend/payments/apps/transactions/models.py b/backend/payments/apps/transactions/models.py index e77713e5b..b9c4cecbb 100644 --- a/backend/payments/apps/transactions/models.py +++ b/backend/payments/apps/transactions/models.py @@ -2,21 +2,24 @@ import uuid -from django.core.validators import MaxValueValidator, MinValueValidator -from django.db import models -from marshmallow.fields import Decimal - from apps.base.fields import MoneyField from apps.payment_accounts.models import Account from apps.transactions.exceptions import DuplicateError +from django.core.validators import MaxValueValidator, MinValueValidator +from django.db import models +from marshmallow.fields import Decimal class TransferHistory(models.Model): account_from = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name='history_accounts_from', + Account, + on_delete=models.PROTECT, + related_name='history_accounts_from', ) account_to = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name='history_accounts_to', + Account, + on_delete=models.PROTECT, + related_name='history_accounts_to', ) amount = MoneyField( validators=[MinValueValidator(0, message='Should be positive value')], diff --git a/backend/payments/apps/transactions/schemas.py b/backend/payments/apps/transactions/schemas.py index f5b50b905..0347c1eda 100644 --- a/backend/payments/apps/transactions/schemas.py +++ b/backend/payments/apps/transactions/schemas.py @@ -22,7 +22,4 @@ class IncomeData: return_url: URL def total_price(self): - return sum( - item_payment_data.price for item_payment_data - in self.items_payment_data - ) + return sum(item_payment_data.price for item_payment_data in self.items_payment_data) diff --git a/backend/payments/apps/transactions/serializers.py b/backend/payments/apps/transactions/serializers.py index e74e20d39..b625fbb10 100644 --- a/backend/payments/apps/transactions/serializers.py +++ b/backend/payments/apps/transactions/serializers.py @@ -1,8 +1,7 @@ -from rest_enumfield import EnumField -from rest_framework import serializers - from apps.base.fields import MoneySerializerField from apps.external_payments.schemas import PaymentTypes +from rest_enumfield import EnumField +from rest_framework import serializers class ItemPaymentData(serializers.Serializer): diff --git a/backend/payments/apps/transactions/services/purchase_items.py b/backend/payments/apps/transactions/services/purchase_items.py index e6b825f0f..0d483db96 100644 --- a/backend/payments/apps/transactions/services/purchase_items.py +++ b/backend/payments/apps/transactions/services/purchase_items.py @@ -1,25 +1,24 @@ -from django.core.exceptions import ValidationError - from apps.base.schemas import URL from apps.external_payments.schemas import PaymentCreateDataClass, PaymentTypes -from apps.external_payments.services.accept_payment import \ - execute_invoice_operations -from apps.external_payments.services.create_payment import \ - get_yookassa_payment_url +from apps.external_payments.services.accept_payment import execute_invoice_operations +from apps.external_payments.services.create_payment import get_yookassa_payment_url from apps.payment_accounts.models import Account, BalanceChange +from django.core.exceptions import ValidationError from ..models import Invoice, Transaction, TransactionHistory from ..schemas import IncomeData, ItemPaymentData def request_purchase_items( - income_data: IncomeData, + income_data: IncomeData, ) -> URL | str: user_account, _ = Account.objects.get_or_create( user_uuid=income_data.user_uuid, ) - if income_data.payment_type == PaymentTypes.from_balance \ - and not is_enough_funds(user_account, income_data): + if income_data.payment_type == PaymentTypes.from_balance and not is_enough_funds( + user_account, + income_data, + ): return 'Not enough funds on balance' invoice_creator = InvoiceCreator(income_data, user_account) @@ -85,8 +84,8 @@ def create_invoice_instance(self) -> int: @staticmethod def create_transaction_instance( - payer_account: Account, - item_payment_data: ItemPaymentData, + payer_account: Account, + item_payment_data: ItemPaymentData, ) -> Transaction: account_to, _ = Account.objects.get_or_create( user_uuid=item_payment_data.owner_uuid, diff --git a/backend/payments/apps/transactions/views.py b/backend/payments/apps/transactions/views.py index 51408b38d..df164d698 100644 --- a/backend/payments/apps/transactions/views.py +++ b/backend/payments/apps/transactions/views.py @@ -1,10 +1,9 @@ import rollbar +from apps.transactions.schemas import IncomeData from dacite import MissingValueError, from_dict from rest_framework import status, viewsets from rest_framework.response import Response -from apps.transactions.schemas import IncomeData - from .serializers import PurchaseItemsSerializer from .services.purchase_items import request_purchase_items diff --git a/backend/payments/config/celery.py b/backend/payments/config/celery.py index 267170852..7e7588419 100644 --- a/backend/payments/config/celery.py +++ b/backend/payments/config/celery.py @@ -2,7 +2,6 @@ import time from celery import Celery - from config import settings os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') diff --git a/backend/payments/config/settings.py b/backend/payments/config/settings.py index eb35fabfb..6d0b689f4 100644 --- a/backend/payments/config/settings.py +++ b/backend/payments/config/settings.py @@ -21,12 +21,10 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - # 3rd party 'django_extensions', 'drf_spectacular', 'rest_framework', - # local 'apps.payment_accounts', 'apps.transactions', diff --git a/backend/payments/flake8-requirements.txt b/backend/payments/flake8-requirements.txt index 6311aba03..ad5cda8a6 100644 --- a/backend/payments/flake8-requirements.txt +++ b/backend/payments/flake8-requirements.txt @@ -1,12 +1,11 @@ -flake8==3.9.2 +flake8==5.0.4 flake8-bugbear==23.3.12 -flake8-builtins==1.5.3 -pep8-naming==0.12.1 +flake8-builtins==2.1.0 +pep8-naming==0.13.3 flake8-commas==2.1.0 flake8-variables-names==0.0.5 -flake8-quotes==3.3.1 +flake8-quotes==3.3.2 flake8-todo==0.7 -flake8-django==1.1.2 +flake8-django==1.1.5 flake8-cognitive-complexity==0.1.0 -flake8-isort==4.1.1 pre-commit==3.1.1 \ No newline at end of file diff --git a/backend/payments/pyproject.toml b/backend/payments/pyproject.toml new file mode 100644 index 000000000..b1af5186c --- /dev/null +++ b/backend/payments/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' \ No newline at end of file