""" Yield Domain Routing API. This handles incoming HTTP requests to yield domains: 1. Detect the domain from the Host header 2. Look up the yield configuration 3. Track the click 4. Redirect to the appropriate affiliate landing page In production, this runs on a separate subdomain or IP (yield.pounce.io) that yield domains CNAME to. """ import logging from datetime import datetime from typing import Optional from fastapi import APIRouter, Depends, Request, Response, HTTPException from fastapi.responses import RedirectResponse, HTMLResponse from sqlalchemy.orm import Session from app.api.deps import get_db from app.config import get_settings from app.models.yield_domain import YieldDomain, YieldTransaction, AffiliatePartner from app.services.intent_detector import detect_domain_intent logger = logging.getLogger(__name__) settings = get_settings() router = APIRouter(prefix="/r", tags=["yield-routing"]) # Revenue split USER_REVENUE_SHARE = 0.70 def hash_ip(ip: str) -> str: """Hash IP for privacy-compliant storage.""" import hashlib return hashlib.sha256(ip.encode()).hexdigest()[:32] def generate_tracking_url( partner: AffiliatePartner, yield_domain: YieldDomain, click_id: int, ) -> str: """ Generate the tracking URL for a partner. Most affiliate networks expect parameters like: - clickid / subid: Our click tracking ID - ref: Domain name or user reference """ # If partner has a tracking URL template, use it if partner.tracking_url_template: return partner.tracking_url_template.format( click_id=click_id, domain=yield_domain.domain, domain_id=yield_domain.id, partner=partner.slug, ) # Default fallbacks by network network_urls = { "comparis_dental": f"https://www.comparis.ch/zahnarzt?subid={click_id}&ref={yield_domain.domain}", "comparis_health": f"https://www.comparis.ch/krankenkassen?subid={click_id}&ref={yield_domain.domain}", "comparis_insurance": f"https://www.comparis.ch/versicherungen?subid={click_id}&ref={yield_domain.domain}", "comparis_hypo": f"https://www.comparis.ch/hypotheken?subid={click_id}&ref={yield_domain.domain}", "comparis_auto": f"https://www.comparis.ch/autoversicherung?subid={click_id}&ref={yield_domain.domain}", "comparis_immo": f"https://www.comparis.ch/immobilien?subid={click_id}&ref={yield_domain.domain}", "homegate": f"https://www.homegate.ch/?ref=pounce&clickid={click_id}", "immoscout": f"https://www.immoscout24.ch/?ref=pounce&clickid={click_id}", "autoscout": f"https://www.autoscout24.ch/?ref=pounce&clickid={click_id}", "jobs_ch": f"https://www.jobs.ch/?ref=pounce&clickid={click_id}", "booking_com": f"https://www.booking.com/?aid=pounce&clickid={click_id}", "hostpoint": f"https://www.hostpoint.ch/?ref=pounce&clickid={click_id}", "infomaniak": f"https://www.infomaniak.com/?ref=pounce&clickid={click_id}", "galaxus": f"https://www.galaxus.ch/?ref=pounce&clickid={click_id}", "zalando": f"https://www.zalando.ch/?ref=pounce&clickid={click_id}", } if partner.slug in network_urls: return network_urls[partner.slug] # Pounce self-promotion fallback with referral tracking # Domain owner gets lifetime commission on signups via their domain referral_code = f"yield_{yield_domain.user_id}_{yield_domain.id}" return f"{settings.site_url}/register?ref={referral_code}&from={yield_domain.domain}&clickid={click_id}" def is_pounce_affinity_domain(domain: str) -> bool: """ Check if a domain has high affinity for Pounce self-promotion. Tech, investment, and domain-related domains convert better for Pounce. """ intent = detect_domain_intent(domain) # Check if the matched category has pounce_affinity flag if intent.category in ["investment", "tech"] or intent.subcategory in ["domains", "dev"]: return True # Check for specific keywords pounce_keywords = { "invest", "domain", "trading", "crypto", "asset", "portfolio", "startup", "tech", "dev", "saas", "digital", "passive", "income" } domain_lower = domain.lower() return any(kw in domain_lower for kw in pounce_keywords) def generate_pounce_promo_page( yield_domain: YieldDomain, click_id: int, ) -> str: """ Generate Pounce self-promotion landing page. Used as fallback when no high-value partner is available, or when the domain has high Pounce affinity. """ referral_code = f"yield_{yield_domain.user_id}_{yield_domain.id}" register_url = f"{settings.site_url}/register?ref={referral_code}&from={yield_domain.domain}&clickid={click_id}" return f"""
Stop paying renewal fees for idle domains.
Let them earn money for you — automatically.
The domain {domain} is not currently active for yield routing.
""", status_code=404 ) # Get partner partner = None if yield_domain.partner_id: partner = db.query(AffiliatePartner).filter( AffiliatePartner.id == yield_domain.partner_id, AffiliatePartner.is_active == True, ).first() # If no partner assigned, try to find one based on intent if not partner and yield_domain.detected_intent: partner = db.query(AffiliatePartner).filter( AffiliatePartner.intent_categories.contains(yield_domain.detected_intent.split('_')[0]), AffiliatePartner.is_active == True, ).order_by(AffiliatePartner.priority.desc()).first() # Create click transaction client_ip = request.client.host if request.client else None user_agent = request.headers.get("user-agent") referrer = request.headers.get("referer") transaction = YieldTransaction( yield_domain_id=yield_domain.id, event_type="click", partner_slug=partner.slug if partner else "unknown", gross_amount=0, net_amount=0, currency="CHF", referrer=referrer, user_agent=user_agent[:500] if user_agent else None, ip_hash=hash_ip(client_ip) if client_ip else None, status="confirmed", confirmed_at=datetime.utcnow(), ) db.add(transaction) # Update domain stats yield_domain.total_clicks += 1 yield_domain.last_click_at = datetime.utcnow() db.commit() db.refresh(transaction) # Generate redirect URL redirect_url = ( generate_tracking_url(partner, yield_domain, transaction.id) if partner else f"{settings.site_url}/buy?ref={domain}" ) # Direct redirect or show landing page if direct: return RedirectResponse(url=redirect_url, status_code=302) # Show interstitial landing page html = generate_landing_page(yield_domain, partner, transaction.id) return HTMLResponse(content=html) @router.get("/") async def yield_routing_info(): """Info endpoint for yield routing service.""" return { "service": "Pounce Yield Routing", "version": "1.0.0", "docs": f"{settings.site_url}/docs#/yield-routing", "status": "active", } # ============================================================================ # Host-based routing (for production deployment) # ============================================================================ @router.api_route("/catch-all", methods=["GET", "HEAD"]) async def catch_all_route( request: Request, db: Session = Depends(get_db), ): """ Catch-all route for host-based routing. In production, this endpoint handles requests where the Host header is the yield domain itself (e.g., zahnarzt-zuerich.ch). This requires: 1. Yield domains to CNAME to yield.pounce.io 2. Nginx/Caddy to route all hosts to this backend 3. This endpoint to parse the Host header """ host = request.headers.get("host", "").lower() # Remove port if present if ":" in host: host = host.split(":")[0] # Skip our own domains our_domains = ["pounce.ch", "pounce.io", "localhost", "127.0.0.1"] if any(host.endswith(d) for d in our_domains): return {"status": "not a yield domain", "host": host} # Look up yield domain yield_domain = db.query(YieldDomain).filter( YieldDomain.domain == host, YieldDomain.status == "active", ).first() if not yield_domain: return HTMLResponse( content=f"