mirror of https://github.com/zulip/zulip.git
billing: Do not require a Stripe account to store Customer discounts.
This commit is contained in:
parent
21b51ba8d1
commit
9018ef5175
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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.
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue