Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…nto dev
  • Loading branch information
menma331 committed Feb 13, 2024
2 parents 9040159 + d0e488e commit 35d52b4
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 53 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ TASK_SERIALIZER=
TIMEZONE=

YOOKASSA_SHOP_ID=
YOOKASSA_SECRET_KEY=
YOOKASSA_SECRET_KEY=
YOOKASSA_RETURN_URL=
72 changes: 72 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ------------------------------------- INIT ----------------------------------------
.PHONY: init
init:
# Установка виртуального окружения.
python -m venv venv
# Активация виртуального окружения.
source venv/bin/activate
# Установка зависимостей проекта.
pip install -r requirements.txt
# -----------------------------------------------------------------------------------


# ----------------------------- MIGRATIONS AND STATIC -------------------------------
# Применить миграции и отправить.
.PHONY: migrations
migrations:
python ./manage.py makemigrations
python ./manage.py migrate

# Сбор статик файлов.
.PHONY: static
static:
python ./manage.py collectstatic
# -----------------------------------------------------------------------------------


# ----------------------------------- LOAD DATA -------------------------------------
# Загрузить данные в базу данных.
.PHONY: load
load:
python ./manage.py loaddata fixtures/categories.json
python ./manage.py loaddata fixtures/providers.json
python ./manage.py loaddata fixtures/products.json
python ./manage.py loaddata fixtures/products_description.json
python ./manage.py loaddata fixtures/products_feature.json
python ./manage.py loaddata fixtures/products_images.json
# -----------------------------------------------------------------------------------


# ------------------------------------- SUPERUSER -----------------------------------
# Создание суперпользователя.
.PHONY: createsuperuser
createsuperuser:
python ./manage.py createsuperuser
# -----------------------------------------------------------------------------------


# ------------------------------ RUN AND STOP SERVER --------------------------------
# Запуск сервера разработки.
.PHONY: run
run:
python manage.py runserver

# Остановка сервера разработки.
.PHONY: stop
stop:
pkill -f "python ./manage.py runserver"
# -----------------------------------------------------------------------------------


# ------------------------------------- CELERY --------------------------------------
# Запустить Celery. Убедитесь в том, что у вас запущен Redis (на WSL2, если
# у вас Windows) и RabbitMQ на вашем локальном хосте!
.PHONY: celery
celery:
celery --app=config worker --loglevel=info --pool=solo

# Запустить Celery Beat для резервной копии Базы Данных.
.PHONY: beat
beat:
celery -A config beat -l info
# -----------------------------------------------------------------------------------
2 changes: 2 additions & 0 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from carts.urls import urlpatterns as cart_urls
from delivers.urls import urlpatterns as deliver_urls
from orders.urls import urlpatterns as order_urls
from payments.urls import urlpatterns as payments_urls


app_name = 'api'
Expand All @@ -16,3 +17,4 @@
urlpatterns += cart_urls
urlpatterns += deliver_urls
urlpatterns += order_urls
urlpatterns += payments_urls
6 changes: 6 additions & 0 deletions common/views/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from djoser.views import UserViewSet
from rest_framework import mixins
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import GenericViewSet

Expand Down Expand Up @@ -67,6 +68,11 @@ class ExtendedUserViewSet(ExtendedView, UserViewSet):
pass


class ExtendedCreateAPIView(ExtendedView, CreateAPIView):
"""Расширенное представление для создания."""
pass


