Arslan1997 commited on
Commit
8e2a6eb
·
1 Parent(s): 811ee7a

LETS GOOOOOOOOOOOOOOOOOOOOOOOOO

Browse files
backend/app/config.py CHANGED
@@ -106,9 +106,14 @@ class Settings(BaseSettings):
106
 
107
  # Billing (Stripe). With BILLING_ENABLED=false everyone gets Studio limits and checkout is off.
108
  billing_enabled: bool = False
109
- stripe_secret_key: str = Field("", validation_alias=AliasChoices("STRIPE_SECRET_KEY", "STRIPE_API_KEY", "STRIPE_SECRET"))
 
 
110
  stripe_webhook_secret: str = Field(
111
- "", validation_alias=AliasChoices("STRIPE_WEBHOOK_SECRET", "STRIPE_WEBHOOK_SIGNING_SECRET", "STRIPE_SIGNING_SECRET")
 
 
 
112
  )
113
  # Optional existing Price ids. Empty = Notestack finds or creates prices by lookup key
114
  # (notestack_<plan>_<cycle>) from the plan table in services/plans.py.
 
106
 
107
  # Billing (Stripe). With BILLING_ENABLED=false everyone gets Studio limits and checkout is off.
108
  billing_enabled: bool = False
109
+ stripe_secret_key: str = Field(
110
+ "", validation_alias=AliasChoices("STRIPE_SECRET_KEY", "STRIPE_API_KEY", "STRIPE_SECRET")
111
+ )
112
  stripe_webhook_secret: str = Field(
113
+ "",
114
+ validation_alias=AliasChoices(
115
+ "STRIPE_WEBHOOK_SECRET", "STRIPE_WEBHOOK_SIGNING_SECRET", "STRIPE_SIGNING_SECRET"
116
+ ),
117
  )
118
  # Optional existing Price ids. Empty = Notestack finds or creates prices by lookup key
119
  # (notestack_<plan>_<cycle>) from the plan table in services/plans.py.
backend/app/routers/billing.py CHANGED
@@ -2,7 +2,6 @@ from typing import Literal
2
 
3
  from fastapi import APIRouter, Depends, HTTPException, Request
4
  from pydantic import BaseModel
5
-
6
  from sqlalchemy.orm import Session
7
 
8
  from app.auth import Ctx, get_ctx
 
2
 
3
  from fastapi import APIRouter, Depends, HTTPException, Request
4
  from pydantic import BaseModel
 
5
  from sqlalchemy.orm import Session
6
 
7
  from app.auth import Ctx, get_ctx
backend/app/services/billing.py CHANGED
@@ -6,7 +6,6 @@ Stripe goes in create_checkout_url, create_portal_url and the webhook route; eve
6
 
7
  from datetime import UTC, datetime
8
 
9
- from fastapi import HTTPException
10
  from sqlalchemy import func, select
11
  from sqlalchemy.orm import Session
12
 
 
6
 
7
  from datetime import UTC, datetime
8
 
 
9
  from sqlalchemy import func, select
10
  from sqlalchemy.orm import Session
11
 
backend/app/services/stripe_billing.py CHANGED
@@ -226,11 +226,13 @@ def portal_url(db: Session, workspace: Workspace, return_url: str) -> str:
226
  if not row or not row.provider_customer_id:
227
  raise HTTPException(404, {"code": "no_billing_account", "message": "There is no billing account yet."})
228
  try:
229
- session = client().v1.billing_portal.sessions.create({"customer": row.provider_customer_id, "return_url": return_url})
 
 
230
  except stripe.InvalidRequestError as exc:
231
  log.error("stripe portal failed: %s", exc)
232
  raise HTTPException(503, {"code": "portal_unavailable",
233
- "message": "Billing management is unavailable right now. Please try again soon."}) from exc
234
  return session.url
235
 
236
 
@@ -327,7 +329,8 @@ def handle_event(db: Session, event) -> str:
327
  return "synced"
328
 
329
  if kind.startswith("invoice."):
330
- sub_id = _get(obj, "subscription") or _get(_get(_get(obj, "parent", {}), "subscription_details", {}), "subscription")
 
331
  ws = _workspace_for(db, {"id": sub_id} if sub_id else None, customer_id=_get(obj, "customer"))
332
  if ws and sub_id:
