diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 47f3eb3a72..9965ad8edf 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -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.actions import do_change_plan_type 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 STRIPE_PUBLISHABLE_KEY = get_secret('stripe_publishable_key') diff --git a/corporate/management/commands/process_billing_updates.py b/corporate/management/commands/process_billing_updates.py index 5391bcf521..6aae5b5137 100644 --- a/corporate/management/commands/process_billing_updates.py +++ b/corporate/management/commands/process_billing_updates.py @@ -17,7 +17,7 @@ from zerver.lib.context_managers import lockfile from zerver.lib.management import sleep_forever from corporate.lib.stripe import StripeConnectionError, \ run_billing_processor_one_step -from zilencer.models import BillingProcessor +from corporate.models import BillingProcessor class Command(BaseCommand): help = """Run BillingProcessors, to sync billing-relevant updates into Stripe. diff --git a/corporate/management/commands/setup_stripe.py b/corporate/management/commands/setup_stripe.py index 9f733ff8ef..04e4823f2e 100644 --- a/corporate/management/commands/setup_stripe.py +++ b/corporate/management/commands/setup_stripe.py @@ -1,5 +1,5 @@ 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 typing import Any diff --git a/corporate/migrations/0001_initial.py b/corporate/migrations/0001_initial.py new file mode 100644 index 0000000000..5c34d7406c --- /dev/null +++ b/corporate/migrations/0001_initial.py @@ -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)), + ], + ), + ] diff --git a/corporate/migrations/__init__.py b/corporate/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/corporate/models.py b/corporate/models.py new file mode 100644 index 0000000000..6b1c40a10f --- /dev/null +++ b/corporate/models.py @@ -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 "" % (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 '' % (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 '' % (self.realm, self.log_row, self.id) diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index d08062c814..81c3810e0f 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -21,7 +21,7 @@ from corporate.lib.stripe import catch_stripe_errors, \ get_seat_count, extract_current_subscription, sign_string, unsign_string, \ get_next_billing_log_entry, run_billing_processor_one_step, \ 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 = ujson.load(fixture_data_file) diff --git a/corporate/views.py b/corporate/views.py index 329eb0fed1..cbb97a1f87 100644 --- a/corporate/views.py +++ b/corporate/views.py @@ -19,7 +19,7 @@ from corporate.lib.stripe import STRIPE_PUBLISHABLE_KEY, \ stripe_get_customer, stripe_get_upcoming_invoice, get_seat_count, \ extract_current_subscription, process_initial_upgrade, sign_string, \ 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') diff --git a/docs/subsystems/billing.md b/docs/subsystems/billing.md index 249bcc54d8..fd6fc3b9af 100644 --- a/docs/subsystems/billing.md +++ b/docs/subsystems/billing.md @@ -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. -Nearly all the billing-relevant code lives in `zilencer/`. +Nearly all the billing-relevant code lives in `corporate/`. ## General architecture diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index b2c1e8c0b5..12bb4edc9d 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -23,7 +23,7 @@ from zerver.models import ( flush_per_request_caches, DefaultStream, Realm, ) from zerver.views.home import home, sent_time_in_epoch_seconds -from zilencer.models import Customer +from corporate.models import Customer class HomeTest(ZulipTestCase): def test_home(self) -> None: diff --git a/zerver/views/home.py b/zerver/views/home.py index 4461b8889f..c3f998e936 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -250,8 +250,8 @@ def home_real(request: HttpRequest) -> HttpResponse: show_billing = False show_plans = False - if settings.ZILENCER_ENABLED: - from zilencer.models import Customer + if settings.CORPORATE_ENABLED: + from corporate.models import Customer if user_profile.is_billing_admin or user_profile.is_realm_admin: customer = Customer.objects.filter(realm=user_profile.realm).first() if customer is not None and customer.has_billing_relationship: diff --git a/zilencer/migrations/0015_delete_billing.py b/zilencer/migrations/0015_delete_billing.py new file mode 100644 index 0000000000..102245ac9d --- /dev/null +++ b/zilencer/migrations/0015_delete_billing.py @@ -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', + ), + ] diff --git a/zilencer/models.py b/zilencer/models.py index cd414a1501..884ca4d974 100644 --- a/zilencer/models.py +++ b/zilencer/models.py @@ -2,8 +2,7 @@ import datetime from django.db import models -from zerver.models import AbstractPushDeviceToken, Realm, UserProfile, \ - RealmAuditLog +from zerver.models import AbstractPushDeviceToken def get_remote_server_by_uuid(uuid: str) -> 'RemoteZulipServer': return RemoteZulipServer.objects.get(uuid=uuid) @@ -35,44 +34,3 @@ class RemotePushDeviceToken(AbstractPushDeviceToken): def __str__(self) -> str: return "" % (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 "" % (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 '' % (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 '' % (self.realm, self.log_row, self.id) diff --git a/zproject/settings.py b/zproject/settings.py index 0157e14fc7..c0c52790f4 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -575,6 +575,7 @@ if USING_PGROONGA: INSTALLED_APPS += EXTRA_INSTALLED_APPS ZILENCER_ENABLED = 'zilencer' in INSTALLED_APPS +CORPORATE_ENABLED = 'corporate' in INSTALLED_APPS # Base URL of the Tornado server # We set it to None when running backend tests or populate_db.