diff options
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 | |||