billing: Do not require a Stripe account to store Customer discounts.

This commit is contained in:
Rishi Gupta 2019-01-28 21:34:31 -08:00
parent 21b51ba8d1
commit 9018ef5175
23 changed files with 52 additions and 21 deletions

View File

@ -163,7 +163,7 @@ def stripe_get_customer(stripe_customer_id: str) -> stripe.Customer:
return stripe.Customer.retrieve(stripe_customer_id, expand=["default_source"])
@catch_stripe_errors
def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
def do_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
realm = user.realm
# We could do a better job of handling race conditions here, but if two
# people from a realm try to upgrade at exactly the same time, the main
@ -183,7 +183,8 @@ def do_create_customer(user: UserProfile, stripe_token: Optional[str]=None) -> C
RealmAuditLog.objects.create(
realm=user.realm, acting_user=user, event_type=RealmAuditLog.STRIPE_CARD_CHANGED,
event_time=event_time)
customer = Customer.objects.create(realm=realm, stripe_customer_id=stripe_customer.id)
customer, created = Customer.objects.update_or_create(realm=realm, defaults={
'stripe_customer_id': stripe_customer.id})
user.is_billing_admin = True
user.save(update_fields=["is_billing_admin"])
return customer
@ -221,8 +222,8 @@ def add_plan_renewal_to_license_ledger_if_needed(plan: CustomerPlan, event_time:
def update_or_create_stripe_customer(user: UserProfile, stripe_token: Optional[str]=None) -> Customer:
realm = user.realm
customer = Customer.objects.filter(realm=realm).first()
if customer is None:
return do_create_customer(user, stripe_token=stripe_token)
if customer is None or customer.stripe_customer_id is None:
return do_create_stripe_customer(user, stripe_token=stripe_token)
if stripe_token is not None:
do_replace_payment_source(user, stripe_token)
return customer
@ -442,12 +443,8 @@ def invoice_plans_as_needed(event_time: datetime) -> None:
for plan in CustomerPlan.objects.filter(next_invoice_date__lte=event_time):
invoice_plan(plan, event_time)
def attach_discount_to_realm(user: UserProfile, discount: Decimal) -> None:
customer = Customer.objects.filter(realm=user.realm).first()
if customer is None:
customer = do_create_customer(user)
customer.default_discount = discount
customer.save()
def attach_discount_to_realm(realm: Realm, discount: Decimal) -> None:
Customer.objects.update_or_create(realm=realm, defaults={'default_discount': discount})
def process_downgrade(user: UserProfile) -> None: # nocoverage
pass

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.18 on 2019-01-29 01:46
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('corporate', '0005_customerplan_invoicing'),
]
operations = [
migrations.AlterField(
model_name='customer',
name='stripe_customer_id',
field=models.CharField(max_length=255, null=True, unique=True),
),
]

View File

@ -9,7 +9,7 @@ from zerver.models import Realm, RealmAuditLog
class Customer(models.Model):
realm = models.OneToOneField(Realm, on_delete=CASCADE) # type: Realm
stripe_customer_id = models.CharField(max_length=255, unique=True) # type: str
stripe_customer_id = models.CharField(max_length=255, null=True, unique=True) # type: str
# Deprecated .. delete once everyone is migrated to new billing system
has_billing_relationship = models.BooleanField(default=False) # type: bool
# A percentage, like 85.

View File

@ -26,7 +26,7 @@ from zerver.models import Realm, UserProfile, get_realm, RealmAuditLog
from corporate.lib.stripe import catch_stripe_errors, attach_discount_to_realm, \
get_seat_count, sign_string, unsign_string, \
BillingError, StripeCardError, StripeConnectionError, stripe_get_customer, \
DEFAULT_INVOICE_DAYS_UNTIL_DUE, MIN_INVOICED_LICENSES, do_create_customer, \
DEFAULT_INVOICE_DAYS_UNTIL_DUE, MIN_INVOICED_LICENSES, do_create_stripe_customer, \
add_months, next_month, next_renewal_date, renewal_amount, \
compute_plan_parameters, update_or_create_stripe_customer, \
process_initial_upgrade, add_plan_renewal_to_license_ledger_if_needed, \
@ -747,7 +747,7 @@ class StripeTest(StripeTestCase):
# If you pay by invoice, your payment method should be
# "Billed by invoice", even if you have a card on file
# user = self.example_user("hamlet")
# do_create_customer(user, stripe_create_token().id)
# do_create_stripe_customer(user, stripe_create_token().id)
# self.login(user.email)
# self.upgrade(invoice=True)
# stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
@ -761,18 +761,32 @@ class StripeTest(StripeTestCase):
def test_attach_discount_to_realm(self, *mocks: Mock) -> None:
# Attach discount before Stripe customer exists
user = self.example_user('hamlet')
attach_discount_to_realm(user, Decimal(85))
attach_discount_to_realm(user.realm, Decimal(85))
self.login(user.email)
# Check that the discount appears in page_params
self.assert_in_success_response(['85'], self.client_get("/upgrade/"))
# Check that the customer was charged the discounted amount
# TODO
# Check upcoming invoice reflects the discount
# TODO
self.upgrade()
stripe_customer_id = Customer.objects.values_list('stripe_customer_id', flat=True).first()
self.assertEqual(1200 * self.seat_count,
[charge for charge in stripe.Charge.list(customer=stripe_customer_id)][0].amount)
stripe_invoice = [invoice for invoice in stripe.Invoice.list(customer=stripe_customer_id)][0]
self.assertEqual([1200 * self.seat_count, -1200 * self.seat_count],
[item.amount for item in stripe_invoice.lines])
# Check CustomerPlan reflects the discount
plan = CustomerPlan.objects.get(price_per_license=1200, discount=Decimal(85))
# Attach discount to existing Stripe customer
attach_discount_to_realm(user, Decimal(25))
# Check upcoming invoice reflects the new discount
# TODO
plan.status = CustomerPlan.ENDED
plan.save(update_fields=['status'])
attach_discount_to_realm(user.realm, Decimal(25))
process_initial_upgrade(user, self.seat_count, True, CustomerPlan.ANNUAL, stripe_create_token().id)
self.assertEqual(6000 * self.seat_count,
[charge for charge in stripe.Charge.list(customer=stripe_customer_id)][0].amount)
stripe_invoice = [invoice for invoice in stripe.Invoice.list(customer=stripe_customer_id)][0]
self.assertEqual([6000 * self.seat_count, -6000 * self.seat_count],
[item.amount for item in stripe_invoice.lines])
plan = CustomerPlan.objects.get(price_per_license=6000, discount=Decimal(25))
@mock_stripe()
def test_replace_payment_source(self, *mocks: Mock) -> None:
@ -923,7 +937,7 @@ class BillingHelpersTest(ZulipTestCase):
def test_update_or_create_stripe_customer_logic(self) -> None:
user = self.example_user('hamlet')
# No existing Customer object
with patch('corporate.lib.stripe.do_create_customer', return_value='returned') as mocked1:
with patch('corporate.lib.stripe.do_create_stripe_customer', return_value='returned') as mocked1:
returned = update_or_create_stripe_customer(user, stripe_token='token')
mocked1.assert_called()
self.assertEqual(returned, 'returned')