Template
Clients can run in-house retail without Shopify while keeping the same Docker/Django/Postgres stack and data-ownership promise. Closes #5.
71 lines
2.3 KiB
Python
71 lines
2.3 KiB
Python
import json
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
|
|
from django.shortcuts import render
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.views.decorators.http import require_http_methods
|
|
|
|
from pos_sync.models import POSConnection, SyncEvent
|
|
from pos_sync.services import POSSyncError, apply_inbound_sale
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _authorized(request) -> bool:
|
|
expected = (settings.POS_WEBHOOK_SECRET or "").strip()
|
|
if not expected:
|
|
conn = POSConnection.objects.filter(is_active=True).exclude(
|
|
webhook_secret=""
|
|
).first()
|
|
expected = (conn.webhook_secret if conn else "").strip()
|
|
if not expected:
|
|
return False
|
|
header = request.headers.get("Authorization", "")
|
|
token = ""
|
|
if header.startswith("Bearer "):
|
|
token = header[7:].strip()
|
|
token = token or request.GET.get("token") or ""
|
|
return token == expected
|
|
|
|
|
|
@login_required
|
|
def event_list(request):
|
|
events = SyncEvent.objects.select_related("connection")[:200]
|
|
connections = POSConnection.objects.all()
|
|
return render(
|
|
request,
|
|
"pos_sync/list.html",
|
|
{"events": events, "connections": connections},
|
|
)
|
|
|
|
|
|
@csrf_exempt
|
|
@require_http_methods(["POST"])
|
|
def inventory_webhook(request):
|
|
if not _authorized(request):
|
|
return HttpResponse("unauthorized", status=401)
|
|
try:
|
|
payload = json.loads(request.body.decode() or "{}")
|
|
except json.JSONDecodeError:
|
|
return HttpResponseBadRequest("invalid json")
|
|
sku = payload.get("sku") or payload.get("SKU") or ""
|
|
try:
|
|
quantity = int(payload.get("quantity") or payload.get("qty") or 0)
|
|
except (TypeError, ValueError):
|
|
return HttpResponseBadRequest("invalid quantity")
|
|
external_id = str(payload.get("external_id") or payload.get("id") or "")
|
|
try:
|
|
event = apply_inbound_sale(
|
|
sku=sku,
|
|
quantity=quantity,
|
|
external_id=external_id,
|
|
payload=payload,
|
|
)
|
|
except POSSyncError as exc:
|
|
logger.warning("POS inbound rejected: %s", exc)
|
|
return JsonResponse({"ok": False, "error": str(exc)}, status=400)
|
|
return JsonResponse({"ok": True, "id": str(event.pk), "status": event.status})
|