diff options
| author | jdlugosz963 <jdlugosz963@gmail.com> | 2023-07-17 01:47:57 +0200 |
|---|---|---|
| committer | jdlugosz963 <jdlugosz963@gmail.com> | 2023-07-17 01:52:26 +0200 |
| commit | c21f4b3dacd597a15a5ec39d525df1dfe1b70376 (patch) | |
| tree | 0dfc51d2ffeb7b5022d9ad852f2fd3620c72196a /restaurant_orders/core | |
| parent | 2c6f98aeef4fa1aba5678fe17c8e762a11db7b40 (diff) | |
| download | restaurant-orders-main.tar.gz restaurant-orders-main.zip | |
Add project.main
Diffstat (limited to 'restaurant_orders/core')
| -rw-r--r-- | restaurant_orders/core/__init__.py | 0 | ||||
| -rw-r--r-- | restaurant_orders/core/admin.py | 5 | ||||
| -rw-r--r-- | restaurant_orders/core/apps.py | 6 | ||||
| -rw-r--r-- | restaurant_orders/core/decorators.py | 36 | ||||
| -rw-r--r-- | restaurant_orders/core/models.py | 90 | ||||
| -rw-r--r-- | restaurant_orders/core/tasks.py | 51 | ||||
| -rw-r--r-- | restaurant_orders/core/utils.py | 100 |
7 files changed, 288 insertions, 0 deletions
diff --git a/restaurant_orders/core/__init__.py b/restaurant_orders/core/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/restaurant_orders/core/__init__.py | |||
diff --git a/restaurant_orders/core/admin.py b/restaurant_orders/core/admin.py new file mode 100644 index 0000000..5094530 --- /dev/null +++ b/restaurant_orders/core/admin.py | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | from django.contrib import admin | ||
| 2 | from core.models import Restaurant, Order | ||
| 3 | |||
| 4 | admin.site.register(Restaurant) | ||
| 5 | admin.site.register(Order) | ||
diff --git a/restaurant_orders/core/apps.py b/restaurant_orders/core/apps.py new file mode 100644 index 0000000..8115ae6 --- /dev/null +++ b/restaurant_orders/core/apps.py | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | from django.apps import AppConfig | ||
| 2 | |||
| 3 | |||
| 4 | class CoreConfig(AppConfig): | ||
| 5 | default_auto_field = 'django.db.models.BigAutoField' | ||
| 6 | name = 'core' | ||
diff --git a/restaurant_orders/core/decorators.py b/restaurant_orders/core/decorators.py new file mode 100644 index 0000000..d8583f8 --- /dev/null +++ b/restaurant_orders/core/decorators.py | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | from django.http import HttpResponse | ||
| 2 | from core.models import Restaurant | ||
| 3 | |||
| 4 | import base64 | ||
| 5 | import hashlib | ||
| 6 | import hmac | ||
| 7 | |||
| 8 | def get_webhook_secret_from_restaurant(pk): | ||
| 9 | try: | ||
| 10 | token = Restaurant.objects.get(pk=pk).woocommerce_webhook_secret | ||
| 11 | if token != '': | ||
| 12 | return token | ||
| 13 | except Restaurant.DoesNotExist: | ||
| 14 | pass | ||
| 15 | return None | ||
| 16 | |||
| 17 | def compare_signatures(body, webhook_secret, request_sig): | ||
| 18 | signature = hmac.new(webhook_secret.encode(), body, hashlib.sha256).digest() | ||
| 19 | return hmac.compare_digest(request_sig.encode(), base64.b64encode(signature)) | ||
| 20 | |||
| 21 | |||
| 22 | def woocommerce_authentication_required(view): | ||
| 23 | def inner(request, restaurant_pk, *args, **kwargs): | ||
| 24 | webhook_secret = get_webhook_secret_from_restaurant(restaurant_pk) | ||
| 25 | request_sig = request.headers.get('x-wc-webhook-signature') | ||
| 26 | |||
| 27 | if not webhook_secret or not request_sig: | ||
| 28 | return HttpResponse('Unauthorized') | ||
| 29 | |||
| 30 | if compare_signatures(request.body, webhook_secret, request_sig): | ||
| 31 | return view(request, restaurant_pk, *args, **kwargs) | ||
| 32 | |||
| 33 | response = HttpResponse('Unauthorized') | ||
| 34 | response.status_code = 403 | ||
| 35 | return response | ||
| 36 | return inner | ||
diff --git a/restaurant_orders/core/models.py b/restaurant_orders/core/models.py new file mode 100644 index 0000000..91147d2 --- /dev/null +++ b/restaurant_orders/core/models.py | |||
| @@ -0,0 +1,90 @@ | |||
| 1 | from django.db import models | ||
| 2 | from django.contrib.auth.models import User | ||
| 3 | from django.shortcuts import Http404, get_object_or_404 | ||
| 4 | |||
| 5 | from datetime import datetime | ||
| 6 | |||
| 7 | |||
| 8 | class Restaurant(models.Model): | ||
| 9 | name = models.CharField(max_length=50) | ||
| 10 | users = models.ManyToManyField(User) | ||
| 11 | |||
| 12 | wordpress_url = models.URLField(max_length=50, unique=True) | ||
| 13 | woocommerce_consumer_key = models.CharField(max_length=50) | ||
| 14 | woocommerce_consumer_secret = models.CharField(max_length=50) | ||
| 15 | woocommerce_webhook_secret = models.CharField(max_length=50) | ||
| 16 | |||
| 17 | @classmethod | ||
| 18 | def get_user_restaurants(cls, user: User): | ||
| 19 | return cls.objects.filter(users=user) | ||
| 20 | |||
| 21 | @classmethod | ||
| 22 | def get_user_restaurant_or_404(cls, pk, user: User): | ||
| 23 | return get_object_or_404(cls, pk=pk, users=user) | ||
| 24 | |||
| 25 | def __str__(self): | ||
| 26 | return self.name | ||
| 27 | |||
| 28 | |||
| 29 | class Order(models.Model): | ||
| 30 | WP_STATES = ( | ||
| 31 | ('pending', 'Oczekujace'), | ||
| 32 | ('processing', 'Przetwarzane'), | ||
| 33 | ('on-hold', 'Wstrzymane'), | ||
| 34 | ('completed', 'Zakonczone'), | ||
| 35 | ('cancelled', 'Anulowane'), | ||
| 36 | ('refunded', 'Zwrocone'), | ||
| 37 | ('failed', 'Nie powiodlo sie'), | ||
| 38 | ('trash', 'Usuniete') | ||
| 39 | ) | ||
| 40 | |||
| 41 | wp_id = models.IntegerField(editable=False) | ||
| 42 | wp_status = models.CharField(max_length=30, choices=WP_STATES) | ||
| 43 | wp_order_key = models.CharField(max_length=50, editable=False) | ||
| 44 | date_created = models.DateField(editable=False) | ||
| 45 | date_modified = models.DateField() | ||
| 46 | line_items = models.JSONField() | ||
| 47 | billing = models.JSONField() | ||
| 48 | restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE) | ||
| 49 | can_display = models.BooleanField(default=True) | ||
| 50 | |||
| 51 | @classmethod | ||
| 52 | def update_or_create_from_response(cls, response, restaurant_model): | ||
| 53 | try: | ||
| 54 | obj = cls.objects.update_or_create( | ||
| 55 | wp_id=response['id'], | ||
| 56 | wp_order_key=response['order_key'], | ||
| 57 | restaurant=restaurant_model, | ||
| 58 | defaults={ | ||
| 59 | 'wp_status': response['status'], | ||
| 60 | 'date_created': datetime.strptime(response['date_created'], '%Y-%m-%dT%H:%M:%S'), | ||
| 61 | 'date_modified': datetime.strptime(response['date_modified'], '%Y-%m-%dT%H:%M:%S'), | ||
| 62 | 'line_items': response['line_items'], | ||
| 63 | 'billing': response['billing'], | ||
| 64 | } | ||
| 65 | ) | ||
| 66 | return obj | ||
| 67 | except KeyError: | ||
| 68 | return None | ||
| 69 | |||
| 70 | |||
| 71 | @classmethod | ||
| 72 | def create_from_response_disable_view(cls, response, restaurant_model): | ||
| 73 | obj, _ = cls.update_or_create_from_response(response, restaurant_model) | ||
| 74 | obj.can_display = False | ||
| 75 | obj.save() | ||
| 76 | return obj | ||
| 77 | |||
| 78 | @classmethod | ||
| 79 | def get_order(cls, pk, user: User): | ||
| 80 | try: | ||
| 81 | return cls.objects.get( | ||
| 82 | pk=pk, | ||
| 83 | can_display=True, | ||
| 84 | restaurant__users=user | ||
| 85 | ) | ||
| 86 | except cls.DoesNotExist: | ||
| 87 | raise Http404 | ||
| 88 | |||
| 89 | def __str__(self): | ||
| 90 | return f'{self.wp_order_key} - {self.date_created}' | ||
diff --git a/restaurant_orders/core/tasks.py b/restaurant_orders/core/tasks.py new file mode 100644 index 0000000..f1c0192 --- /dev/null +++ b/restaurant_orders/core/tasks.py | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | from celery import shared_task | ||
| 2 | |||
| 3 | from django.core import serializers | ||
| 4 | |||
| 5 | from restaurant_orders.consumers import NotificationsConsumer | ||
| 6 | |||
| 7 | from core.utils import Orders, SendMail, SendSms | ||
| 8 | import re | ||
| 9 | |||
| 10 | |||
| 11 | def send_notification(is_success, message, user_pk): | ||
| 12 | status = NotificationsConsumer.OK if is_success else NotificationsConsumer.ERROR | ||
| 13 | NotificationsConsumer.send_notifications( | ||
| 14 | user_pk, | ||
| 15 | status, | ||
| 16 | message | ||
| 17 | ) | ||
| 18 | |||
| 19 | |||
| 20 | |||
| 21 | @shared_task | ||
| 22 | def create_order_and_send_notification(order, items, is_email=None, is_sms=None, user_pk=None): | ||
| 23 | order = [obj for obj in serializers.deserialize('json', order)] | ||
| 24 | if len(order) != 1: | ||
| 25 | return | ||
| 26 | order = order[0].object | ||
| 27 | |||
| 28 | phone = order.billing.get('phone') | ||
| 29 | email = order.billing.get('email') | ||
| 30 | new_order = Orders(order.restaurant, order.billing).create_custom_order(items) | ||
| 31 | |||
| 32 | |||
| 33 | # if new_order is None: | ||
| 34 | # send_notification(False, | ||
| 35 | # "Niestety nie udalo sie skontaktowac z restauracja, prosze sprowbowac ponownie pozniej.", | ||
| 36 | # user_pk) | ||
| 37 | # return | ||
| 38 | |||
| 39 | EMAIL_REGEX = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' | ||
| 40 | |||
| 41 | if is_sms: # TODO: Make regex for sms | ||
| 42 | sms = SendSms(new_order).send() | ||
| 43 | send_notification(*sms, user_pk) | ||
| 44 | |||
| 45 | if is_email and re.fullmatch(EMAIL_REGEX, str(email)): | ||
| 46 | mail = SendMail(new_order).send() | ||
| 47 | send_notification(*mail, user_pk) | ||
| 48 | |||
| 49 | |||
| 50 | |||
| 51 | |||
diff --git a/restaurant_orders/core/utils.py b/restaurant_orders/core/utils.py new file mode 100644 index 0000000..d716a9c --- /dev/null +++ b/restaurant_orders/core/utils.py | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | import json | ||
| 2 | from woocommerce import API | ||
| 3 | from twilio.rest import Client | ||
| 4 | from twilio.base.exceptions import TwilioRestException | ||
| 5 | |||
| 6 | from django.core.mail import send_mail | ||
| 7 | from django.conf import settings | ||
| 8 | |||
| 9 | from core.models import Order | ||
| 10 | |||
| 11 | class Sender(): | ||
| 12 | def __init__(self, order): | ||
| 13 | self.order = order | ||
| 14 | |||
| 15 | def get_order_url(self): | ||
| 16 | order_id = self.order.wp_id | ||
| 17 | order_key = self.order.wp_order_key | ||
| 18 | restaurant_url = self.order.restaurant.wordpress_url | ||
| 19 | return f'{restaurant_url}/zamowienie/order-pay/{order_id}/?pay_for_order=true&key={order_key}' | ||
| 20 | |||
| 21 | def get_message_body(self): | ||
| 22 | return f'Prosze dokonac platnosci: {self.get_order_url()}' | ||
| 23 | |||
| 24 | def send(self) -> (bool, str): | ||
| 25 | pass | ||
| 26 | |||
| 27 | |||
| 28 | class SendSms(Sender): | ||
| 29 | def __init__(self, order): | ||
| 30 | account_sid = settings.TWILIO_ACCOUNT_SID | ||
| 31 | auth_token = settings.TWILIO_TOKEN | ||
| 32 | |||
| 33 | self.client = Client(account_sid, auth_token) | ||
| 34 | self.from_ = "+17432007359" | ||
| 35 | |||
| 36 | super().__init__(order) | ||
| 37 | |||
| 38 | def send(self) -> (bool, str): | ||
| 39 | phone = self.order.billing.get('phone', None) | ||
| 40 | phone = "+48609155122" | ||
| 41 | if phone: | ||
| 42 | try: | ||
| 43 | message = self.client.messages.create(to=phone, | ||
| 44 | from_=self.from_, | ||
| 45 | body=self.get_message_body()) | ||
| 46 | except TwilioRestException as err: | ||
| 47 | return (False, err.msg) | ||
| 48 | else: | ||
| 49 | return (True, 'Wyslano sms') | ||
| 50 | return (False, 'Nie znaleziono numeru telefonu.') | ||
| 51 | |||
| 52 | |||
| 53 | |||
| 54 | class SendMail(Sender): | ||
| 55 | def send(self) -> (bool, str): | ||
| 56 | email = self.order.billing.get('email', None) | ||
| 57 | email = 'jdlugosz963@gmail.com' | ||
| 58 | if email: # Jesli sie spierdoli to wypluje | ||
| 59 | try: | ||
| 60 | send_mail('Strona do zaplaty', self.get_message_body(), 'no-reply@reami.pl', (email, ), fail_silently=False) | ||
| 61 | except smtplib.SMTPException: | ||
| 62 | return (False, "Niestety nie udalo sie wyslac maila.") | ||
| 63 | else: | ||
| 64 | return (True, "Wyslano maila.") | ||
| 65 | return (False, "Nie znaleziono maila.") | ||
| 66 | |||
| 67 | |||
| 68 | class Orders: | ||
| 69 | def __init__(self, restaurant, billing): | ||
| 70 | self.restaurant = restaurant | ||
| 71 | self.billing = billing | ||
| 72 | |||
| 73 | self.wcapi = API( | ||
| 74 | url=restaurant.wordpress_url, | ||
| 75 | consumer_key=restaurant.woocommerce_consumer_key, | ||
| 76 | consumer_secret=restaurant.woocommerce_consumer_secret, | ||
| 77 | timeout=7 | ||
| 78 | ) | ||
| 79 | |||
| 80 | def get_custom_order_data(self, items): | ||
| 81 | return { | ||
| 82 | "payment_method": "bacs", | ||
| 83 | "payment_method_title": "Direct Bank Transfer", | ||
| 84 | "set_paid": False, | ||
| 85 | "billing": self.billing, | ||
| 86 | "shipping": self.billing, | ||
| 87 | "line_items": [ | ||
| 88 | { | ||
| 89 | "product_id": pk, | ||
| 90 | "total": total, | ||
| 91 | "quantity": 1, | ||
| 92 | } for pk, total in items | ||
| 93 | ] | ||
| 94 | } | ||
| 95 | |||
| 96 | def create_custom_order(self, items): | ||
| 97 | data = self.get_custom_order_data(items) | ||
| 98 | response = self.wcapi.post("orders", data=data).json() | ||
| 99 | return Order.create_from_response_disable_view(response, self.restaurant) | ||
| 100 | |||
