124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import logging
|
|
|
|
import psycopg2
|
|
|
|
from odoo import http
|
|
from odoo.exceptions import ValidationError
|
|
from odoo.http import request
|
|
from odoo.tools import consteq
|
|
|
|
from odoo.addons.pos_enterprise.models.data_validator import list_of, object_of
|
|
|
|
order_event_schema = object_of({
|
|
'header': object_of({
|
|
'event_name': True,
|
|
'event_id': True,
|
|
'version': True,
|
|
'timestamp': True,
|
|
}),
|
|
'body': object_of({
|
|
'customer': object_of({
|
|
'id': True,
|
|
'name': True,
|
|
}),
|
|
'driver': object_of({
|
|
'name': True,
|
|
}),
|
|
'service_type': True,
|
|
'outlet': object_of({
|
|
'id': True,
|
|
'external_outlet_id': True,
|
|
}),
|
|
'order': object_of({
|
|
'status': True,
|
|
'pin': True,
|
|
'order_number': True,
|
|
'order_total': True,
|
|
'order_items': list_of(object_of({
|
|
'quantity': True,
|
|
'price': True,
|
|
'notes': True,
|
|
'name': True,
|
|
'id': True,
|
|
'external_id': True,
|
|
'variants': list_of(object_of({
|
|
'id': True,
|
|
'name': True,
|
|
'external_id': True,
|
|
})),
|
|
})),
|
|
'applied_promotions': list_of(object_of({
|
|
'scope': True,
|
|
'detail': object_of({
|
|
'item': object_of({
|
|
'id': True,
|
|
'external_id': True,
|
|
}),
|
|
'id': True,
|
|
}),
|
|
})),
|
|
'currency': True,
|
|
'takeaway_charges': True,
|
|
'created_at': True,
|
|
}),
|
|
}),
|
|
})
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GoFoodController(http.Controller):
|
|
|
|
@http.route('/gofood/notification', type='jsonrpc', methods=['POST'], auth='public')
|
|
def gofood_webhook(self):
|
|
"""Process the notification data sent by GoFood to the webhook
|
|
"""
|
|
data = request.get_json_data()
|
|
try:
|
|
is_valid, error = order_event_schema(data)
|
|
if not is_valid:
|
|
_logger.warning("GoFood: %r", error)
|
|
return http.Response('Invalid notification data: %s' % error, status=400)
|
|
|
|
store_sudo = request.env['platform.order.entity'].sudo()._find_store_from_data('gofood', data)
|
|
if not store_sudo:
|
|
_logger.warning("GoFood: Store not found for notification data: %r", data)
|
|
return http.Response('Store not found', status=404)
|
|
resp = self._verify_notification_signature(store_sudo)
|
|
if resp:
|
|
return resp
|
|
|
|
order_sudo = request.env['pos.order'].sudo()._find_order_from_data('gofood', data, raise_if_not_found=False)
|
|
if not order_sudo:
|
|
store_sudo._create_order_from_data(data)
|
|
else:
|
|
order_sudo._update_order_status_from_data(data)
|
|
|
|
except ValidationError:
|
|
return http.Response('Invalid notification data', status=400)
|
|
|
|
return http.Response('OK', status=200)
|
|
|
|
@staticmethod
|
|
def _verify_notification_signature(store):
|
|
received_signature = request.httprequest.headers.get('X_GO_SIGNATURE')
|
|
if not received_signature:
|
|
_logger.warning("Received notification with missing signature.")
|
|
return http.Response(status=403)
|
|
|
|
expected_signature = store.provider_id._gofood_calculate_signature(request.httprequest.get_data())
|
|
if not consteq(received_signature, expected_signature):
|
|
_logger.warning("Received notification with invalid signature.")
|
|
return http.Response(status=403)
|
|
|
|
idempotency_key = request.httprequest.headers.get('X_GO_IDEMPOTENCY_KEY')
|
|
try:
|
|
with request.env.cr.savepoint():
|
|
request.env['gofood.idempotency.key'].sudo().create({'key': idempotency_key})
|
|
except psycopg2.errors.UniqueViolation:
|
|
_logger.warning("Received notification with duplicate idempotency key %s.", idempotency_key)
|
|
return http.Response(status=403)
|
|
|
|
return None
|