mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
models: Move billing models from zilencer to corporate.
This commit is contained in:
@@ -18,7 +18,7 @@ from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
|
|||||||
from zerver.lib.utils import generate_random_token
|
from zerver.lib.utils import generate_random_token
|
||||||
from zerver.lib.actions import do_change_plan_type
|
from zerver.lib.actions import do_change_plan_type
|
||||||
from zerver.models import Realm, UserProfile, RealmAuditLog
|
from zerver.models import Realm, UserProfile, RealmAuditLog
|
||||||
from zilencer.models import Customer, Plan, Coupon, BillingProcessor
|
from corporate.models import Customer, Plan, Coupon, BillingProcessor
|
||||||
from zproject.settings import get_secret
|
from zproject.settings import get_secret
|
||||||
|
|
||||||
STRIPE_PUBLISHABLE_KEY = get_secret('stripe_publishable_key')
|
STRIPE_PUBLISHABLE_KEY = get_secret('stripe_publishable_key')
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from zerver.lib.context_managers import lockfile
|
|||||||
from zerver.lib.management import sleep_forever
|
from zerver.lib.management import sleep_forever
|
||||||
from corporate.lib.stripe import StripeConnectionError, \
|
from corporate.lib.stripe import StripeConnectionError, \
|
||||||
run_billing_processor_one_step
|
run_billing_processor_one_step
|
||||||
from zilencer.models import BillingProcessor
|
from corporate.models import BillingProcessor
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = """Run BillingProcessors, to sync billing-relevant updates into Stripe.
|
help = """Run BillingProcessors, to sync billing-relevant updates into Stripe.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from zerver.lib.management import ZulipBaseCommand
|
from zerver.lib.management import ZulipBaseCommand
|
||||||
from zilencer.models import Plan, Coupon, Customer
|
from corporate.models import Plan, Coupon, Customer
|
||||||
from zproject.settings import get_secret
|
from zproject.settings import get_secret
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|||||||
53
corporate/migrations/0001_initial.py
Normal file
53
corporate/migrations/0001_initial.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.14 on 2018-09-25 12:02
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0189_userprofile_add_some_emojisets'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BillingProcessor',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('state', models.CharField(max_length=20)),
|
||||||
|
('last_modified', models.DateTimeField(auto_now=True)),
|
||||||
|
('log_row', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='zerver.RealmAuditLog')),
|
||||||
|
('realm', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Coupon',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('percent_off', models.SmallIntegerField(unique=True)),
|
||||||
|
('stripe_coupon_id', models.CharField(max_length=255, unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Customer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('stripe_customer_id', models.CharField(max_length=255, unique=True)),
|
||||||
|
('has_billing_relationship', models.BooleanField(default=False)),
|
||||||
|
('realm', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='zerver.Realm')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Plan',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nickname', models.CharField(max_length=40, unique=True)),
|
||||||
|
('stripe_plan_id', models.CharField(max_length=255, unique=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
corporate/migrations/__init__.py
Normal file
0
corporate/migrations/__init__.py
Normal file
46
corporate/models.py
Normal file
46
corporate/models.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from zerver.models import Realm, RealmAuditLog
|
||||||
|
|
||||||
|
class Customer(models.Model):
|
||||||
|
realm = models.OneToOneField(Realm, on_delete=models.CASCADE) # type: Realm
|
||||||
|
stripe_customer_id = models.CharField(max_length=255, unique=True) # type: str
|
||||||
|
# Becomes True the first time a payment successfully goes through, and never
|
||||||
|
# goes back to being False
|
||||||
|
has_billing_relationship = models.BooleanField(default=False) # type: bool
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "<Customer %s %s>" % (self.realm, self.stripe_customer_id)
|
||||||
|
|
||||||
|
class Plan(models.Model):
|
||||||
|
# The two possible values for nickname
|
||||||
|
CLOUD_MONTHLY = 'monthly'
|
||||||
|
CLOUD_ANNUAL = 'annual'
|
||||||
|
nickname = models.CharField(max_length=40, unique=True) # type: str
|
||||||
|
|
||||||
|
stripe_plan_id = models.CharField(max_length=255, unique=True) # type: str
|
||||||
|
|
||||||
|
class Coupon(models.Model):
|
||||||
|
percent_off = models.SmallIntegerField(unique=True) # type: int
|
||||||
|
stripe_coupon_id = models.CharField(max_length=255, unique=True) # type: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '<Coupon: %s %s %s>' % (self.percent_off, self.stripe_coupon_id, self.id)
|
||||||
|
|
||||||
|
class BillingProcessor(models.Model):
|
||||||
|
log_row = models.ForeignKey(RealmAuditLog, on_delete=models.CASCADE) # RealmAuditLog
|
||||||
|
# Exactly one processor, the global processor, has realm=None.
|
||||||
|
realm = models.OneToOneField(Realm, null=True, on_delete=models.CASCADE) # type: Realm
|
||||||
|
|
||||||
|
DONE = 'done'
|
||||||
|
STARTED = 'started'
|
||||||
|
SKIPPED = 'skipped' # global processor only
|
||||||
|
STALLED = 'stalled' # realm processors only
|
||||||
|
state = models.CharField(max_length=20) # type: str
|
||||||
|
|
||||||
|
last_modified = models.DateTimeField(auto_now=True) # type: datetime.datetime
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '<BillingProcessor: %s %s %s>' % (self.realm, self.log_row, self.id)
|
||||||
@@ -21,7 +21,7 @@ from corporate.lib.stripe import catch_stripe_errors, \
|
|||||||
get_seat_count, extract_current_subscription, sign_string, unsign_string, \
|
get_seat_count, extract_current_subscription, sign_string, unsign_string, \
|
||||||
get_next_billing_log_entry, run_billing_processor_one_step, \
|
get_next_billing_log_entry, run_billing_processor_one_step, \
|
||||||
BillingError, StripeCardError, StripeConnectionError
|
BillingError, StripeCardError, StripeConnectionError
|
||||||
from zilencer.models import Customer, Plan, Coupon, BillingProcessor
|
from corporate.models import Customer, Plan, Coupon, BillingProcessor
|
||||||
|
|
||||||
fixture_data_file = open(os.path.join(os.path.dirname(__file__), 'stripe_fixtures.json'), 'r')
|
fixture_data_file = open(os.path.join(os.path.dirname(__file__), 'stripe_fixtures.json'), 'r')
|
||||||
fixture_data = ujson.load(fixture_data_file)
|
fixture_data = ujson.load(fixture_data_file)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from corporate.lib.stripe import STRIPE_PUBLISHABLE_KEY, \
|
|||||||
stripe_get_customer, stripe_get_upcoming_invoice, get_seat_count, \
|
stripe_get_customer, stripe_get_upcoming_invoice, get_seat_count, \
|
||||||
extract_current_subscription, process_initial_upgrade, sign_string, \
|
extract_current_subscription, process_initial_upgrade, sign_string, \
|
||||||
unsign_string, BillingError, process_downgrade, do_replace_payment_source
|
unsign_string, BillingError, process_downgrade, do_replace_payment_source
|
||||||
from zilencer.models import Customer, Plan
|
from corporate.models import Customer, Plan
|
||||||
|
|
||||||
billing_logger = logging.getLogger('corporate.stripe')
|
billing_logger = logging.getLogger('corporate.stripe')
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ To set up the development environment to work on the billing code:
|
|||||||
|
|
||||||
It is safe to run `manage.py setup_stripe` multiple times.
|
It is safe to run `manage.py setup_stripe` multiple times.
|
||||||
|
|
||||||
Nearly all the billing-relevant code lives in `zilencer/`.
|
Nearly all the billing-relevant code lives in `corporate/`.
|
||||||
|
|
||||||
## General architecture
|
## General architecture
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from zerver.models import (
|
|||||||
flush_per_request_caches, DefaultStream, Realm,
|
flush_per_request_caches, DefaultStream, Realm,
|
||||||
)
|
)
|
||||||
from zerver.views.home import home, sent_time_in_epoch_seconds
|
from zerver.views.home import home, sent_time_in_epoch_seconds
|
||||||
from zilencer.models import Customer
|
from corporate.models import Customer
|
||||||
|
|
||||||
class HomeTest(ZulipTestCase):
|
class HomeTest(ZulipTestCase):
|
||||||
def test_home(self) -> None:
|
def test_home(self) -> None:
|
||||||
|
|||||||
@@ -250,8 +250,8 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
|||||||
|
|
||||||
show_billing = False
|
show_billing = False
|
||||||
show_plans = False
|
show_plans = False
|
||||||
if settings.ZILENCER_ENABLED:
|
if settings.CORPORATE_ENABLED:
|
||||||
from zilencer.models import Customer
|
from corporate.models import Customer
|
||||||
if user_profile.is_billing_admin or user_profile.is_realm_admin:
|
if user_profile.is_billing_admin or user_profile.is_realm_admin:
|
||||||
customer = Customer.objects.filter(realm=user_profile.realm).first()
|
customer = Customer.objects.filter(realm=user_profile.realm).first()
|
||||||
if customer is not None and customer.has_billing_relationship:
|
if customer is not None and customer.has_billing_relationship:
|
||||||
|
|||||||
39
zilencer/migrations/0015_delete_billing.py
Normal file
39
zilencer/migrations/0015_delete_billing.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.14 on 2018-09-25 12:01
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zilencer', '0014_cleanup_pushdevicetoken'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='billingprocessor',
|
||||||
|
name='log_row',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='billingprocessor',
|
||||||
|
name='realm',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Coupon',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='customer',
|
||||||
|
name='realm',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Plan',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='BillingProcessor',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Customer',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2,8 +2,7 @@ import datetime
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from zerver.models import AbstractPushDeviceToken, Realm, UserProfile, \
|
from zerver.models import AbstractPushDeviceToken
|
||||||
RealmAuditLog
|
|
||||||
|
|
||||||
def get_remote_server_by_uuid(uuid: str) -> 'RemoteZulipServer':
|
def get_remote_server_by_uuid(uuid: str) -> 'RemoteZulipServer':
|
||||||
return RemoteZulipServer.objects.get(uuid=uuid)
|
return RemoteZulipServer.objects.get(uuid=uuid)
|
||||||
@@ -35,44 +34,3 @@ class RemotePushDeviceToken(AbstractPushDeviceToken):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "<RemotePushDeviceToken %s %s>" % (self.server, self.user_id)
|
return "<RemotePushDeviceToken %s %s>" % (self.server, self.user_id)
|
||||||
|
|
||||||
class Customer(models.Model):
|
|
||||||
realm = models.OneToOneField(Realm, on_delete=models.CASCADE) # type: Realm
|
|
||||||
stripe_customer_id = models.CharField(max_length=255, unique=True) # type: str
|
|
||||||
# Becomes True the first time a payment successfully goes through, and never
|
|
||||||
# goes back to being False
|
|
||||||
has_billing_relationship = models.BooleanField(default=False) # type: bool
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return "<Customer %s %s>" % (self.realm, self.stripe_customer_id)
|
|
||||||
|
|
||||||
class Plan(models.Model):
|
|
||||||
# The two possible values for nickname
|
|
||||||
CLOUD_MONTHLY = 'monthly'
|
|
||||||
CLOUD_ANNUAL = 'annual'
|
|
||||||
nickname = models.CharField(max_length=40, unique=True) # type: str
|
|
||||||
|
|
||||||
stripe_plan_id = models.CharField(max_length=255, unique=True) # type: str
|
|
||||||
|
|
||||||
class Coupon(models.Model):
|
|
||||||
percent_off = models.SmallIntegerField(unique=True) # type: int
|
|
||||||
stripe_coupon_id = models.CharField(max_length=255, unique=True) # type: str
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return '<Coupon: %s %s %s>' % (self.percent_off, self.stripe_coupon_id, self.id)
|
|
||||||
|
|
||||||
class BillingProcessor(models.Model):
|
|
||||||
log_row = models.ForeignKey(RealmAuditLog, on_delete=models.CASCADE) # RealmAuditLog
|
|
||||||
# Exactly one processor, the global processor, has realm=None.
|
|
||||||
realm = models.OneToOneField(Realm, null=True, on_delete=models.CASCADE) # type: Realm
|
|
||||||
|
|
||||||
DONE = 'done'
|
|
||||||
STARTED = 'started'
|
|
||||||
SKIPPED = 'skipped' # global processor only
|
|
||||||
STALLED = 'stalled' # realm processors only
|
|
||||||
state = models.CharField(max_length=20) # type: str
|
|
||||||
|
|
||||||
last_modified = models.DateTimeField(auto_now=True) # type: datetime.datetime
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return '<BillingProcessor: %s %s %s>' % (self.realm, self.log_row, self.id)
|
|
||||||
|
|||||||
@@ -575,6 +575,7 @@ if USING_PGROONGA:
|
|||||||
INSTALLED_APPS += EXTRA_INSTALLED_APPS
|
INSTALLED_APPS += EXTRA_INSTALLED_APPS
|
||||||
|
|
||||||
ZILENCER_ENABLED = 'zilencer' in INSTALLED_APPS
|
ZILENCER_ENABLED = 'zilencer' in INSTALLED_APPS
|
||||||
|
CORPORATE_ENABLED = 'corporate' in INSTALLED_APPS
|
||||||
|
|
||||||
# Base URL of the Tornado server
|
# Base URL of the Tornado server
|
||||||
# We set it to None when running backend tests or populate_db.
|
# We set it to None when running backend tests or populate_db.
|
||||||
|
|||||||
Reference in New Issue
Block a user