corporate/models: Modify Customer to accommodate self-hosted customers.

This is a part of our efforts to introduce billing for our on-premise
customers.
This commit is contained in:
Eeshan Garg 2021-11-20 19:20:14 +05:30 committed by Tim Abbott
parent f760850993
commit a3095331eb
4 changed files with 62 additions and 2 deletions

View File

@ -454,8 +454,11 @@ def make_end_of_cycle_updates_if_needed(
licenses_at_next_renewal=licenses_at_next_renewal,
)
realm = new_plan.customer.realm
assert realm is not None
RealmAuditLog.objects.create(
realm=new_plan.customer.realm,
realm=realm,
event_time=event_time,
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
extra_data=orjson.dumps(
@ -629,6 +632,7 @@ def process_initial_upgrade(
realm = user.realm
customer = update_or_create_stripe_customer(user)
assert customer.stripe_customer_id is not None # for mypy
assert customer.realm is not None
ensure_realm_does_not_have_active_plan(customer.realm)
(
billing_cycle_anchor,
@ -722,12 +726,14 @@ def update_license_ledger_for_manual_plan(
licenses_at_next_renewal: Optional[int] = None,
) -> None:
if licenses is not None:
assert plan.customer.realm is not None
assert get_latest_seat_count(plan.customer.realm) <= licenses
assert licenses > plan.licenses()
LicenseLedger.objects.create(
plan=plan, event_time=event_time, licenses=licenses, licenses_at_next_renewal=licenses
)
elif licenses_at_next_renewal is not None:
assert plan.customer.realm is not None
assert get_latest_seat_count(plan.customer.realm) <= licenses_at_next_renewal
LicenseLedger.objects.create(
plan=plan,
@ -779,6 +785,7 @@ def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
if plan.invoicing_status == CustomerPlan.STARTED:
raise NotImplementedError("Plan with invoicing_status==STARTED needs manual resolution.")
if not plan.customer.stripe_customer_id:
assert plan.customer.realm is not None
raise BillingError(
f"Realm {plan.customer.realm.string_id} has a paid plan without a Stripe customer."
)
@ -981,6 +988,7 @@ def do_change_plan_status(plan: CustomerPlan, status: int) -> None:
def process_downgrade(plan: CustomerPlan) -> None:
from zerver.lib.actions import do_change_plan_type
assert plan.customer.realm is not None
do_change_plan_type(plan.customer.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
plan.status = CustomerPlan.ENDED
plan.save(update_fields=["status"])

View File

@ -71,6 +71,7 @@ def handle_checkout_session_completed_event(
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
stripe_customer = stripe.Customer.retrieve(stripe_setup_intent.customer)
assert session.customer.realm is not None
user = get_user_by_delivery_email(stripe_customer.email, session.customer.realm)
payment_method = stripe_setup_intent.payment_method
@ -116,6 +117,7 @@ def handle_payment_intent_succeeded_event(
payment_intent.status = PaymentIntent.SUCCEEDED
payment_intent.save()
metadata = stripe_payment_intent.metadata
assert payment_intent.customer.realm is not None
user = get_user_by_delivery_email(metadata["user_email"], payment_intent.customer.realm)
description = ""

View File

@ -0,0 +1,32 @@
# Generated by Django 3.2.9 on 2021-11-27 00:08
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zilencer", "0018_remoterealmauditlog"),
("zerver", "0370_realm_enable_spectator_access"),
("corporate", "0015_event_paymentintent_session"),
]
operations = [
migrations.AddField(
model_name="customer",
name="remote_server",
field=models.OneToOneField(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="zilencer.remotezulipserver",
),
),
migrations.AlterField(
model_name="customer",
name="realm",
field=models.OneToOneField(
null=True, on_delete=django.db.models.deletion.CASCADE, to="zerver.realm"
),
),
]

View File

@ -8,6 +8,7 @@ from django.db import models
from django.db.models import CASCADE
from zerver.models import Realm, UserProfile
from zilencer.models import RemoteZulipServer
class Customer(models.Model):
@ -17,7 +18,10 @@ class Customer(models.Model):
and the active plan, if any.
"""
realm: Realm = models.OneToOneField(Realm, on_delete=CASCADE)
realm: Optional[Realm] = models.OneToOneField(Realm, on_delete=CASCADE, null=True)
remote_server: Optional[RemoteZulipServer] = models.OneToOneField(
RemoteZulipServer, on_delete=CASCADE, null=True
)
stripe_customer_id: Optional[str] = models.CharField(max_length=255, null=True, unique=True)
sponsorship_pending: bool = models.BooleanField(default=False)
# A percentage, like 85.
@ -30,6 +34,20 @@ class Customer(models.Model):
# they purchased.
exempt_from_from_license_number_check: bool = models.BooleanField(default=False)
@property
def is_self_hosted(self) -> bool:
is_self_hosted = self.remote_server is not None
if is_self_hosted:
assert self.realm is None
return is_self_hosted
@property
def is_cloud(self) -> bool:
is_cloud = self.realm is not None
if is_cloud:
assert self.remote_server is None
return is_cloud
def __str__(self) -> str:
return f"<Customer {self.realm} {self.stripe_customer_id}>"