summaryrefslogtreecommitdiff
path: root/Biz/PodcastItLater/Web.py
diff options
context:
space:
mode:
Diffstat (limited to 'Biz/PodcastItLater/Web.py')
-rw-r--r--Biz/PodcastItLater/Web.py72
1 files changed, 72 insertions, 0 deletions
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index 2f86b16..c469874 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -15,8 +15,10 @@ Provides ludic + htmx interface and RSS feed generation.
# : dep pytest-asyncio
# : dep pytest-mock
# : dep starlette
+# : dep stripe
import Biz.EmailAgent
import Biz.PodcastItLater.Admin as Admin
+import Biz.PodcastItLater.Billing as Billing
import Biz.PodcastItLater.Core as Core
import html as html_module
import httpx
@@ -1123,6 +1125,76 @@ app.get("/admin")(Admin.admin_queue_status)
app.post("/queue/{job_id}/retry")(Admin.retry_queue_item)
+@app.get("/billing")
+def billing_page(request: Request) -> Response:
+ """Display billing page with current plan and upgrade options."""
+ user_id = request.session.get("user_id")
+ if not user_id:
+ return RedirectResponse(url="/?error=login_required")
+
+ user = Core.Database.get_user_by_id(user_id)
+ if not user:
+ return RedirectResponse(url="/?error=user_not_found")
+
+ tier = user.get("plan_tier", "free")
+ tier_info = Billing.get_tier_info(tier)
+
+ # Get current usage
+ period_start, period_end = Billing.get_period_boundaries(user)
+ Billing.get_usage(user_id, period_start, period_end)
+
+ # Billing page component to be implemented
+ return Response(f"<h1>Billing - Current plan: {tier_info['name']}</h1>")
+
+
+@app.post("/billing/checkout")
+def billing_checkout(request: Request, data: FormData) -> Response:
+ """Create Stripe Checkout session."""
+ user_id = request.session.get("user_id")
+ if not user_id:
+ return Response("Unauthorized", status_code=401)
+
+ tier = data.get("tier", "personal")
+ if tier not in {"personal", "pro"}:
+ return Response("Invalid tier", status_code=400)
+
+ try:
+ checkout_url = Billing.create_checkout_session(user_id, tier, BASE_URL)
+ return RedirectResponse(url=checkout_url)
+ except ValueError as e:
+ logger.exception("Checkout error")
+ return Response(f"Error: {e!s}", status_code=400)
+
+
+@app.post("/billing/portal")
+def billing_portal(request: Request) -> Response:
+ """Create Stripe Billing Portal session."""
+ user_id = request.session.get("user_id")
+ if not user_id:
+ return Response("Unauthorized", status_code=401)
+
+ try:
+ portal_url = Billing.create_portal_session(user_id, BASE_URL)
+ return RedirectResponse(url=portal_url)
+ except ValueError as e:
+ logger.exception("Portal error")
+ return Response(f"Error: {e!s}", status_code=400)
+
+
+@app.post("/stripe/webhook")
+async def stripe_webhook(request: Request) -> Response:
+ """Handle Stripe webhook events."""
+ payload = await request.body()
+ sig_header = request.headers.get("stripe-signature", "")
+
+ try:
+ result = Billing.handle_webhook_event(payload, sig_header)
+ return Response(f"OK: {result['status']}", status_code=200)
+ except Exception as e:
+ logger.exception("Webhook error")
+ return Response(f"Error: {e!s}", status_code=400)
+
+
@app.post("/queue/{job_id}/cancel")
def cancel_queue_item(request: Request, job_id: int) -> Response:
"""Cancel a pending queue item."""