Spaces:
Running
Running
| """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}" | |
| 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) | |
| 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)", | |
| } | |
| 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, | |
| } | |