Add account self-delete and subscription lifecycle sync (#34)
Soft-delete DELETE /api/user/ for authenticated users (hide conversations, blacklist tokens, block staff self-delete). Sync Stripe portal cancel/change via subscription.updated/deleted webhooks and expose cancel_at_period_end for Account UI (chat_web_app#75 companion).
This commit is contained in:
@@ -221,17 +221,66 @@ def assign_founders_from_stripe(
|
||||
*,
|
||||
stripe_subscription_id: str = "",
|
||||
) -> UserSubscription:
|
||||
"""Backward-compatible helper; prefer ``assign_plan_from_stripe``."""
|
||||
return assign_plan_from_stripe(
|
||||
user,
|
||||
plan_slug=SubscriptionPlan.Slug.FOUNDERS,
|
||||
stripe_subscription_id=stripe_subscription_id,
|
||||
)
|
||||
|
||||
|
||||
def assign_plan_from_stripe(
|
||||
user,
|
||||
*,
|
||||
plan_slug: str | None = None,
|
||||
stripe_subscription_id: str = "",
|
||||
status: str = UserSubscription.Status.ACTIVE,
|
||||
cancel_at_period_end: bool | None = None,
|
||||
current_period_end=None,
|
||||
keep_existing_plan_if_unknown: bool = False,
|
||||
) -> UserSubscription:
|
||||
"""Assign a catalog plan from a Stripe Checkout / subscription event."""
|
||||
seed_subscription_plans(update_existing=False)
|
||||
plan = get_plan(SubscriptionPlan.Slug.FOUNDERS)
|
||||
slug = (plan_slug or "").strip().lower()
|
||||
plan = get_plan(slug) if slug else None
|
||||
if plan is None and keep_existing_plan_if_unknown:
|
||||
existing = UserSubscription.objects.filter(user=user).select_related("plan").first()
|
||||
if existing and existing.plan_id:
|
||||
plan = existing.plan
|
||||
if plan is None:
|
||||
if slug:
|
||||
logger.warning(
|
||||
"Unknown plan_slug=%s; falling back to Founders for user=%s",
|
||||
slug,
|
||||
getattr(user, "pk", None),
|
||||
)
|
||||
plan = get_plan(SubscriptionPlan.Slug.FOUNDERS)
|
||||
if plan is None:
|
||||
raise RuntimeError("Founders plan missing from catalog")
|
||||
return assign_plan(
|
||||
sub = assign_plan(
|
||||
user,
|
||||
plan=plan,
|
||||
source=UserSubscription.Source.STRIPE,
|
||||
status=UserSubscription.Status.ACTIVE,
|
||||
status=status,
|
||||
stripe_subscription_id=stripe_subscription_id or "",
|
||||
)
|
||||
update_fields: list[str] = []
|
||||
if cancel_at_period_end is not None:
|
||||
sub.cancel_at_period_end = bool(cancel_at_period_end)
|
||||
update_fields.append("cancel_at_period_end")
|
||||
if current_period_end is not None:
|
||||
sub.current_period_end = current_period_end
|
||||
update_fields.append("current_period_end")
|
||||
if update_fields:
|
||||
sub.save(update_fields=update_fields)
|
||||
return sub
|
||||
|
||||
|
||||
def resolve_plan_from_stripe_price(price_id: str | None) -> SubscriptionPlan | None:
|
||||
"""Map a Stripe Price id to a local SubscriptionPlan when configured."""
|
||||
if not price_id:
|
||||
return None
|
||||
return SubscriptionPlan.objects.filter(stripe_price_id=price_id).first()
|
||||
|
||||
|
||||
def plan_to_dict(plan: SubscriptionPlan | None) -> dict[str, Any] | None:
|
||||
|
||||
Reference in New Issue
Block a user