diff --git a/src/alpine_bits_python/api.py b/src/alpine_bits_python/api.py index bd0870b..faab88f 100644 --- a/src/alpine_bits_python/api.py +++ b/src/alpine_bits_python/api.py @@ -2,7 +2,6 @@ import asyncio import gzip -import hashlib import json import multiprocessing import os @@ -836,16 +835,27 @@ async def handle_webhook_unified( if not webhook_endpoint.hotel.is_active: raise HTTPException(status_code=404, detail="Hotel is not active") - # 3. Hash payload (canonical JSON for consistent hashing) - payload_json_str = json.dumps(payload, sort_keys=True) - payload_hash = hashlib.sha256(payload_json_str.encode("utf-8")).hexdigest() - payload_size = len(payload_json_str.encode("utf-8")) + # 3. Track payload metadata with canonical hashing handled by WebhookRequestData + payload_size = len(body) # Check payload size limit (10MB) if payload_size > 10 * 1024 * 1024: _LOGGER.error("Payload too large: %d bytes", payload_size) raise HTTPException(status_code=413, detail="Payload too large (max 10MB)") + webhook_request_data = WebhookRequestData( + payload_json=payload, + webhook_endpoint_id=webhook_endpoint.id, + hotel_id=webhook_endpoint.hotel_id, + status=WebhookStatus.PROCESSING, + processing_started_at=timestamp, + created_at=timestamp, + source_ip=request.client.host if request.client else None, + user_agent=request.headers.get("user-agent"), + ) + + payload_hash = webhook_request_data.payload_hash + # 4. Check for duplicate with row-level locking duplicate = await db_session.execute( select(WebhookRequest) @@ -892,18 +902,7 @@ async def handle_webhook_unified( webhook_request.status = WebhookStatus.PROCESSING webhook_request.processing_started_at = timestamp else: - webhook_request_data = WebhookRequestData( - payload_hash=payload_hash, - webhook_endpoint_id=webhook_endpoint.id, - hotel_id=webhook_endpoint.hotel_id, - status=WebhookStatus.PROCESSING, - payload_json=payload, - processing_started_at=timestamp, - created_at=timestamp, - source_ip=request.client.host if request.client else None, - user_agent=request.headers.get("user-agent"), - ) - # 5. Create new webhook_request + # 5. Create new webhook_request from validated data webhook_request = WebhookRequest(**webhook_request_data.model_dump()) db_session.add(webhook_request) @@ -923,10 +922,20 @@ async def handle_webhook_unified( event_dispatcher=request.app.state.event_dispatcher, ) - # 8. Update status + # 8. Update status and link created entities when available webhook_request.status = WebhookStatus.COMPLETED webhook_request.processing_completed_at = datetime.now(UTC) + created_customer_id = result.get("customer_id") if isinstance(result, dict) else None + created_reservation_id = ( + result.get("reservation_id") if isinstance(result, dict) else None + ) + + if created_customer_id: + webhook_request.created_customer_id = created_customer_id + if created_reservation_id: + webhook_request.created_reservation_id = created_reservation_id + await db_session.commit() return { diff --git a/src/alpine_bits_python/webhook_processor.py b/src/alpine_bits_python/webhook_processor.py index 9431c15..e051c7e 100644 --- a/src/alpine_bits_python/webhook_processor.py +++ b/src/alpine_bits_python/webhook_processor.py @@ -323,6 +323,8 @@ async def process_wix_form_submission( "received_keys": list(data.keys()), "timestamp": timestamp, "note": "No authentication required for this endpoint", + "customer_id": db_customer.id, + "reservation_id": db_reservation.id, }