import logging import sys import uuid from fastapi import FastAPI, Request from .database import engine from . import models from .routers import payment from .routers.transaction import transaction_router from .routers.auto import auto_router from .routers.user_check import user_check_router from fastapi.middleware.cors import CORSMiddleware from starlette.middleware.base import BaseHTTPMiddleware from config import load_config from authorizenet import apicontractsv1 from authorizenet.apicontrollers import getCustomerProfileIdsController from authorizenet.constants import constants ApplicationConfig = load_config() # Configure logging def setup_logging(): """Configure structured logging for the application.""" log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) root_logger = logging.getLogger() root_logger.setLevel(log_level) root_logger.handlers.clear() console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(log_level) console_handler.setFormatter(formatter) root_logger.addHandler(console_handler) # Reduce noise from uvicorn and other libs logging.getLogger('uvicorn.access').setLevel(logging.WARNING) return logging.getLogger('eamco_authorize') logger = setup_logging() models.Base.metadata.create_all(bind=engine) app = FastAPI() # Request ID middleware for request tracking/correlation class RequestIDMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())[:8] request.state.request_id = request_id response = await call_next(request) response.headers["X-Request-ID"] = request_id return response app.add_middleware(RequestIDMiddleware) app.add_middleware( CORSMiddleware, allow_origins=ApplicationConfig.origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["Authorization", "Content-Type", "Accept", "Origin", "X-Requested-With", "X-Request-ID"], ) app.include_router(payment.router, prefix="/api", tags=["payment"]) app.include_router(transaction_router, prefix="/api", tags=["transactions"]) app.include_router(auto_router, prefix="/api", tags=["auto"]) app.include_router(user_check_router, prefix="/user", tags=["usercheck"]) @app.get("/") def read_root(): return {"message": "Welcome to the HVAC Payment API"} def validate_authorize_credentials(): """ Validates Authorize.net credentials at startup. Returns True if credentials are valid, raises exception if not. """ api_login_id = ApplicationConfig.API_LOGIN_ID transaction_key = ApplicationConfig.TRANSACTION_KEY # Check if credentials are present if not api_login_id or not api_login_id.strip(): raise ValueError("AUTHORIZE_API_LOGIN_ID is not configured") if not transaction_key or not transaction_key.strip(): raise ValueError("AUTHORIZE_TRANSACTION_KEY is not configured") # Test the credentials by making a simple API call merchantAuth = apicontractsv1.merchantAuthenticationType( name=api_login_id, transactionKey=transaction_key ) request = apicontractsv1.getCustomerProfileIdsRequest( merchantAuthentication=merchantAuth ) controller = getCustomerProfileIdsController(request) # Set environment based on config if ApplicationConfig.CURRENT_SETTINGS in ['PRODUCTION', 'LOCAL']: controller.setenvironment(constants.PRODUCTION) else: controller.setenvironment(constants.SANDBOX) controller.execute() response = controller.getresponse() if response is None: raise ValueError("Could not connect to Authorize.net - check network connectivity") if response.messages.resultCode != "Ok": error_code = response.messages.message[0].code if response.messages.message else "Unknown" error_text = response.messages.message[0].text if response.messages.message else "Unknown error" raise ValueError(f"Authorize.net credential validation failed: {error_code} - {error_text}") return True @app.on_event("startup") async def startup_event(): """Application startup - validate payment credentials.""" logger.info("Starting eamco_authorize service...") logger.info(f"Mode: {ApplicationConfig.CURRENT_SETTINGS}") try: validate_authorize_credentials() logger.info("Authorize.net credentials: VALID") except ValueError as e: logger.critical(f"PAYMENT CREDENTIALS INVALID: {e}") logger.critical("Service will start but ALL PAYMENT OPERATIONS WILL FAIL") # In production, you might want to sys.exit(1) here instead