""" WhatsApp Business Cloud API Schemas """ from pydantic import BaseModel, Field, validator from typing import Optional, List, Dict, Any from datetime import datetime from enum import Enum # ============================================================ # Enums # ============================================================ class WhatsAppMessageType(str, Enum): """WhatsApp message types""" TEMPLATE = "template" TEXT = "text" IMAGE = "image" DOCUMENT = "document" INTERACTIVE = "interactive" class WhatsAppMessageStatus(str, Enum): """WhatsApp message delivery status""" PENDING = "pending" SENT = "sent" DELIVERED = "delivered" READ = "read" FAILED = "failed" class TemplateCategory(str, Enum): """WhatsApp template categories""" MARKETING = "MARKETING" UTILITY = "UTILITY" AUTHENTICATION = "AUTHENTICATION" # ============================================================ # Template Message Schemas # ============================================================ class TemplateParameter(BaseModel): """Template parameter for dynamic content""" type: str = Field(default="text", description="Parameter type (text, currency, date_time)") text: Optional[str] = Field(None, description="Text value for the parameter") class Config: json_schema_extra = { "example": { "type": "text", "text": "PO-2024-001" } } class TemplateComponent(BaseModel): """Template component (header, body, buttons)""" type: str = Field(..., description="Component type (header, body, button)") parameters: Optional[List[TemplateParameter]] = Field(None, description="Component parameters") sub_type: Optional[str] = Field(None, description="Button sub_type (quick_reply, url)") index: Optional[int] = Field(None, description="Button index") class Config: json_schema_extra = { "example": { "type": "body", "parameters": [ {"type": "text", "text": "PO-2024-001"}, {"type": "text", "text": "100.50"} ] } } class TemplateMessageRequest(BaseModel): """Request to send a template message""" template_name: str = Field(..., description="WhatsApp template name") language: str = Field(default="es", description="Template language code") components: List[TemplateComponent] = Field(..., description="Template components with parameters") class Config: json_schema_extra = { "example": { "template_name": "po_notification", "language": "es", "components": [ { "type": "body", "parameters": [ {"type": "text", "text": "PO-2024-001"}, {"type": "text", "text": "Supplier XYZ"}, {"type": "text", "text": "€1,250.00"} ] } ] } } # ============================================================ # Send Message Schemas # ============================================================ class SendWhatsAppMessageRequest(BaseModel): """Request to send a WhatsApp message""" tenant_id: str = Field(..., description="Tenant ID") recipient_phone: str = Field(..., description="Recipient phone number (E.164 format)") recipient_name: Optional[str] = Field(None, description="Recipient name") message_type: WhatsAppMessageType = Field(..., description="Message type") template: Optional[TemplateMessageRequest] = Field(None, description="Template details (required for template messages)") text: Optional[str] = Field(None, description="Text message body (for text messages)") media_url: Optional[str] = Field(None, description="Media URL (for image/document messages)") metadata: Optional[Dict[str, Any]] = Field(None, description="Additional metadata (PO number, order ID, etc.)") notification_id: Optional[str] = Field(None, description="Link to existing notification") @validator('recipient_phone') def validate_phone(cls, v): """Validate E.164 phone format""" if not v.startswith('+'): raise ValueError('Phone number must be in E.164 format (starting with +)') if len(v) < 10 or len(v) > 16: raise ValueError('Phone number length must be between 10 and 16 characters') return v @validator('template') def validate_template(cls, v, values): """Validate template is provided for template messages""" if values.get('message_type') == WhatsAppMessageType.TEMPLATE and not v: raise ValueError('Template details required for template messages') return v class Config: json_schema_extra = { "example": { "tenant_id": "123e4567-e89b-12d3-a456-426614174000", "recipient_phone": "+34612345678", "recipient_name": "Supplier ABC", "message_type": "template", "template": { "template_name": "po_notification", "language": "es", "components": [ { "type": "body", "parameters": [ {"type": "text", "text": "PO-2024-001"}, {"type": "text", "text": "€1,250.00"} ] } ] }, "metadata": { "po_number": "PO-2024-001", "po_id": "123e4567-e89b-12d3-a456-426614174111" } } } class SendWhatsAppMessageResponse(BaseModel): """Response after sending a WhatsApp message""" success: bool = Field(..., description="Whether message was sent successfully") message_id: str = Field(..., description="Internal message ID") whatsapp_message_id: Optional[str] = Field(None, description="WhatsApp's message ID") status: WhatsAppMessageStatus = Field(..., description="Message status") error_message: Optional[str] = Field(None, description="Error message if failed") class Config: json_schema_extra = { "example": { "success": True, "message_id": "123e4567-e89b-12d3-a456-426614174222", "whatsapp_message_id": "wamid.HBgNMzQ2MTIzNDU2Nzg5FQIAERgSMzY5RTFFNTdEQzZBRkVCODdBAA==", "status": "sent", "error_message": None } } # ============================================================ # Webhook Schemas # ============================================================ class WebhookValue(BaseModel): """Webhook notification value""" messaging_product: str metadata: Dict[str, Any] contacts: Optional[List[Dict[str, Any]]] = None messages: Optional[List[Dict[str, Any]]] = None statuses: Optional[List[Dict[str, Any]]] = None class WebhookEntry(BaseModel): """Webhook entry""" id: str changes: List[Dict[str, Any]] class WhatsAppWebhook(BaseModel): """WhatsApp webhook payload""" object: str entry: List[WebhookEntry] class WebhookVerification(BaseModel): """Webhook verification request""" mode: str = Field(..., alias="hub.mode") token: str = Field(..., alias="hub.verify_token") challenge: str = Field(..., alias="hub.challenge") class Config: populate_by_name = True # ============================================================ # Message Status Schemas # ============================================================ class MessageStatusUpdate(BaseModel): """Message status update""" whatsapp_message_id: str = Field(..., description="WhatsApp message ID") status: WhatsAppMessageStatus = Field(..., description="New status") timestamp: datetime = Field(..., description="Status update timestamp") error_code: Optional[str] = Field(None, description="Error code if failed") error_message: Optional[str] = Field(None, description="Error message if failed") # ============================================================ # Template Management Schemas # ============================================================ class WhatsAppTemplateCreate(BaseModel): """Create a WhatsApp template""" tenant_id: Optional[str] = Field(None, description="Tenant ID (null for system templates)") template_name: str = Field(..., description="Template name in WhatsApp") template_key: str = Field(..., description="Internal template key") display_name: str = Field(..., description="Display name") description: Optional[str] = Field(None, description="Template description") category: TemplateCategory = Field(..., description="Template category") language: str = Field(default="es", description="Template language") header_type: Optional[str] = Field(None, description="Header type (TEXT, IMAGE, DOCUMENT, VIDEO)") header_text: Optional[str] = Field(None, max_length=60, description="Header text (max 60 chars)") body_text: str = Field(..., description="Body text with {{1}}, {{2}} placeholders") footer_text: Optional[str] = Field(None, max_length=60, description="Footer text (max 60 chars)") parameters: Optional[List[Dict[str, Any]]] = Field(None, description="Parameter definitions") buttons: Optional[List[Dict[str, Any]]] = Field(None, description="Button definitions") class Config: json_schema_extra = { "example": { "template_name": "po_notification", "template_key": "po_notification_v1", "display_name": "Purchase Order Notification", "description": "Notify supplier of new purchase order", "category": "UTILITY", "language": "es", "body_text": "Hola {{1}}, has recibido una nueva orden de compra {{2}} por un total de {{3}}.", "parameters": [ {"name": "supplier_name", "example": "Proveedor ABC"}, {"name": "po_number", "example": "PO-2024-001"}, {"name": "total_amount", "example": "€1,250.00"} ] } } class WhatsAppTemplateResponse(BaseModel): """WhatsApp template response""" id: str tenant_id: Optional[str] template_name: str template_key: str display_name: str description: Optional[str] category: str language: str status: str body_text: str parameter_count: int is_active: bool sent_count: int created_at: datetime updated_at: datetime class Config: from_attributes = True json_schema_extra = { "example": { "id": "123e4567-e89b-12d3-a456-426614174333", "tenant_id": None, "template_name": "po_notification", "template_key": "po_notification_v1", "display_name": "Purchase Order Notification", "description": "Notify supplier of new purchase order", "category": "UTILITY", "language": "es", "status": "APPROVED", "body_text": "Hola {{1}}, has recibido una nueva orden de compra {{2}} por un total de {{3}}.", "parameter_count": 3, "is_active": True, "sent_count": 125, "created_at": "2024-01-15T10:30:00", "updated_at": "2024-01-15T10:30:00" } } # ============================================================ # Message Query Schemas # ============================================================ class WhatsAppMessageResponse(BaseModel): """WhatsApp message response""" id: str tenant_id: str notification_id: Optional[str] whatsapp_message_id: Optional[str] recipient_phone: str recipient_name: Optional[str] message_type: str status: str template_name: Optional[str] template_language: Optional[str] message_body: Optional[str] sent_at: Optional[datetime] delivered_at: Optional[datetime] read_at: Optional[datetime] failed_at: Optional[datetime] error_message: Optional[str] metadata: Optional[Dict[str, Any]] created_at: datetime updated_at: datetime class Config: from_attributes = True class WhatsAppDeliveryStats(BaseModel): """WhatsApp delivery statistics""" total_messages: int sent: int delivered: int read: int failed: int pending: int unique_recipients: int total_conversations: int delivery_rate: float period: Dict[str, str] class Config: json_schema_extra = { "example": { "total_messages": 1500, "sent": 1480, "delivered": 1450, "read": 1200, "failed": 20, "pending": 0, "unique_recipients": 350, "total_conversations": 400, "delivery_rate": 96.67, "period": { "start": "2024-01-01T00:00:00", "end": "2024-01-31T23:59:59" } } }