From 6be453162ddd7a161ed09e06fd23db300ad492d7 Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 6 Apr 2023 00:01:05 +0200 Subject: [PATCH] Use DRF for API --- CHANGELOG.md | 6 +++++ api/authentications.py | 22 ++++++++++++++++ api/exception_handlers.py | 13 ++++++++++ api/exceptions.py | 19 ++++++++++++++ api/v1/services.py | 26 +++++++++++++++++++ api/v1/views.py | 53 ++++++++------------------------------- core/settings.py | 8 ++++++ requirements.txt | 1 + 8 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 api/authentications.py create mode 100644 api/exception_handlers.py create mode 100644 api/exceptions.py create mode 100644 api/v1/services.py diff --git a/CHANGELOG.md b/CHANGELOG.md index aff2a3c..a8098dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.35] - 2023-04-06 + +### Improved + +- Code base [@AivGitHub](https://github.com/AivGitHub/). + ## [0.0.34] - 2023-04-4 ### Added diff --git a/api/authentications.py b/api/authentications.py new file mode 100644 index 0000000..eb932c5 --- /dev/null +++ b/api/authentications.py @@ -0,0 +1,22 @@ +from django.conf import settings +from rest_framework.authentication import BaseAuthentication + +from api.exceptions import AuthenticationFailedException +from payments.core import stripe + + +class StripeAuthentication(BaseAuthentication): + def authenticate(self, request): + try: + signature = request.headers['Stripe-Signature'] + except KeyError: + raise AuthenticationFailedException('SSO header is missing') + try: + event: stripe.Event = stripe.Webhook.construct_event(request.body, signature, + settings.STRIPE_ENDPOINT_SECRET) + except ValueError as e: + raise AuthenticationFailedException() + except stripe.error.SignatureVerificationError: + raise AuthenticationFailedException() + + return None, event diff --git a/api/exception_handlers.py b/api/exception_handlers.py new file mode 100644 index 0000000..a3e041c --- /dev/null +++ b/api/exception_handlers.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from rest_framework.views import exception_handler + + +def api_exception_handler(exc, context): + response = exception_handler(exc, context) + if response is not None: + response.data['message'] = response.data['detail'] + response.data['time'] = datetime.now() + del response.data['detail'] + + return response diff --git a/api/exceptions.py b/api/exceptions.py new file mode 100644 index 0000000..d0b95c9 --- /dev/null +++ b/api/exceptions.py @@ -0,0 +1,19 @@ +from rest_framework import status +from rest_framework.exceptions import APIException + + +class BaseCustomException(APIException): + detail = None + status_code = None + + def __init__(self, detail=None, code=None): + super().__init__(detail=detail, code=code) + self.detail = detail + self.status_code = code + + +class AuthenticationFailedException(BaseCustomException): + def __init__(self, detail=None): + if detail is None: + detail = 'Not authenticated' + super().__init__(detail=detail, code=status.HTTP_401_UNAUTHORIZED) diff --git a/api/v1/services.py b/api/v1/services.py new file mode 100644 index 0000000..a2a939d --- /dev/null +++ b/api/v1/services.py @@ -0,0 +1,26 @@ +from payments.core import stripe +from payments.models import get_payment_instance, Subscription + + +class StripeWebhookService: + def __init__(self, event: stripe.Event): + self.event = event + + def procces_post_request(self): + if self.event.type == 'checkout.session.completed': + self.checkout_session_completed() + elif self.event.type == 'invoice.payment_succeeded': + self.invoice_payment_succeeded() + elif self.event.type == 'customer.subscription.updated': + self.customer_subscription_updated() + + def customer_subscription_updated(self): + payment_instance = Subscription.objects.get(psp_id=self.event.data.object.id) + payment_instance.update_from_event(self.event) + + def checkout_session_completed(self): + payment_instance = get_payment_instance(self.event) + payment_instance.from_event(self.event, save=True) + + def invoice_payment_succeeded(self): + pass diff --git a/api/v1/views.py b/api/v1/views.py index f20237d..05799df 100644 --- a/api/v1/views.py +++ b/api/v1/views.py @@ -1,50 +1,19 @@ -from http import HTTPStatus - -from django.conf import settings -from django.http import HttpResponse from django.utils.decorators import method_decorator -from django.views import View from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView -from payments.core import stripe -from payments.models import get_payment_instance, Subscription +from api.authentications import StripeAuthentication +from api.v1.services import StripeWebhookService @method_decorator(csrf_exempt, name='dispatch') -class StripeWebhook(View): - def post(self, request, *args, **kwargs): - try: - signature = request.headers['Stripe-Signature'] - except KeyError: - return HttpResponse(status=HTTPStatus.FORBIDDEN) - - try: - event = stripe.Webhook.construct_event(request.body, signature, settings.STRIPE_ENDPOINT_SECRET) - except ValueError as e: - return HttpResponse(status=HTTPStatus.FORBIDDEN) - except stripe.error.SignatureVerificationError: - return HttpResponse(status=HTTPStatus.FORBIDDEN) - - print(event.data.object.object) - if event.type == 'checkout.session.completed': - self.checkout_session_completed(event) - elif event.type == 'invoice.payment_succeeded': - self.invoice_payment_succeeded(event) - elif event.type == 'customer.subscription.updated': - self.customer_subscription_updated(event) +class StripeWebhook(APIView): + authentication_classes = (StripeAuthentication,) - return HttpResponse(status=HTTPStatus.OK) - - @staticmethod - def customer_subscription_updated(event: stripe.Event): - payment_instance = Subscription.objects.get(psp_id=event.data.object.id) - payment_instance.update_from_event(event) - - @staticmethod - def checkout_session_completed(event: stripe.Event): - payment_instance = get_payment_instance(event) - payment_instance.from_event(event, save=True) + def post(self, request, *args, **kwargs): + service = StripeWebhookService(request.auth) + service.procces_post_request() - @staticmethod - def invoice_payment_succeeded(event: stripe.Event): - pass + return Response(status=status.HTTP_200_OK) diff --git a/core/settings.py b/core/settings.py index 2b20a3d..7ae0cde 100644 --- a/core/settings.py +++ b/core/settings.py @@ -71,6 +71,7 @@ 'django.contrib.staticfiles', # 3rd party apps 'fontawesomefree', + 'rest_framework', # Custom apps 'accounts', 'base', @@ -224,3 +225,10 @@ STRIPE_PUBLIC_KEY = ENV.get_value('STRIPE_PUBLIC_KEY') STRIPE_SECRET_KEY = ENV.get_value('STRIPE_SECRET_KEY') STRIPE_ENDPOINT_SECRET = ENV.get_value('STRIPE_ENDPOINT_SECRET') + +REST_FRAMEWORK = { + 'EXCEPTION_HANDLER': 'api.exception_handlers.api_exception_handler', + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ), +} diff --git a/requirements.txt b/requirements.txt index ed9b2a1..a5791e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ currencies==2020.12.12 Django==4.1.3 django-environ==0.9.0 django-storages==1.13.1 +djangorestframework==3.14.0 fontawesomefree==6.3.0 googleapis-common-protos==1.57.0 grpcio==1.51.1