summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.tasks/tasks.jsonl6
-rw-r--r--Biz/PodcastItLater/Billing.py25
-rw-r--r--Biz/PodcastItLater/Web.py34
3 files changed, 54 insertions, 11 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl
index 3d0d504..9cf17f5 100644
--- a/.tasks/tasks.jsonl
+++ b/.tasks/tasks.jsonl
@@ -55,11 +55,11 @@
{"taskCreatedAt":"2025-11-13T19:38:33.491331064Z","taskDependencies":[],"taskId":"t-1fbABoD","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Extract extract_og_metadata and send_magic_link to Core module for reusability","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:46:04.679290775Z"}
{"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"}
{"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":"t-1f9QP23","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:28.494715433Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.709672316Z"}
{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskId":"t-1fbDyr2","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.194926176Z"}
{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskId":"t-1fbElKv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Open","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:38:34.384489707Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:28.242735917Z"}
-{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"InProgress","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:27.967202307Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.65796855Z"}
+{"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":"t-1f9RIzd","taskStatus":"Done","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.606196024Z"}
{"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":"t-1f9SnU7","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"}
{"taskCreatedAt":"2025-11-13T19:38:35.119686179Z","taskDependencies":[],"taskId":"t-1fbHr0w","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove button classes from navbar links (make them regular nav links)","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.185088389Z"}
{"taskCreatedAt":"2025-11-13T19:38:35.311151364Z","taskDependencies":[],"taskId":"t-1fbIeOF","taskNamespace":null,"taskParent":"t-1f9Td4U","taskStatus":"Done","taskTitle":"Remove 'Logged in as' text from navbar","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:18:17.23552934Z"}
diff --git a/Biz/PodcastItLater/Billing.py b/Biz/PodcastItLater/Billing.py
index 0e2537a..bf907bf 100644
--- a/Biz/PodcastItLater/Billing.py
+++ b/Biz/PodcastItLater/Billing.py
@@ -201,7 +201,7 @@ def create_portal_session(user_id: int, base_url: str) -> str:
Portal session URL to redirect user to
Raises:
- ValueError: If user has no Stripe customer ID
+ ValueError: If user has no Stripe customer ID or portal not configured
"""
user = Core.Database.get_user_by_id(user_id)
if not user:
@@ -212,12 +212,25 @@ def create_portal_session(user_id: int, base_url: str) -> str:
msg = "User has no Stripe customer ID"
raise ValueError(msg)
- session = stripe.billing_portal.Session.create(
- customer=user["stripe_customer_id"],
- return_url=f"{base_url}/",
- )
+ try:
+ session = stripe.billing_portal.Session.create(
+ customer=user["stripe_customer_id"],
+ return_url=f"{base_url}/account",
+ )
+ except Exception as e:
+ # Catch Stripe errors (portal not configured, etc.)
+ logger.exception("Stripe portal error")
+ msg = (
+ "Billing portal not configured. "
+ "Please contact support or cancel via your account page."
+ )
+ raise ValueError(msg) from e
- logger.info("Created portal session for user %s: %s", user_id, session.id)
+ logger.info(
+ "Created portal session for user %s: %s",
+ user_id,
+ session.id,
+ )
return session.url
diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py
index af603e2..c2f957a 100644
--- a/Biz/PodcastItLater/Web.py
+++ b/Biz/PodcastItLater/Web.py
@@ -1079,6 +1079,9 @@ def account_page(request: Request) -> html.html | RedirectResponse:
subscription_status = user.get("subscription_status", "")
cancel_at_period_end = user.get("cancel_at_period_end", 0) == 1
+ # Get error message from query params
+ error_message = request.query_params.get("error")
+
return html.html(
html.head(
html.meta(charset="utf-8"),
@@ -1128,6 +1131,28 @@ def account_page(request: Request) -> html.html | RedirectResponse:
],
),
),
+ # Error alert
+ html.div(
+ html.div(
+ html.i(
+ classes=[
+ "bi",
+ "bi-exclamation-triangle-fill",
+ "me-2",
+ ],
+ ),
+ error_message or "",
+ classes=[
+ "alert",
+ "alert-danger",
+ "d-flex",
+ "align-items-center",
+ ],
+ role="alert", # type: ignore[call-arg]
+ ),
+ )
+ if error_message
+ else html.div(),
# Account info section
html.div(
html.h4(
@@ -1498,7 +1523,7 @@ def billing_checkout(request: Request, data: FormData) -> Response:
@app.post("/billing/portal")
-def billing_portal(request: Request) -> Response:
+def billing_portal(request: Request) -> Response | RedirectResponse:
"""Create Stripe Billing Portal session."""
user_id = request.session.get("user_id")
if not user_id:
@@ -1509,7 +1534,12 @@ def billing_portal(request: Request) -> Response:
return RedirectResponse(url=portal_url, status_code=303)
except ValueError as e:
logger.exception("Portal error")
- return Response(f"Error: {e!s}", status_code=400)
+ # Redirect back to account page with error message
+ error_msg = str(e)
+ return RedirectResponse(
+ url=f"/account?error={urllib.parse.quote(error_msg)}",
+ status_code=303,
+ )
@app.post("/stripe/webhook")