"""Author RAG — Referral System API. Routes: POST /api/admin/{slug}/referral/generate → create referral link + Stripe coupon GET /api/admin/{slug}/referral/stats → clicks, signups, pending commissions GET /api/admin/{slug}/referral/commissions → detailed commission list """ import structlog from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_current_author, get_db from app.models.referral import ReferralLink, ReferralCommission from app.config import get_settings logger = structlog.get_logger(__name__) cfg = get_settings() router = APIRouter() def _gen_code(author_name: str) -> str: """Generate a human-readable referral code from author name.""" import re, random, string slug = re.sub(r"[^a-z0-9]", "", author_name.lower())[:10] suffix = "".join(random.choices(string.digits, k=4)) return f"{slug}{suffix}" if slug else f"ref{suffix}" @router.post("/{slug}/referral/generate", status_code=201) async def generate_referral_link( slug: str, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Generate (or return existing) referral link for this author. Creates a Stripe coupon that gives referred customers 20% off for 3 months. Author earns 20% commission on those payments. """ if author.id != slug: raise HTTPException(403, "Forbidden") # Return existing active link if already generated existing = await db.execute( select(ReferralLink).where( ReferralLink.referrer_id == slug, ReferralLink.is_active == True, # noqa: E712 ) ) link = existing.scalar_one_or_none() if link: return _link_dict(link) # Generate new code code = _gen_code(author.full_name or "author") # Create Stripe coupon (20% off for 3 months) stripe_coupon_id = None try: import stripe stripe.api_key = cfg.STRIPE_SECRET_KEY if cfg.STRIPE_SECRET_KEY: coupon = stripe.Coupon.create( id=f"{code}_DISC", percent_off=20, duration="repeating", duration_in_months=3, ) stripe_coupon_id = coupon.id except Exception as e: logger.warning("Stripe coupon creation failed — link created without coupon", error=str(e)) link = ReferralLink( referrer_id=slug, code=code, stripe_coupon_id=stripe_coupon_id, discount_pct=20, discount_months=3, ) db.add(link) await db.commit() logger.info("Referral link generated", author_id=slug, code=code) return _link_dict(link) @router.get("/{slug}/referral/stats") async def referral_stats( slug: str, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Return referral program stats — clicks, signups, earnings.""" if author.id != slug: raise HTTPException(403, "Forbidden") link_result = await db.execute( select(ReferralLink).where(ReferralLink.referrer_id == slug, ReferralLink.is_active == True) # noqa: E712 ) link = link_result.scalar_one_or_none() if not link: return {"has_link": False, "message": "Generate your referral link to start earning."} # Commission totals comm_result = await db.execute( select( func.count(ReferralCommission.id), func.sum(ReferralCommission.commission_amount_usd), ).where(ReferralCommission.referrer_id == slug) ) total_count, total_earned = comm_result.one() total_earned = float(total_earned or 0) pending_result = await db.execute( select(func.sum(ReferralCommission.commission_amount_usd)) .where(ReferralCommission.referrer_id == slug, ReferralCommission.status == "pending") ) pending = float(pending_result.scalar() or 0) return { "has_link": True, "code": link.code, "referral_url": f"{cfg.FRONTEND_URL}/?ref={link.code}", "clicks": link.click_count, "signups": link.signup_count, "total_commissions": total_count or 0, "total_earned_usd": round(total_earned, 2), "pending_payout_usd": round(pending, 2), "payout_threshold_usd": 100.0, "discount_offer": f"{link.discount_pct}% off for {link.discount_months} months (for new customers)", } @router.get("/{slug}/referral/commissions") async def list_commissions( slug: str, db: AsyncSession = Depends(get_db), author=Depends(get_current_author), ): """Detailed list of commission records.""" if author.id != slug: raise HTTPException(403, "Forbidden") result = await db.execute( select(ReferralCommission) .where(ReferralCommission.referrer_id == slug) .order_by(ReferralCommission.created_at.desc()) .limit(100) ) commissions = result.scalars().all() return { "commissions": [ { "id": c.id, "payment_amount_usd": c.payment_amount_usd, "commission_amount_usd": c.commission_amount_usd, "status": c.status, "created_at": c.created_at.isoformat() if c.created_at else None, "paid_at": c.paid_at.isoformat() if c.paid_at else None, } for c in commissions ] } def _link_dict(link: ReferralLink) -> dict: return { "id": link.id, "code": link.code, "referral_url": f"{cfg.FRONTEND_URL}/?ref={link.code}", "stripe_coupon_id": link.stripe_coupon_id, "discount_pct": link.discount_pct, "discount_months": link.discount_months, "clicks": link.click_count, "signups": link.signup_count, "is_active": link.is_active, "created_at": link.created_at.isoformat() if link.created_at else None, }