2025-08-16 15:00:36 +02:00
|
|
|
# services/pos/app/core/config.py
|
|
|
|
|
"""
|
|
|
|
|
POS Integration Service Configuration
|
|
|
|
|
"""
|
|
|
|
|
|
2025-09-27 11:18:13 +02:00
|
|
|
import os
|
2025-08-16 15:00:36 +02:00
|
|
|
from typing import List, Optional
|
|
|
|
|
from pydantic import Field
|
|
|
|
|
from shared.config.base import BaseServiceSettings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Settings(BaseServiceSettings):
|
|
|
|
|
"""POS Integration service settings extending base configuration"""
|
|
|
|
|
|
|
|
|
|
# Override service-specific settings
|
|
|
|
|
SERVICE_NAME: str = "pos-service"
|
|
|
|
|
VERSION: str = "1.0.0"
|
|
|
|
|
APP_NAME: str = "Bakery POS Integration Service"
|
|
|
|
|
DESCRIPTION: str = "Integration service for external POS systems (Square, Toast, Lightspeed)"
|
|
|
|
|
|
|
|
|
|
# API Configuration
|
|
|
|
|
API_V1_STR: str = "/api/v1"
|
|
|
|
|
|
2025-09-27 11:18:13 +02:00
|
|
|
# Database configuration (secure approach - build from components)
|
|
|
|
|
@property
|
|
|
|
|
def DATABASE_URL(self) -> str:
|
|
|
|
|
"""Build database URL from secure components"""
|
|
|
|
|
# Try complete URL first (for backward compatibility)
|
|
|
|
|
complete_url = os.getenv("POS_DATABASE_URL")
|
|
|
|
|
if complete_url:
|
|
|
|
|
return complete_url
|
|
|
|
|
|
|
|
|
|
# Build from components (secure approach)
|
|
|
|
|
user = os.getenv("POS_DB_USER", "pos_user")
|
|
|
|
|
password = os.getenv("POS_DB_PASSWORD", "pos_pass123")
|
|
|
|
|
host = os.getenv("POS_DB_HOST", "localhost")
|
|
|
|
|
port = os.getenv("POS_DB_PORT", "5432")
|
|
|
|
|
name = os.getenv("POS_DB_NAME", "pos_db")
|
|
|
|
|
|
|
|
|
|
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{name}"
|
2025-08-16 15:00:36 +02:00
|
|
|
|
|
|
|
|
# POS-specific Redis database
|
|
|
|
|
REDIS_DB: int = Field(default=5, env="POS_REDIS_DB")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# POS PROVIDER CONFIGURATIONS
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Square POS Configuration
|
|
|
|
|
SQUARE_APPLICATION_ID: Optional[str] = Field(default=None, env="SQUARE_APPLICATION_ID")
|
|
|
|
|
SQUARE_ACCESS_TOKEN: Optional[str] = Field(default=None, env="SQUARE_ACCESS_TOKEN")
|
|
|
|
|
SQUARE_WEBHOOK_SIGNATURE_KEY: Optional[str] = Field(default=None, env="SQUARE_WEBHOOK_SIGNATURE_KEY")
|
|
|
|
|
SQUARE_ENVIRONMENT: str = Field(default="sandbox", env="SQUARE_ENVIRONMENT") # sandbox or production
|
|
|
|
|
SQUARE_BASE_URL: str = "https://connect.squareup.com"
|
|
|
|
|
SQUARE_SANDBOX_URL: str = "https://connect.squareupsandbox.com"
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def SQUARE_API_URL(self) -> str:
|
|
|
|
|
return self.SQUARE_SANDBOX_URL if self.SQUARE_ENVIRONMENT == "sandbox" else self.SQUARE_BASE_URL
|
|
|
|
|
|
|
|
|
|
# Toast POS Configuration
|
|
|
|
|
TOAST_CLIENT_ID: Optional[str] = Field(default=None, env="TOAST_CLIENT_ID")
|
|
|
|
|
TOAST_CLIENT_SECRET: Optional[str] = Field(default=None, env="TOAST_CLIENT_SECRET")
|
|
|
|
|
TOAST_WEBHOOK_SECRET: Optional[str] = Field(default=None, env="TOAST_WEBHOOK_SECRET")
|
|
|
|
|
TOAST_ENVIRONMENT: str = Field(default="sandbox", env="TOAST_ENVIRONMENT") # sandbox or production
|
|
|
|
|
TOAST_BASE_URL: str = "https://ws-api.toasttab.com"
|
|
|
|
|
TOAST_SANDBOX_URL: str = "https://ws-sandbox-api.toasttab.com"
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def TOAST_API_URL(self) -> str:
|
|
|
|
|
return self.TOAST_SANDBOX_URL if self.TOAST_ENVIRONMENT == "sandbox" else self.TOAST_BASE_URL
|
|
|
|
|
|
|
|
|
|
# Lightspeed POS Configuration
|
|
|
|
|
LIGHTSPEED_CLIENT_ID: Optional[str] = Field(default=None, env="LIGHTSPEED_CLIENT_ID")
|
|
|
|
|
LIGHTSPEED_CLIENT_SECRET: Optional[str] = Field(default=None, env="LIGHTSPEED_CLIENT_SECRET")
|
|
|
|
|
LIGHTSPEED_WEBHOOK_SECRET: Optional[str] = Field(default=None, env="LIGHTSPEED_WEBHOOK_SECRET")
|
|
|
|
|
LIGHTSPEED_CLUSTER_ID: Optional[str] = Field(default=None, env="LIGHTSPEED_CLUSTER_ID")
|
|
|
|
|
LIGHTSPEED_BASE_URL: str = "https://api-{cluster}.lightspeedhq.com"
|
|
|
|
|
|
|
|
|
|
def get_lightspeed_api_url(self, cluster_id: Optional[str] = None) -> str:
|
|
|
|
|
cluster = cluster_id or self.LIGHTSPEED_CLUSTER_ID or "us1"
|
|
|
|
|
return self.LIGHTSPEED_BASE_URL.format(cluster=cluster)
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# WEBHOOK CONFIGURATION
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Webhook Base Configuration
|
|
|
|
|
WEBHOOK_BASE_URL: str = Field(default="https://your-domain.com", env="WEBHOOK_BASE_URL")
|
|
|
|
|
WEBHOOK_SECRET: str = Field(default="your-webhook-secret", env="WEBHOOK_SECRET")
|
|
|
|
|
WEBHOOK_TIMEOUT_SECONDS: int = Field(default=30, env="WEBHOOK_TIMEOUT_SECONDS")
|
|
|
|
|
|
|
|
|
|
# Webhook Rate Limiting
|
|
|
|
|
WEBHOOK_RATE_LIMIT_PER_MINUTE: int = Field(default=1000, env="WEBHOOK_RATE_LIMIT_PER_MINUTE")
|
|
|
|
|
WEBHOOK_BURST_LIMIT: int = Field(default=100, env="WEBHOOK_BURST_LIMIT")
|
|
|
|
|
|
|
|
|
|
# Webhook Retry Configuration
|
|
|
|
|
WEBHOOK_MAX_RETRIES: int = Field(default=3, env="WEBHOOK_MAX_RETRIES")
|
|
|
|
|
WEBHOOK_RETRY_DELAY_SECONDS: int = Field(default=5, env="WEBHOOK_RETRY_DELAY_SECONDS")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# SYNC CONFIGURATION
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Data Synchronization Settings
|
|
|
|
|
SYNC_ENABLED: bool = Field(default=True, env="POS_SYNC_ENABLED")
|
|
|
|
|
SYNC_INTERVAL_SECONDS: int = Field(default=300, env="POS_SYNC_INTERVAL_SECONDS") # 5 minutes
|
|
|
|
|
SYNC_BATCH_SIZE: int = Field(default=100, env="POS_SYNC_BATCH_SIZE")
|
|
|
|
|
SYNC_MAX_RETRY_ATTEMPTS: int = Field(default=3, env="POS_SYNC_MAX_RETRY_ATTEMPTS")
|
|
|
|
|
SYNC_RETRY_DELAY_SECONDS: int = Field(default=60, env="POS_SYNC_RETRY_DELAY_SECONDS")
|
|
|
|
|
|
|
|
|
|
# Historical Data Sync
|
|
|
|
|
HISTORICAL_SYNC_DAYS: int = Field(default=30, env="POS_HISTORICAL_SYNC_DAYS")
|
|
|
|
|
INITIAL_SYNC_BATCH_SIZE: int = Field(default=50, env="POS_INITIAL_SYNC_BATCH_SIZE")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# SECURITY & ENCRYPTION
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# API Credential Encryption
|
|
|
|
|
ENCRYPTION_KEY: Optional[str] = Field(default=None, env="POS_ENCRYPTION_KEY")
|
|
|
|
|
CREDENTIALS_ENCRYPTION_ENABLED: bool = Field(default=True, env="POS_CREDENTIALS_ENCRYPTION_ENABLED")
|
|
|
|
|
|
|
|
|
|
# API Rate Limiting
|
|
|
|
|
API_RATE_LIMIT_PER_MINUTE: int = Field(default=60, env="POS_API_RATE_LIMIT_PER_MINUTE")
|
|
|
|
|
API_BURST_LIMIT: int = Field(default=10, env="POS_API_BURST_LIMIT")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# CACHING CONFIGURATION
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# POS Data Cache TTL
|
|
|
|
|
POS_CONFIG_CACHE_TTL: int = Field(default=3600, env="POS_CONFIG_CACHE_TTL") # 1 hour
|
|
|
|
|
POS_TRANSACTION_CACHE_TTL: int = Field(default=300, env="POS_TRANSACTION_CACHE_TTL") # 5 minutes
|
|
|
|
|
POS_PRODUCT_CACHE_TTL: int = Field(default=1800, env="POS_PRODUCT_CACHE_TTL") # 30 minutes
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# SUPPORTED POS SYSTEMS
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
SUPPORTED_POS_SYSTEMS: List[str] = ["square", "toast", "lightspeed"]
|
|
|
|
|
|
|
|
|
|
# Default POS system for new tenants
|
|
|
|
|
DEFAULT_POS_SYSTEM: str = Field(default="square", env="DEFAULT_POS_SYSTEM")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# INTER-SERVICE COMMUNICATION
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Override service URLs
|
|
|
|
|
SALES_SERVICE_URL: str = Field(
|
|
|
|
|
default="http://sales-service:8000",
|
|
|
|
|
env="SALES_SERVICE_URL"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
INVENTORY_SERVICE_URL: str = Field(
|
|
|
|
|
default="http://inventory-service:8000",
|
|
|
|
|
env="INVENTORY_SERVICE_URL"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# BUSINESS RULES
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Transaction Processing
|
|
|
|
|
MIN_TRANSACTION_AMOUNT: float = Field(default=0.01, env="POS_MIN_TRANSACTION_AMOUNT")
|
|
|
|
|
MAX_TRANSACTION_AMOUNT: float = Field(default=10000.0, env="POS_MAX_TRANSACTION_AMOUNT")
|
|
|
|
|
|
|
|
|
|
# Duplicate Detection Window (in minutes)
|
|
|
|
|
DUPLICATE_DETECTION_WINDOW: int = Field(default=5, env="POS_DUPLICATE_DETECTION_WINDOW")
|
|
|
|
|
|
|
|
|
|
# Data Retention
|
|
|
|
|
TRANSACTION_RETENTION_DAYS: int = Field(default=1095, env="POS_TRANSACTION_RETENTION_DAYS") # 3 years
|
|
|
|
|
WEBHOOK_LOG_RETENTION_DAYS: int = Field(default=30, env="POS_WEBHOOK_LOG_RETENTION_DAYS")
|
|
|
|
|
SYNC_LOG_RETENTION_DAYS: int = Field(default=90, env="POS_SYNC_LOG_RETENTION_DAYS")
|
|
|
|
|
|
|
|
|
|
# ================================================================
|
|
|
|
|
# MONITORING & ALERTING
|
|
|
|
|
# ================================================================
|
|
|
|
|
|
|
|
|
|
# Health Check Configuration
|
|
|
|
|
POS_HEALTH_CHECK_ENABLED: bool = Field(default=True, env="POS_HEALTH_CHECK_ENABLED")
|
|
|
|
|
POS_HEALTH_CHECK_INTERVAL: int = Field(default=60, env="POS_HEALTH_CHECK_INTERVAL") # seconds
|
|
|
|
|
|
|
|
|
|
# Alert Thresholds
|
|
|
|
|
WEBHOOK_FAILURE_THRESHOLD: int = Field(default=5, env="POS_WEBHOOK_FAILURE_THRESHOLD")
|
|
|
|
|
SYNC_FAILURE_THRESHOLD: int = Field(default=3, env="POS_SYNC_FAILURE_THRESHOLD")
|
|
|
|
|
API_ERROR_THRESHOLD: int = Field(default=10, env="POS_API_ERROR_THRESHOLD")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Global settings instance
|
|
|
|
|
settings = Settings()
|