class ListViewSet(ExtendedGenericViewSet, mixins.ListModelMixin):
"""
Класс включающий базовый набор поведения generic view и включающий
Expand Down
74 changes: 65 additions & 9 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
]

MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
Expand All @@ -52,11 +53,10 @@
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',

# package middlewares
'corsheaders.middleware.CorsMiddleware',
'crum.CurrentRequestUserMiddleware',

# package middlewares
'debug_toolbar.middleware.DebugToolbarMiddleware',
'request_logging.middleware.LoggingMiddleware',
# 'customers.middleware.ActiveUserMiddleware',
]

Expand Down Expand Up @@ -171,8 +171,8 @@
],

'SWAGGER_UI_SETTINGS': {
'DeepLinking': True,
'DisplayOperationId': True,
'deepLinking': True,
'displayOperationId': True,
'syntaxHighlight.active': True,
'syntaxHighlight.theme': 'arta',
'defaultModelsExpandDepth': -1,
Expand Down Expand Up @@ -297,6 +297,8 @@
YOOKASSA_RETURN_URL = env.str(var='YOOKASSA_RETURN_URL')
# endregion -------------------------------------------------------------------------


# region ------------------------- DJANGO DEBUGGER ----------------------------------
if DEBUG:
# region ------------------------- SENTRY ---------------------------------------
sentry_sdk.init(
Expand All @@ -311,9 +313,63 @@
)
# endregion ---------------------------------------------------------------------

INTERNAL_IPS = [
'127.0.0.1',
]
# endregion -------------------------------------------------------------------------

# region ------------------------- DJANGO DEBUGGER ----------------------------------
INTERNAL_IPS = [
'127.0.0.1',
]

# region ------------------------------- LOGGING ------------------------------------
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
'logstash': {
'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
},
'json': {
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(levelname)s %(message)s',
'json_indent': None,
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'logstash': {
'level': 'INFO',
'formatter': 'json',
'class': 'logstash.TCPLogstashHandler',
'host': 'localhost',
'port': 50000, # Значение по умолчанию: 5959
'version': 1,
'message_type': 'django',
'fqdn': False, # Полное доменное имя. Значение по умолчанию: false.
'tags': ['django.request'], # Список тегов. По умолчанию: None.
},

},
'loggers': {
'django.request': {
'handlers': ['console', 'logstash'],
'level': 'DEBUG',
'propagate': False,
},
'request_logging': {
'handlers': ['console', 'logstash'],
'level': 'DEBUG',
'propagate': False,
},
},
'root': {
'handlers': ['console'],
'level': 'INFO',
},
}
# endregion -------------------------------------------------------------------------
2 changes: 1 addition & 1 deletion orders/models/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Order(BaseModel):
class Status(models.TextChoices):
"""Статус заказа."""
CREATE = 'CR', _('Заказ создан')
WORK = 'WO', _('В работе.')
WORK = 'WO', _('В работе')
COMPLETED = 'CO', _('Завершенный')
CANCELLED = 'CA', _('Отмененный')

Expand Down
21 changes: 0 additions & 21 deletions orders/views/orders.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
from django.db import transaction
from drf_spectacular.utils import extend_schema_view, extend_schema
from rest_framework import permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework_simplejwt import authentication

from common.views import mixins
from ..models.orders import Order
from ..serializers.api import orders as orders_s
from ..services.orders import OrderCreateService
from payments.services.webhooks import PaymentConfirmWebHookService

if TYPE_CHECKING:
from django.db.models import QuerySet
Expand All @@ -28,10 +26,6 @@
summary='Создать заказ',
tags=['Заказ'],
),
payment_confirmation=extend_schema(
summary='Обработать платеж с помощью WebHook',
tags=['Заказ'],
),
)
class OrderMakingViewSet(mixins.CreateViewSet):
"""Представление оформления заказа."""
Expand Down Expand Up @@ -64,21 +58,6 @@ def create(self, request: Request, *args: None, **kwargs: None) -> Response:
status=status.HTTP_201_CREATED,
)

@action(methods=['POST'], detail=False)
def payment_confirmation(self, request: Request) -> Response:
"""Обработка платежа с помощью webhook-а."""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
with transaction.atomic():
serializer.save()
payment_confirm_webhook = PaymentConfirmWebHookService(request=request)
payment_confirm_webhook.execute()

return Response(
data={'answer': 'Подтверждение оплаты прошло успешно!'},
status=status.HTTP_200_OK,
)


@extend_schema_view(
partial_update=extend_schema(
Expand Down
23 changes: 6 additions & 17 deletions payments/serializers/api/payments.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
from rest_framework import serializers

from payments.models.payments import OrderPayment

class PaymentConfirmWebHookSerializer(serializers.Serializer):
"""
Сериализатор подтверждения платежа с помощью WebHook-а.
Атрибуты:
* `product` (PrimaryKeyRelatedField): товар.
* `quantity` (IntegerField): количество одного товара.
"""
id = serializers.UUIDField(
required=True,
label='Идентификатор webhook',
)
status = serializers.CharField(
label='Статус платежа',
default='succeeded',
read_only=True,
)

class EmptyPaymentSerializer(serializers.ModelSerializer):
"""Пустой сериализатор для подтверждения заказа с помощью webhook-а."""
class Meta:
model = OrderPayment
fields = ('order_id',)
12 changes: 8 additions & 4 deletions payments/services/payments.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __create_and_get_payment_with_yookassa(self) -> None:
'type': "redirect",
'return_url': YOOKASSA_RETURN_URL,
},
'capture': True,
'capture': False,
'description': f'Оплата заказа на {self.__price} руб.',
'metadata': {
'order_id': self.__order.pk,
Expand All @@ -113,9 +113,12 @@ def __create_and_get_payment_with_yookassa(self) -> None:
detail='Ошибка на стороне Yookassa при создании платежа', code=error,
)

def __add_and_save_payment_id(self) -> None:
"""Добавить и сохранить `id` платежа в таблицу `OrderPayment`."""
def __add_payment_id(self) -> None:
"""Добавить `id` платежа в таблицу `OrderPayment`."""
self._order_payment.payment_id = self._payment_response.id

def __save_payment_id(self) -> None:
"""Сохранить `id` платежа в таблицу `OrderPayment`."""
self._order_payment.save()

def execute_payment_and_get_address(self) -> str:
Expand All @@ -127,5 +130,6 @@ def execute_payment_and_get_address(self) -> str:
self.__create_payment()
self._setting_an_account()
self.__create_and_get_payment_with_yookassa()
self.__add_and_save_payment_id()
self.__add_payment_id()
self.__save_payment_id()
return self._payment_response.confirmation.confirmation_url
13 changes: 13 additions & 0 deletions payments/services/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from yookassa import Payment
from yookassa.domain.notification import PaymentWebhookNotification

from orders.models.orders import Order
from .payments import _PaymentBaseService
from ..models.payments import OrderPayment

Expand Down Expand Up @@ -97,6 +98,10 @@ def __check_payment_status_with_get_request(self) -> None:
def __is_status_succeeded(self) -> None:
"""Является ли статус успешным."""
if not self._payment_response.status == 'succeeded':
logger.error(
msg={f'Ошибка на стороне Yookassa. Платежа {self.__payment_id} '
f'не переведен в статус succeeded': ParseError}
)
raise ParseError(
f'Ошибка на стороне Yookassa. Платежа {self.__payment_id}'
f' не переведен в статус succeeded'
Expand All @@ -108,6 +113,12 @@ def __update_status_payment(self) -> None:
is_paid=OrderPayment.Status.PAID,
)

def __update_status_order(self) -> None:
"""Обновить статус заказа."""
Order.objects.filter(id=self._order_payment.pk).update(
order_status=Order.Status.WORK,
)

def execute(self) -> None:
"""Выполнить обработку webhook-а."""
self._setting_an_account()
Expand All @@ -117,5 +128,7 @@ def execute(self) -> None:
self.__is_such_payment_in_database()
self.__get_current_payment()
self.__confirm_payment()
self.__check_payment_status_with_get_request()
self.__is_status_succeeded()
self.__update_status_payment()
self.__update_status_order()
9 changes: 9 additions & 0 deletions payments/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.urls import path, include

from .views import payments

urlpatterns = [
path('orders/payment_confirmation/', payments.PaymentConfirmationAPIView.as_view(), name='payment_confirmation')
]


Empty file added payments/views/__init__.py
Empty file.
Loading

0 comments on commit 35d52b4

Please sign in to comment.