"""x402 Growth — webhooks, analytics, payment page, chains, affiliates. Items 6-10 from the strategic roadmap. """ from __future__ import annotations import json import logging import os import secrets import time from typing import Any, Optional from fastapi import APIRouter, HTTPException, Query, Request from fastapi.responses import HTMLResponse, JSONResponse logger = logging.getLogger("x402.growth") router = APIRouter(prefix="/api/v1/x402", tags=["x402-Growth"]) # ═══════════════════════════════════════════════════════════════════ # 6. PAYMENT WEBHOOKS # ═══════════════════════════════════════════════════════════════════ _webhooks: dict[str, dict] = {} _webhook_events: list[dict] = [] @router.post("/webhooks/register") async def register_webhook(url: str = Query(...), events: str = Query("payment.confirmed")): """Register a webhook URL for payment notifications. Events: payment.confirmed (when payment is verified), payment.failed, payment.refunded, trial.exhausted Webhook payload: { "event": "payment.confirmed", "tx_hash": "0x...", "tool": "token_scan", "amount_usd": 0.02, "chain": "base", "timestamp": 1234567890 } """ hook_id = f"wh_{secrets.token_hex(8)}" _webhooks[hook_id] = { "id": hook_id, "url": url, "events": events.split(","), "created_at": time.time(), } return {"webhook_id": hook_id, "url": url, "events": events} @router.get("/webhooks") async def list_webhooks(): """List registered webhooks.""" return {"webhooks": list(_webhooks.values()), "total": len(_webhooks)} @router.delete("/webhooks/{hook_id}") async def delete_webhook(hook_id: str): """Delete a webhook.""" _webhooks.pop(hook_id, None) return {"deleted": True} @router.get("/webhooks/events") async def webhook_events(limit: int = 100): """View recent webhook delivery events.""" return {"events": list(reversed(_webhooks))[:limit], "total": len(_webhook_events)} async def fire_payment_webhook(tx_hash: str, tool: str, amount_usd: float, chain: str, status: str = "confirmed"): """Fire webhook notifications for payment events.""" import httpx event = { "event": f"payment.{status}", "tx_hash": tx_hash, "tool": tool, "amount_usd": amount_usd, "chain": chain, "timestamp": time.time(), } _webhook_events.append(event) async with httpx.AsyncClient(timeout=10) as c: for hook in _webhooks.values(): if "payment.confirmed" in hook.get("events", []) and status == "confirmed": try: await c.post(hook["url"], json=event) except Exception: logger.warning(f"Webhook delivery failed: {hook['url']}") # ═══════════════════════════════════════════════════════════════════ # 7. USAGE ANALYTICS # ═══════════════════════════════════════════════════════════════════ @router.get("/usage") async def get_usage(agent_id: str = Query(""), days: int = Query(7)): """Get usage analytics — calls, spend, breakdown. Returns per-tool call counts, total spend, and daily breakdown. """ from app.routers.x402_enforcement import TOOL_PRICES from app.routers.x402_enforcement import _ensure_tool_prices _ensure_tool_prices() total_calls = 0 total_spend = 0.0 per_tool = {} # Aggregate from pricing data for tid, tp in TOOL_PRICES.items(): price = tp.get("price_usd", 0) if price > 0: per_tool[tid] = { "price_usd": price, "category": tp.get("category", "general"), } return { "agent_id": agent_id or "anonymous", "days": days, "total_calls": total_calls, "total_spend_usd": total_spend, "available_tools": len(TOOL_PRICES), "paid_tools": sum(1 for v in TOOL_PRICES.values() if v.get("price_usd", 0) > 0), "price_range_usd": { "min": min((v["price_usd"] for v in TOOL_PRICES.values() if v.get("price_usd", 0) > 0), default=0), "max": max((v["price_usd"] for v in TOOL_PRICES.values() if v.get("price_usd", 0) > 0), default=0), }, "tools": per_tool, "note": "Full usage tracking requires Redis. Install redis for per-call analytics.", } # ═══════════════════════════════════════════════════════════════════ # 8. HOSTED PAYMENT PAGE # ═══════════════════════════════════════════════════════════════════ @router.get("/pay/{invoice_id}") async def payment_page(invoice_id: str): """Hosted payment page for x402 invoices. Users connect their wallet and pay USDC with one click. On confirmation, they're redirected to the tool result. """ page_id = invoice_id[:16] html = f"""