From c21f4b3dacd597a15a5ec39d525df1dfe1b70376 Mon Sep 17 00:00:00 2001 From: jdlugosz963 Date: Mon, 17 Jul 2023 01:47:57 +0200 Subject: Add project. --- restaurant_orders/core/__init__.py | 0 restaurant_orders/core/admin.py | 5 ++ restaurant_orders/core/apps.py | 6 +++ restaurant_orders/core/decorators.py | 36 +++++++++++++ restaurant_orders/core/models.py | 90 +++++++++++++++++++++++++++++++ restaurant_orders/core/tasks.py | 51 ++++++++++++++++++ restaurant_orders/core/utils.py | 100 +++++++++++++++++++++++++++++++++++ 7 files changed, 288 insertions(+) create mode 100644 restaurant_orders/core/__init__.py create mode 100644 restaurant_orders/core/admin.py create mode 100644 restaurant_orders/core/apps.py create mode 100644 restaurant_orders/core/decorators.py create mode 100644 restaurant_orders/core/models.py create mode 100644 restaurant_orders/core/tasks.py create mode 100644 restaurant_orders/core/utils.py (limited to 'restaurant_orders/core') diff --git a/restaurant_orders/core/__init__.py b/restaurant_orders/core/__init__.py new file mode 100644 index 0000000..e69de29 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 @@ +from django.contrib import admin +from core.models import Restaurant, Order + +admin.site.register(Restaurant) +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 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + 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 @@ +from django.http import HttpResponse +from core.models import Restaurant + +import base64 +import hashlib +import hmac + +def get_webhook_secret_from_restaurant(pk): + try: + token = Restaurant.objects.get(pk=pk).woocommerce_webhook_secret + if token != '': + return token + except Restaurant.DoesNotExist: + pass + return None + +def compare_signatures(body, webhook_secret, request_sig): + signature = hmac.new(webhook_secret.encode(), body, hashlib.sha256).digest() + return hmac.compare_digest(request_sig.encode(), base64.b64encode(signature)) + + +def woocommerce_authentication_required(view): + def inner(request, restaurant_pk, *args, **kwargs): + webhook_secret = get_webhook_secret_from_restaurant(restaurant_pk) + request_sig = request.headers.get('x-wc-webhook-signature') + + if not webhook_secret or not request_sig: + return HttpResponse('Unauthorized') + + if compare_signatures(request.body, webhook_secret, request_sig): + return view(request, restaurant_pk, *args, **kwargs) + + response = HttpResponse('Unauthorized') + response.status_code = 403 + return response + 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 @@ +from django.db import models +from django.contrib.auth.models import User +from django.shortcuts import Http404, get_object_or_404 + +from datetime import datetime + + +class Restaurant(models.Model): + name = models.CharField(max_length=50) + users = models.ManyToManyField(User) + + wordpress_url = models.URLField(max_length=50, unique=True) + woocommerce_consumer_key = models.CharField(max_length=50) + woocommerce_consumer_secret = models.CharField(max_length=50) + woocommerce_webhook_secret = models.CharField(max_length=50) + + @classmethod + def get_user_restaurants(cls, user: User): + return cls.objects.filter(users=user) + + @classmethod + def get_user_restaurant_or_404(cls, pk, user: User): + return get_object_or_404(cls, pk=pk, users=user) + + def __str__(self): + return self.name + + +class Order(models.Model): + WP_STATES = ( + ('pending', 'Oczekujace'), + ('processing', 'Przetwarzane'), + ('on-hold', 'Wstrzymane'), + ('completed', 'Zakonczone'), + ('cancelled', 'Anulowane'), + ('refunded', 'Zwrocone'), + ('failed', 'Nie powiodlo sie'), + ('trash', 'Usuniete') + ) + + wp_id = models.IntegerField(editable=False) + wp_status = models.CharField(max_length=30, choices=WP_STATES) + wp_order_key = models.CharField(max_length=50, editable=False) + date_created = models.DateField(editable=False) + date_modified = models.DateField() + line_items = models.JSONField() + billing = models.JSONField() + restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE) + can_display = models.BooleanField(default=True) + + @classmethod + def update_or_create_from_response(cls, response, restaurant_model): + try: + obj = cls.objects.update_or_create( + wp_id=response['id'], + wp_order_key=response['order_key'], + restaurant=restaurant_model, + defaults={ + 'wp_status': response['status'], + 'date_created': datetime.strptime(response['date_created'], '%Y-%m-%dT%H:%M:%S'), + 'date_modified': datetime.strptime(response['date_modified'], '%Y-%m-%dT%H:%M:%S'), + 'line_items': response['line_items'], + 'billing': response['billing'], + } + ) + return obj + except KeyError: + return None + + + @classmethod + def create_from_response_disable_view(cls, response, restaurant_model): + obj, _ = cls.update_or_create_from_response(response, restaurant_model) + obj.can_display = False + obj.save() + return obj + + @classmethod + def get_order(cls, pk, user: User): + try: + return cls.objects.get( + pk=pk, + can_display=True, + restaurant__users=user + ) + except cls.DoesNotExist: + raise Http404 + + def __str__(self): + 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 @@ +from celery import shared_task + +from django.core import serializers + +from restaurant_orders.consumers import NotificationsConsumer + +from core.utils import Orders, SendMail, SendSms +import re + + +def send_notification(is_success, message, user_pk): + status = NotificationsConsumer.OK if is_success else NotificationsConsumer.ERROR + NotificationsConsumer.send_notifications( + user_pk, + status, + message + ) + + + +@shared_task +def create_order_and_send_notification(order, items, is_email=None, is_sms=None, user_pk=None): + order = [obj for obj in serializers.deserialize('json', order)] + if len(order) != 1: + return + order = order[0].object + + phone = order.billing.get('phone') + email = order.billing.get('email') + new_order = Orders(order.restaurant, order.billing).create_custom_order(items) + + +# if new_order is None: +# send_notification(False, +# "Niestety nie udalo sie skontaktowac z restauracja, prosze sprowbowac ponownie pozniej.", +# user_pk) +# return + + EMAIL_REGEX = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' + + if is_sms: # TODO: Make regex for sms + sms = SendSms(new_order).send() + send_notification(*sms, user_pk) + + if is_email and re.fullmatch(EMAIL_REGEX, str(email)): + mail = SendMail(new_order).send() + send_notification(*mail, user_pk) + + + + 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 @@ +import json +from woocommerce import API +from twilio.rest import Client +from twilio.base.exceptions import TwilioRestException + +from django.core.mail import send_mail +from django.conf import settings + +from core.models import Order + +class Sender(): + def __init__(self, order): + self.order = order + + def get_order_url(self): + order_id = self.order.wp_id + order_key = self.order.wp_order_key + restaurant_url = self.order.restaurant.wordpress_url + return f'{restaurant_url}/zamowienie/order-pay/{order_id}/?pay_for_order=true&key={order_key}' + + def get_message_body(self): + return f'Prosze dokonac platnosci: {self.get_order_url()}' + + def send(self) -> (bool, str): + pass + + +class SendSms(Sender): + def __init__(self, order): + account_sid = settings.TWILIO_ACCOUNT_SID + auth_token = settings.TWILIO_TOKEN + + self.client = Client(account_sid, auth_token) + self.from_ = "+17432007359" + + super().__init__(order) + + def send(self) -> (bool, str): + phone = self.order.billing.get('phone', None) + phone = "+48609155122" + if phone: + try: + message = self.client.messages.create(to=phone, + from_=self.from_, + body=self.get_message_body()) + except TwilioRestException as err: + return (False, err.msg) + else: + return (True, 'Wyslano sms') + return (False, 'Nie znaleziono numeru telefonu.') + + + +class SendMail(Sender): + def send(self) -> (bool, str): + email = self.order.billing.get('email', None) + email = 'jdlugosz963@gmail.com' + if email: # Jesli sie spierdoli to wypluje + try: + send_mail('Strona do zaplaty', self.get_message_body(), 'no-reply@reami.pl', (email, ), fail_silently=False) + except smtplib.SMTPException: + return (False, "Niestety nie udalo sie wyslac maila.") + else: + return (True, "Wyslano maila.") + return (False, "Nie znaleziono maila.") + + +class Orders: + def __init__(self, restaurant, billing): + self.restaurant = restaurant + self.billing = billing + + self.wcapi = API( + url=restaurant.wordpress_url, + consumer_key=restaurant.woocommerce_consumer_key, + consumer_secret=restaurant.woocommerce_consumer_secret, + timeout=7 + ) + + def get_custom_order_data(self, items): + return { + "payment_method": "bacs", + "payment_method_title": "Direct Bank Transfer", + "set_paid": False, + "billing": self.billing, + "shipping": self.billing, + "line_items": [ + { + "product_id": pk, + "total": total, + "quantity": 1, + } for pk, total in items + ] + } + + def create_custom_order(self, items): + data = self.get_custom_order_data(items) + response = self.wcapi.post("orders", data=data).json() + return Order.create_from_response_disable_view(response, self.restaurant) + -- cgit v1.2.3