#!/usr/bin/env python3 """Test sending a test event to the Conversions Api from Meta.""" import asyncio import json import logging import time from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker from src.alpine_bits_python.customer_service import CustomerService from src.alpine_bits_python.db import Base from src.alpine_bits_python.reservation_service import ReservationService # Set up logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) TEST_CODE = "TEST54726" # Meta CAPI configuration (placeholder values) PIXEL_ID = "539512870322352" ACCESS_TOKEN = "EAATsRaQOv94BPoib5XUn9ZBjNPfeZB4JlKJR1LYtiMdzbEoIa7XFDmHq3pY8UvOcHnbNYDraym107hwRd3EfzO8EpQ5ZB5C4OfF7KJ41KIrfQntkoWrCYcQReecd4vhzVk82hjm55yGDhkxzuNuzG85FZCT0nZB6VyIxZAVLR2estoUSAoQ06J742aMkZCN2AZDZD" CAPI_ENDPOINT = f"https://graph.facebook.com/v19.0/{PIXEL_ID}/events" async def load_test_data_from_db(): """Load reservations and hashed customers from the database.""" # Connect to the test database db_url = "sqlite+aiosqlite:///alpinebits_capi_test.db" engine = create_async_engine(db_url, echo=False) # Create tables if they don't exist async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) # Create async session async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) async with async_session() as session: # Initialize services reservation_service = ReservationService(session) customer_service = CustomerService(session) # Get all reservations with customers reservations_with_customers = ( await reservation_service.get_reservations_with_filters() ) if not reservations_with_customers: logger.warning("No reservations found in database") return [] logger.info("Found %d reservations", len(reservations_with_customers)) # Prepare data with hashed customer info result = [] for reservation, customer in reservations_with_customers: # Get hashed customer data hashed_customer = await customer_service.get_hashed_customer(customer.id) result.append( { "reservation": reservation, "customer": customer, "hashed_customer": hashed_customer, } ) await engine.dispose() return result def _build_user_data(hashed_customer): """Build user_data dict from hashed customer information. Args: hashed_customer: HashedCustomer database object with SHA256 hashed PII Returns: dict: User data for Meta Conversions API """ user_data = {} if not hashed_customer: return user_data # Map hashed customer fields to Meta CAPI field names field_mapping = { "hashed_email": "em", "hashed_phone": "ph", "hashed_given_name": "fn", "hashed_surname": "ln", "hashed_city": "ct", "hashed_postal_code": "zp", "hashed_country_code": "country", "hashed_gender": "ge", "hashed_birth_date": "db", } for source_field, target_field in field_mapping.items(): value = getattr(hashed_customer, source_field, None) if value: user_data[target_field] = value return user_data def _build_custom_data(reservation, booking_value): """Build custom_data dict from reservation information. Args: reservation: Reservation database object booking_value: Booking value in EUR Returns: dict: Custom data for Meta Conversions API """ custom_data = { "currency": "EUR", "value": booking_value, "content_type": "hotel_booking", } # Add optional reservation details optional_fields = { "hotel_code": "hotel_code", "hotel_name": "hotel_name", "num_adults": "num_adults", "num_children": "num_children", } for source_field, target_field in optional_fields.items(): value = getattr(reservation, source_field, None) if value: custom_data[target_field] = value # Add date fields with ISO format if reservation.start_date: custom_data["checkin_date"] = reservation.start_date.isoformat() if reservation.end_date: custom_data["checkout_date"] = reservation.end_date.isoformat() return custom_data def _add_utm_parameters(custom_data, reservation): """Add UTM parameters to custom_data if available. Args: custom_data: Custom data dict to modify reservation: Reservation database object """ utm_fields = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"] if any(getattr(reservation, field, None) for field in utm_fields): for field in utm_fields: custom_data[field] = getattr(reservation, field, None) def _format_fbc(fbclid, timestamp): """Format Facebook Click ID (fbclid) to fbc parameter. The fbc format is: fb.{subdomain_index}.{timestamp_ms}.{fbclid} Args: fbclid: Facebook Click ID from the ad URL timestamp: DateTime object from reservation creation Returns: str: Formatted fbc value for Meta Conversions API """ # Extract timestamp in milliseconds timestamp_ms = int(timestamp.timestamp() * 1000) # Subdomain index is typically 1 subdomain_index = 1 return f"fb.{subdomain_index}.{timestamp_ms}.{fbclid}" def create_meta_capi_event(reservation, customer, hashed_customer): """Create a Meta Conversions API event from reservation and customer data. Args: reservation: Reservation database object customer: Customer database object (currently unused) hashed_customer: HashedCustomer database object with SHA256 hashed PII Returns: dict: Formatted event for Meta Conversions API """ del customer # Currently unused but kept for API consistency # Calculate booking value (example: random value between 500-2000 EUR) booking_value = 1250.00 # Euro # Current timestamp event_time = int(time.time()) # Build user_data with hashed customer information user_data = _build_user_data(hashed_customer) # Add tracking parameters if available if reservation.fbclid and reservation.created_at: # Format fbclid as fbc parameter user_data["fbc"] = _format_fbc(reservation.fbclid, reservation.created_at) if reservation.gclid: user_data["gclid"] = reservation.gclid # Build custom_data custom_data = _build_custom_data(reservation, booking_value) # Add UTM parameters to event _add_utm_parameters(custom_data, reservation) # Return the event return { "event_name": "Purchase", "event_time": event_time, "event_id": reservation.unique_id, # Unique event ID for deduplication "event_source_url": "https://example.com/booking-confirmation", "action_source": "website", "user_data": user_data, "custom_data": custom_data, } async def send_test_event(): """Load data from DB and create test Meta CAPI event.""" logger.info("Loading test data from database...") # Load data test_data = await load_test_data_from_db() if not test_data: logger.error("No test data available. Please add reservations to the database.") return # Use the first reservation for testing data = test_data[0] reservation = data["reservation"] customer = data["customer"] hashed_customer = data["hashed_customer"] logger.info("Using reservation: %s", reservation.unique_id) logger.info("Customer: %s %s", customer.given_name, customer.surname) # Create the event event = create_meta_capi_event(reservation, customer, hashed_customer) # Create the full payload with test_event_code at top level payload = { "data": [event], "test_event_code": TEST_CODE, } # Log the event (pretty print) separator = "=" * 80 logger.info("\n%s", separator) logger.info("META CONVERSIONS API EVENT") logger.info("%s", separator) logger.info("\nEndpoint: %s", CAPI_ENDPOINT) logger.info("\nPayload:\n%s", json.dumps(payload, indent=2)) logger.info("\n%s", separator) logger.info("\nNOTE: This is a test event. To actually send it:") logger.info("1. Set PIXEL_ID to your Meta Pixel ID") logger.info("2. Set ACCESS_TOKEN to your Meta access token") logger.info("3. Uncomment the httpx.post() call below") logger.info( "4. Test the event at: https://developers.facebook.com/tools/events_manager/" ) logger.info(" Use test event code: %s", TEST_CODE) # Uncomment to actually send the event # async with httpx.AsyncClient() as client: # response = await client.post( # CAPI_ENDPOINT, # json=payload, # params={"access_token": ACCESS_TOKEN}, # ) # logger.info("Response status: %s", response.status_code) # logger.info("Response body: %s", response.text) if __name__ == "__main__": asyncio.run(send_test_event())