Add account self-delete and subscription lifecycle sync (#34)
CI / test (pull_request) Successful in 10s
Unit Tests / test (pull_request) Successful in 9s

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:
2026-08-01 14:15:09 -05:00
parent cc45ae5808
commit b3203f755d
12 changed files with 542 additions and 9 deletions
+64 -1
View File
@@ -8,10 +8,13 @@ from rest_framework import status
from rest_framework.test import APITestCase
from chat_backend.tests.factories import make_company, make_user
from finance.models import Invoice, Payment
from finance.models import Invoice, Payment, UserSubscription
from finance.services.plans import assign_plan_from_stripe, seed_subscription_plans
from finance.services.webhooks import (
dispatch_stripe_event,
handle_checkout_session_completed,
handle_customer_subscription_deleted,
handle_customer_subscription_updated,
handle_invoice_paid,
handle_invoice_payment_failed,
)
@@ -107,6 +110,66 @@ class WebhookHandlerUnitTestCase(APITestCase):
)
self.assertIsNone(result)
def test_checkout_assigns_plan_from_metadata(self):
seed_subscription_plans(update_existing=False)
session = {
"id": "cs_test_plan_meta",
"metadata": {"user_id": str(self.user.pk), "plan_slug": "founders"},
"customer": "cus_meta",
"subscription": "sub_meta",
"payment_intent": "pi_meta",
"payment_status": "paid",
"amount_total": 1000,
"currency": "usd",
}
handle_checkout_session_completed(session)
sub = self.user.subscription
self.assertEqual(sub.plan.slug, "founders")
self.assertEqual(sub.source, UserSubscription.Source.STRIPE)
self.assertEqual(sub.status, UserSubscription.Status.ACTIVE)
def test_subscription_updated_sets_cancel_at_period_end(self):
seed_subscription_plans(update_existing=False)
assign_plan_from_stripe(
self.user,
plan_slug="founders",
stripe_subscription_id="sub_cancel",
)
result = handle_customer_subscription_updated(
{
"id": "sub_cancel",
"status": "active",
"cancel_at_period_end": True,
"current_period_end": 1_700_259_200,
"metadata": {"user_id": str(self.user.pk), "plan_slug": "founders"},
}
)
self.assertIsNotNone(result)
sub = UserSubscription.objects.get(user=self.user)
self.assertTrue(sub.cancel_at_period_end)
self.assertEqual(sub.status, UserSubscription.Status.ACTIVE)
self.assertIsNotNone(sub.current_period_end)
def test_subscription_deleted_marks_canceled(self):
seed_subscription_plans(update_existing=False)
assign_plan_from_stripe(
self.user,
plan_slug="founders",
stripe_subscription_id="sub_gone",
)
result = handle_customer_subscription_deleted(
{
"id": "sub_gone",
"status": "canceled",
"current_period_end": 1_700_259_200,
"metadata": {"user_id": str(self.user.pk)},
}
)
self.assertIsNotNone(result)
sub = UserSubscription.objects.get(user=self.user)
self.assertEqual(sub.status, UserSubscription.Status.CANCELED)
self.assertFalse(sub.cancel_at_period_end)
class StripeWebhookViewTestCase(APITestCase):
def setUp(self):