333
  sync_subscription(db, ws, sc.v1.subscriptions.retrieve(sub_id))
@@ -337,7 +340,8 @@ def handle_event(db: Session, event) -> str:
337
  to = _get(obj, "customer_email") or _owner_email(db, ws)
338
  url = _get(obj, "hosted_invoice_url") or f"{settings.frontend_url.rstrip('/')}/app/settings"
339
  if to:
340
- email_service.send_payment_action(to, "confirm" if kind.endswith("action_required") else "failed", url)
 
341
  return "synced" if ws else "unknown"
342
 
343
  if kind == "charge.dispute.created" and first:
 
226
  if not row or not row.provider_customer_id:
227
  raise HTTPException(404, {"code": "no_billing_account", "message": "There is no billing account yet."})
228
  try:
229
+ session = client().v1.billing_portal.sessions.create(
230
+ {"customer": row.provider_customer_id, "return_url": return_url}
231
+ )
232
  except stripe.InvalidRequestError as exc:
233
  log.error("stripe portal failed: %s", exc)
234
  raise HTTPException(503, {"code": "portal_unavailable",
235
+ "message": "Billing management is unavailable. Please try again soon."}) from exc
236
  return session.url
237
 
238
 
 
329
  return "synced"
330
 
331
  if kind.startswith("invoice."):
332
+ details = _get(_get(obj, "parent", {}), "subscription_details", {}) # newer API versions
333
+ sub_id = _get(obj, "subscription") or _get(details, "subscription")
334
  ws = _workspace_for(db, {"id": sub_id} if sub_id else None, customer_id=_get(obj, "customer"))
335
  if ws and sub_id:
336
  sync_subscription(db, ws, sc.v1.subscriptions.retrieve(sub_id))
 
340
  to = _get(obj, "customer_email") or _owner_email(db, ws)
341
  url = _get(obj, "hosted_invoice_url") or f"{settings.frontend_url.rstrip('/')}/app/settings"
342
  if to:
343
+ action = "confirm" if kind.endswith("action_required") else "failed"
344
+ email_service.send_payment_action(to, action, url)
345
  return "synced" if ws else "unknown"
346
 
347
  if kind == "charge.dispute.created" and first:
backend/requirements.txt CHANGED
@@ -17,3 +17,4 @@ dspy==2.6.*
17
  feedparser==6.*
18
  beautifulsoup4==4.*
19
  pypdf==5.*
 
 
17
  feedparser==6.*
18
  beautifulsoup4==4.*
19
  pypdf==5.*
20
+ stripe==13.*
frontend/src/components/AppShell.tsx CHANGED
@@ -72,11 +72,11 @@ export default function AppShell() {
72
  <div className="sidebar-top">
73
  {collapsed ? (
74
  <button type="button" className="sidebar-brand sidebar-brand-btn" onClick={toggle} aria-label="Expand sidebar" title="Expand sidebar">
75
- <Logo size={28} />
76
  </button>
77
  ) : (
78
  <NavLink to="/" className="sidebar-brand">
79
- <Logo size={28} />
80
  </NavLink>
81
  )}
82
  <button
 
72
  <div className="sidebar-top">
73
  {collapsed ? (
74
  <button type="button" className="sidebar-brand sidebar-brand-btn" onClick={toggle} aria-label="Expand sidebar" title="Expand sidebar">
75
+ <Logo size={28} withWordmark={false} />
76
  </button>
77
  ) : (
78
  <NavLink to="/" className="sidebar-brand">
79
+ <Logo size={28} withWordmark={false} />
80
  </NavLink>
81
  )}
82
  <button
frontend/src/styles/pages.css CHANGED
@@ -1874,6 +1874,12 @@ select.input option {
1874
  gap: 14px;
1875
  }
1876
 
 
 
 
 
 
 
1877
  .side-link {
1878
  padding: 7px 10px;
1879
  font-size: 0.9rem;
 
1874
  gap: 14px;
1875
  }
1876
 
1877
+ /* Collapsed: just the logo (and the upgrade rocket), so nothing to scroll. */
1878
+ .shell.collapsed > .sidebar {
1879
+ overflow: hidden;
1880
+ padding: 14px 10px;
1881
+ }
1882
+
1883
  .side-link {
1884
  padding: 7px 10px;
1885
  font-size: 0.9rem;