mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
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:
@@ -454,8 +454,11 @@ def make_end_of_cycle_updates_if_needed(
|
|||||||
licenses_at_next_renewal=licenses_at_next_renewal,
|
licenses_at_next_renewal=licenses_at_next_renewal,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
realm = new_plan.customer.realm
|
||||||
|
assert realm is not None
|
||||||
|
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
realm=new_plan.customer.realm,
|
realm=realm,
|
||||||
event_time=event_time,
|
event_time=event_time,
|
||||||
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
|
event_type=RealmAuditLog.CUSTOMER_SWITCHED_FROM_MONTHLY_TO_ANNUAL_PLAN,
|
||||||
extra_data=orjson.dumps(
|
extra_data=orjson.dumps(
|
||||||
@@ -629,6 +632,7 @@ def process_initial_upgrade(
|
|||||||
realm = user.realm
|
realm = user.realm
|
||||||
customer = update_or_create_stripe_customer(user)
|
customer = update_or_create_stripe_customer(user)
|
||||||
assert customer.stripe_customer_id is not None # for mypy
|
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)
|
ensure_realm_does_not_have_active_plan(customer.realm)
|
||||||
(
|
(
|
||||||
billing_cycle_anchor,
|
billing_cycle_anchor,
|
||||||
@@ -722,12 +726,14 @@ def update_license_ledger_for_manual_plan(
|
|||||||
licenses_at_next_renewal: Optional[int] = None,
|
licenses_at_next_renewal: Optional[int] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if licenses is not None:
|
if licenses is not None:
|
||||||
|
assert plan.customer.realm is not None
|
||||||
assert get_latest_seat_count(plan.customer.realm) <= licenses
|
assert get_latest_seat_count(plan.customer.realm) <= licenses
|
||||||
assert licenses > plan.licenses()
|
assert licenses > plan.licenses()
|
||||||
LicenseLedger.objects.create(
|
LicenseLedger.objects.create(
|
||||||
plan=plan, event_time=event_time, licenses=licenses, licenses_at_next_renewal=licenses
|
plan=plan, event_time=event_time, licenses=licenses, licenses_at_next_renewal=licenses
|
||||||
)
|
)
|
||||||
elif licenses_at_next_renewal is not None:
|
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
|
assert get_latest_seat_count(plan.customer.realm) <= licenses_at_next_renewal
|
||||||
LicenseLedger.objects.create(
|
LicenseLedger.objects.create(
|
||||||
plan=plan,
|
plan=plan,
|
||||||
@@ -779,6 +785,7 @@ def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
|
|||||||
if plan.invoicing_status == CustomerPlan.STARTED:
|
if plan.invoicing_status == CustomerPlan.STARTED:
|
||||||
raise NotImplementedError("Plan with invoicing_status==STARTED needs manual resolution.")
|
raise NotImplementedError("Plan with invoicing_status==STARTED needs manual resolution.")
|
||||||
if not plan.customer.stripe_customer_id:
|
if not plan.customer.stripe_customer_id:
|
||||||
|
assert plan.customer.realm is not None
|
||||||
raise BillingError(
|
raise BillingError(
|
||||||
f"Realm {plan.customer.realm.string_id} has a paid plan without a Stripe customer."
|
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:
|
def process_downgrade(plan: CustomerPlan) -> None:
|
||||||
from zerver.lib.actions import do_change_plan_type
|
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)
|
do_change_plan_type(plan.customer.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None)
|
||||||
plan.status = CustomerPlan.ENDED
|
plan.status = CustomerPlan.ENDED
|
||||||
plan.save(update_fields=["status"])
|
plan.save(update_fields=["status"])
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ def handle_checkout_session_completed_event(
|
|||||||
|
|
||||||
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
|
stripe_setup_intent = stripe.SetupIntent.retrieve(stripe_session.setup_intent)
|
||||||
stripe_customer = stripe.Customer.retrieve(stripe_setup_intent.customer)
|
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)
|
user = get_user_by_delivery_email(stripe_customer.email, session.customer.realm)
|
||||||
payment_method = stripe_setup_intent.payment_method
|
payment_method = stripe_setup_intent.payment_method
|
||||||
|
|
||||||
@@ -116,6 +117,7 @@ def handle_payment_intent_succeeded_event(
|
|||||||
payment_intent.status = PaymentIntent.SUCCEEDED
|
payment_intent.status = PaymentIntent.SUCCEEDED
|
||||||
payment_intent.save()
|
payment_intent.save()
|
||||||
metadata = stripe_payment_intent.metadata
|
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)
|
user = get_user_by_delivery_email(metadata["user_email"], payment_intent.customer.realm)
|
||||||
|
|
||||||
description = ""
|
description = ""
|
||||||
|
|||||||
@@ -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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -8,6 +8,7 @@ from django.db import models
|
|||||||
from django.db.models import CASCADE
|
from django.db.models import CASCADE
|
||||||
|
|
||||||
from zerver.models import Realm, UserProfile
|
from zerver.models import Realm, UserProfile
|
||||||
|
from zilencer.models import RemoteZulipServer
|
||||||
|
|
||||||
|
|
||||||
class Customer(models.Model):
|
class Customer(models.Model):
|
||||||
@@ -17,7 +18,10 @@ class Customer(models.Model):
|
|||||||
and the active plan, if any.
|
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)
|
stripe_customer_id: Optional[str] = models.CharField(max_length=255, null=True, unique=True)
|
||||||
sponsorship_pending: bool = models.BooleanField(default=False)
|
sponsorship_pending: bool = models.BooleanField(default=False)
|
||||||
# A percentage, like 85.
|
# A percentage, like 85.
|
||||||
@@ -30,6 +34,20 @@ class Customer(models.Model):
|
|||||||
# they purchased.
|
# they purchased.
|
||||||
exempt_from_from_license_number_check: bool = models.BooleanField(default=False)
|
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:
|
def __str__(self) -> str:
|
||||||
return f"<Customer {self.realm} {self.stripe_customer_id}>"
|
return f"<Customer {self.realm} {self.stripe_customer_id}>"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user