Add multi-plan subscriptions, quotas, and token usage APIs
Implements #16/#17/#36: Founders/Standard/Pro/Business/Backer catalog, Backer email whitelist, prompt-window + token-period gates, and tokens_in/out on conversation/prompt + subscription usage APIs.
This commit is contained in:
@@ -7,7 +7,8 @@ from typing import Any
|
||||
import stripe
|
||||
from django.conf import settings
|
||||
|
||||
from finance.models import Invoice
|
||||
from finance.models import Invoice, SubscriptionPlan
|
||||
from finance.services.plans import get_plan, seed_subscription_plans
|
||||
|
||||
|
||||
class StripeNotConfiguredError(RuntimeError):
|
||||
@@ -24,21 +25,47 @@ def configure_stripe() -> str:
|
||||
return secret
|
||||
|
||||
|
||||
def subscription_line_items() -> list[dict[str, Any]]:
|
||||
"""Build Checkout line_items from settings-backed subscription pricing."""
|
||||
price_id = settings.STRIPE_PRICE_ID
|
||||
def resolve_checkout_plan(plan_slug: str | None = None) -> SubscriptionPlan:
|
||||
"""Return the plan for Checkout (defaults to public Founders)."""
|
||||
seed_subscription_plans(update_existing=False)
|
||||
slug = (plan_slug or SubscriptionPlan.Slug.FOUNDERS).strip().lower()
|
||||
plan = get_plan(slug)
|
||||
if plan is None:
|
||||
raise ValueError(f"Unknown plan: {slug}")
|
||||
if not plan.is_selectable:
|
||||
raise ValueError(f"Plan '{plan.slug}' is not available for checkout.")
|
||||
return plan
|
||||
|
||||
|
||||
def subscription_line_items(plan: SubscriptionPlan) -> list[dict[str, Any]]:
|
||||
"""Build Checkout line_items from a SubscriptionPlan (or legacy settings)."""
|
||||
price_id = (plan.stripe_price_id or "").strip() or (
|
||||
settings.STRIPE_PRICE_ID if plan.slug == SubscriptionPlan.Slug.FOUNDERS else ""
|
||||
)
|
||||
if price_id:
|
||||
return [{"price": price_id, "quantity": 1}]
|
||||
|
||||
# Founders without a plan stripe_price_id may still use legacy env amount.
|
||||
unit_amount = plan.price_cents
|
||||
product_name = plan.name
|
||||
currency = plan.currency or settings.SUBSCRIPTION_PRICE_CURRENCY
|
||||
interval = plan.interval or settings.SUBSCRIPTION_PRICE_INTERVAL
|
||||
if plan.slug == SubscriptionPlan.Slug.FOUNDERS and not plan.stripe_price_id:
|
||||
# Keep env overrides working for the live Founders price.
|
||||
unit_amount = int(
|
||||
getattr(settings, "SUBSCRIPTION_PRICE_AMOUNT_CENTS", None) or unit_amount
|
||||
)
|
||||
product_name = (
|
||||
getattr(settings, "SUBSCRIPTION_PRODUCT_NAME", None) or product_name
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"price_data": {
|
||||
"currency": settings.SUBSCRIPTION_PRICE_CURRENCY,
|
||||
"unit_amount": settings.SUBSCRIPTION_PRICE_AMOUNT_CENTS,
|
||||
"recurring": {"interval": settings.SUBSCRIPTION_PRICE_INTERVAL},
|
||||
"product_data": {
|
||||
"name": settings.SUBSCRIPTION_PRODUCT_NAME,
|
||||
},
|
||||
"currency": currency,
|
||||
"unit_amount": unit_amount,
|
||||
"recurring": {"interval": interval},
|
||||
"product_data": {"name": product_name},
|
||||
},
|
||||
"quantity": 1,
|
||||
}
|
||||
@@ -50,19 +77,22 @@ def create_checkout_session(
|
||||
user,
|
||||
success_url: str | None = None,
|
||||
cancel_url: str | None = None,
|
||||
plan_slug: str | None = None,
|
||||
):
|
||||
"""Create a Stripe Checkout Session for the subscription plan."""
|
||||
"""Create a Stripe Checkout Session for a selectable subscription plan."""
|
||||
configure_stripe()
|
||||
plan = resolve_checkout_plan(plan_slug)
|
||||
|
||||
metadata = {
|
||||
"user_id": str(user.pk),
|
||||
"company_id": str(user.company_id) if user.company_id else "",
|
||||
"plan_slug": plan.slug,
|
||||
}
|
||||
customer_email = getattr(user, "email", None) or None
|
||||
|
||||
session = stripe.checkout.Session.create(
|
||||
mode="subscription",
|
||||
line_items=subscription_line_items(),
|
||||
line_items=subscription_line_items(plan),
|
||||
success_url=success_url or settings.STRIPE_CHECKOUT_SUCCESS_URL,
|
||||
cancel_url=cancel_url or settings.STRIPE_CHECKOUT_CANCEL_URL,
|
||||
customer_email=customer_email,
|
||||
@@ -70,7 +100,7 @@ def create_checkout_session(
|
||||
metadata=metadata,
|
||||
subscription_data={"metadata": metadata},
|
||||
)
|
||||
return session
|
||||
return session, plan
|
||||
|
||||
|
||||
def resolve_stripe_customer_id(*, user) -> str | None:
|
||||
|
||||
Reference in New Issue
Block a user