+ CDEK webhook for instant status updates
This commit is contained in:
parent
99949dc318
commit
55f2e0b02e
|
|
@ -1,4 +1,5 @@
|
|||
APP_HOME="/var/www/poizonstore-stage"
|
||||
SITE_URL="https://crm-poizonstore.ru"
|
||||
|
||||
# === Keys ===
|
||||
# Django
|
||||
|
|
@ -12,6 +13,7 @@ TG_BOT_TOKEN=""
|
|||
# External API settings
|
||||
CDEK_CLIENT_ID=""
|
||||
CDEK_CLIENT_SECRET=""
|
||||
CDEK_WEBHOOK_URL_SALT=""
|
||||
POIZON_TOKEN=""
|
||||
CURRENCY_GETGEOIP_API_KEY=""
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import requests
|
|||
from django.conf import settings
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from poizonstore.utils import deep_get
|
||||
from store.models import Checklist
|
||||
from store.utils import is_migration_running
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'poizonstore.settings')
|
||||
|
|
@ -79,12 +81,23 @@ class CDEKStatus:
|
|||
POSTOMAT_RECEIVED = "POSTOMAT_RECEIVED"
|
||||
|
||||
|
||||
class CDEKWebhookTypes:
|
||||
ORDER_STATUS = "ORDER_STATUS"
|
||||
|
||||
|
||||
CDEK_STATUS_TO_ORDER_STATUS = {
|
||||
CDEKStatus.DELIVERED: Checklist.Status.COMPLETED,
|
||||
CDEKStatus.READY_FOR_SHIPMENT_IN_SENDER_CITY: Checklist.Status.CDEK,
|
||||
}
|
||||
|
||||
|
||||
class CDEKClient:
|
||||
AUTH_ENDPOINT = 'oauth/token'
|
||||
ORDER_INFO_ENDPOINT = 'orders'
|
||||
CALCULATOR_TARIFF_ENDPOINT = 'calculator/tariff'
|
||||
CALCULATOR_TARIFF_LIST_ENDPOINT = 'calculator/tarifflist'
|
||||
BARCODE_ENDPOINT = 'print/barcodes'
|
||||
WEBHOOK_ENDPOINT = 'webhooks'
|
||||
|
||||
MAX_RETRIES = 2
|
||||
|
||||
|
|
@ -204,8 +217,26 @@ class CDEKClient:
|
|||
|
||||
return []
|
||||
|
||||
def setup_webhooks(self):
|
||||
if not settings.SITE_URL:
|
||||
return
|
||||
|
||||
request_data = {
|
||||
"type": CDEKWebhookTypes.ORDER_STATUS,
|
||||
"url": f"{settings.SITE_URL}/cdek/webhook/{settings.CDEK_WEBHOOK_URL_SALT}/"
|
||||
}
|
||||
return self.request('POST', self.WEBHOOK_ENDPOINT, json=request_data)
|
||||
|
||||
@staticmethod
|
||||
def process_orderstatus_webhook(data) -> tuple:
|
||||
""" Unpack CDEK request to data. Info: https://api-docs.cdek.ru/29924139.html """
|
||||
cdek_number = deep_get(data, "attributes", "cdek_number")
|
||||
cdek_status = deep_get(data, "attributes", "code")
|
||||
return cdek_number, cdek_status
|
||||
|
||||
|
||||
client = CDEKClient(settings.CDEK_CLIENT_ID, settings.CDEK_CLIENT_SECRET)
|
||||
|
||||
if not is_migration_running():
|
||||
client.authorize()
|
||||
client.setup_webhooks()
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ def get_secret(setting):
|
|||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = get_secret("SECRET_KEY")
|
||||
SITE_URL = get_secret("SITE_URL")
|
||||
|
||||
# External API settings
|
||||
CDEK_CLIENT_ID = get_secret("CDEK_CLIENT_ID")
|
||||
CDEK_CLIENT_SECRET = get_secret("CDEK_CLIENT_SECRET")
|
||||
CDEK_WEBHOOK_URL_SALT = get_secret("CDEK_WEBHOOK_URL_SALT")
|
||||
|
||||
POIZON_TOKEN = get_secret("POIZON_TOKEN")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
from functools import reduce
|
||||
|
||||
from rest_framework.fields import DecimalField
|
||||
|
||||
|
||||
class PriceField(DecimalField):
|
||||
def __init__(self, *args, max_digits=10, decimal_places=2, min_value=0, **kwargs):
|
||||
super().__init__(*args, max_digits=max_digits, decimal_places=decimal_places, min_value=min_value, **kwargs)
|
||||
|
||||
|
||||
def deep_get(dictionary, *keys, default=None):
|
||||
"""Get value from a nested dictionary (JSON)"""
|
||||
return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else default, keys, dictionary)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ from .models import Checklist, GlobalSettings
|
|||
|
||||
@shared_task
|
||||
def check_cdek_status(order_id):
|
||||
"""Manually check CDEK status of order"""
|
||||
|
||||
obj = Checklist.objects.filter(id=order_id).first()
|
||||
if obj is None or obj.cdek_tracking is None or obj.status == Checklist.Status.COMPLETED:
|
||||
return
|
||||
|
|
@ -20,6 +22,7 @@ def check_cdek_status(order_id):
|
|||
|
||||
old_status = obj.status
|
||||
new_status = obj.status
|
||||
|
||||
if CDEKStatus.DELIVERED in statuses:
|
||||
new_status = Checklist.Status.COMPLETED
|
||||
elif CDEKStatus.READY_FOR_SHIPMENT_IN_SENDER_CITY in statuses:
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from rest_framework.generics import get_object_or_404
|
|||
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from external_api.cdek import CDEKClient
|
||||
from external_api.cdek import CDEKClient, CDEKWebhookTypes, CDEK_STATUS_TO_ORDER_STATUS
|
||||
from external_api.poizon import PoizonClient
|
||||
from utils.exceptions import CRMException
|
||||
from store.filters import GiftFilter, ChecklistFilter
|
||||
|
|
@ -350,6 +350,35 @@ class CDEKAPI(viewsets.GenericViewSet):
|
|||
r = self.client.calculate_tarifflist(data)
|
||||
return prepare_external_response(r)
|
||||
|
||||
@action(url_path=f'webhook/{settings.CDEK_WEBHOOK_URL_SALT}', detail=False, methods=['post'])
|
||||
def webhook(self, request, *args, **kwargs):
|
||||
data = request.data
|
||||
response = Response()
|
||||
|
||||
match data.get("type"):
|
||||
case CDEKWebhookTypes.ORDER_STATUS:
|
||||
cdek_number, cdek_status = self.client.process_orderstatus_webhook(data)
|
||||
if cdek_number is None or cdek_status is None:
|
||||
return response
|
||||
|
||||
order = Checklist.objects.filter(cdek_tracking=cdek_number).first()
|
||||
if order is None:
|
||||
return response
|
||||
|
||||
# New status or old one
|
||||
new_order_status = CDEK_STATUS_TO_ORDER_STATUS.get(cdek_status, order.status)
|
||||
|
||||
# Update status
|
||||
if order.status != new_order_status:
|
||||
print(f'Order [{order.id}] status: {order.status} -> {new_order_status}')
|
||||
order.status = new_order_status
|
||||
order.save()
|
||||
|
||||
case _:
|
||||
pass
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# TODO: review permissions
|
||||
class PoizonAPI(viewsets.GenericViewSet):
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user