mirror of
https://github.com/zulip/zulip.git
synced 2026-07-09 21:21:47 +08:00
billing: Remove process_downgrade.
This commit is contained in:
parent
84a31ca800
commit
8ec91fc42d
@ -103,29 +103,6 @@ def stripe_get_customer(stripe_customer_id: str) -> stripe.Customer:
|
||||
def stripe_get_upcoming_invoice(stripe_customer_id: str) -> stripe.Invoice:
|
||||
return stripe.Invoice.upcoming(customer=stripe_customer_id)
|
||||
|
||||
@catch_stripe_errors
|
||||
def stripe_get_invoice_preview_for_downgrade(
|
||||
stripe_customer_id: str, stripe_subscription_id: str,
|
||||
stripe_subscriptionitem_id: str) -> stripe.Invoice:
|
||||
return stripe.Invoice.upcoming(
|
||||
customer=stripe_customer_id, subscription=stripe_subscription_id,
|
||||
subscription_items=[{'id': stripe_subscriptionitem_id, 'quantity': 0}])
|
||||
|
||||
def preview_invoice_total_for_downgrade(stripe_customer: stripe.Customer) -> int:
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
if stripe_subscription is None:
|
||||
# Most likely situation is: user A goes to billing page, user B
|
||||
# cancels subscription, user A clicks on "downgrade" or something
|
||||
# else that calls this function.
|
||||
billing_logger.error("Trying to extract subscription item that doesn't exist, for Stripe customer %s"
|
||||
% (stripe_customer.id,))
|
||||
raise BillingError('downgrade without subscription', BillingError.TRY_RELOADING)
|
||||
for item in stripe_subscription['items']:
|
||||
# There should only be one item, but we can't index into stripe_subscription['items']
|
||||
stripe_subscriptionitem_id = item.id
|
||||
return stripe_get_invoice_preview_for_downgrade(
|
||||
stripe_customer.id, stripe_subscription.id, stripe_subscriptionitem_id).total
|
||||
|
||||
# This allows us to access /billing in tests without having to mock the
|
||||
# whole invoice object
|
||||
def upcoming_invoice_total(stripe_customer_id: str) -> int:
|
||||
@ -289,34 +266,5 @@ def attach_discount_to_realm(user: UserProfile, percent_off: int) -> None:
|
||||
else:
|
||||
do_replace_coupon(user, coupon)
|
||||
|
||||
@catch_stripe_errors
|
||||
def process_downgrade(user: UserProfile) -> None:
|
||||
# This is no longer implementing our intended policy. Until that is
|
||||
# resolved we should use the Stripe dashboard to downgrade.
|
||||
assert(settings.TEST_SUITE)
|
||||
stripe_customer = stripe_get_customer(
|
||||
Customer.objects.filter(realm=user.realm).first().stripe_customer_id)
|
||||
subscription_balance = preview_invoice_total_for_downgrade(stripe_customer)
|
||||
# If subscription_balance > 0, they owe us money. This is likely due to
|
||||
# people they added in the last day, so we can just forgive it.
|
||||
# Stripe automatically forgives it when we delete the subscription, so nothing we need to do there.
|
||||
if subscription_balance < 0:
|
||||
stripe_customer.account_balance = stripe_customer.account_balance + subscription_balance
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
# Wish these two could be transaction.atomic
|
||||
stripe_subscription = stripe.Subscription.delete(stripe_subscription)
|
||||
stripe.Customer.save(stripe_customer)
|
||||
with transaction.atomic():
|
||||
user.realm.has_seat_based_plan = False
|
||||
user.realm.save(update_fields=['has_seat_based_plan'])
|
||||
RealmAuditLog.objects.create(
|
||||
realm=user.realm,
|
||||
acting_user=user,
|
||||
event_type=RealmAuditLog.STRIPE_PLAN_CHANGED,
|
||||
event_time=timestamp_to_datetime(stripe_subscription.canceled_at),
|
||||
extra_data=ujson.dumps({'plan': None, 'quantity': stripe_subscription.quantity}))
|
||||
# Doing this last, since it results in user-visible confirmation (via
|
||||
# product changes) that the downgrade succeeded.
|
||||
# Keeping it out of the transaction.atomic block because it will
|
||||
# eventually have a lot of stuff going on.
|
||||
do_change_plan_type(user.realm, Realm.LIMITED)
|
||||
def process_downgrade(user: UserProfile) -> None: # nocoverage
|
||||
pass
|
||||
|
||||
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -27,8 +27,7 @@ from corporate.lib.stripe import catch_stripe_errors, \
|
||||
do_subscribe_customer_to_plan, attach_discount_to_realm, \
|
||||
get_seat_count, extract_current_subscription, sign_string, unsign_string, \
|
||||
BillingError, StripeCardError, StripeConnectionError, stripe_get_customer, \
|
||||
DEFAULT_INVOICE_DAYS_UNTIL_DUE, MIN_INVOICED_SEAT_COUNT, do_create_customer, \
|
||||
process_downgrade
|
||||
DEFAULT_INVOICE_DAYS_UNTIL_DUE, MIN_INVOICED_SEAT_COUNT, do_create_customer
|
||||
from corporate.models import Customer, Plan, Coupon
|
||||
from corporate.views import payment_method_string
|
||||
import corporate.urls
|
||||
@ -625,27 +624,11 @@ class StripeTest(ZulipTestCase):
|
||||
|
||||
# If you signup with a card and then downgrade, we still have your
|
||||
# card on file, and should show it
|
||||
realm = do_create_realm('realm1', 'realm1')
|
||||
user = do_create_user('name@realm1.com', 'password', realm, 'name', 'name')
|
||||
self.login(user.email, password='password', realm=realm)
|
||||
self.upgrade(realm=realm)
|
||||
with patch('corporate.lib.stripe.preview_invoice_total_for_downgrade', return_value=1):
|
||||
process_downgrade(user)
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
self.assertEqual('Card ending in 4242', payment_method_string(stripe_customer))
|
||||
# TODO
|
||||
|
||||
# If you signup via invoice, and then downgrade immediately, the
|
||||
# default_source is in a weird intermediate state.
|
||||
realm = do_create_realm('realm2', 'realm2')
|
||||
user = do_create_user('name@realm2.com', 'password', realm, 'name', 'name')
|
||||
self.login(user.email, password='password', realm=realm)
|
||||
self.upgrade(invoice=True, realm=realm)
|
||||
with patch('corporate.lib.stripe.preview_invoice_total_for_downgrade', return_value=1):
|
||||
process_downgrade(user)
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
# Could be either one, depending on exact timing with the test
|
||||
self.assertTrue('Unknown payment method' in payment_method_string(stripe_customer) or
|
||||
'No payment method' in payment_method_string(stripe_customer))
|
||||
# TODO
|
||||
|
||||
@mock_stripe()
|
||||
def test_attach_discount_to_realm(self, *mocks: Mock) -> None:
|
||||
@ -673,86 +656,6 @@ class StripeTest(ZulipTestCase):
|
||||
upcoming_invoice = stripe.Invoice.upcoming(customer=stripe_customer.id)
|
||||
self.assertEqual(upcoming_invoice.amount_due, get_seat_count(user.realm) * 80 * 75)
|
||||
|
||||
# Tests upgrade followed by immediate downgrade. Doesn't test the
|
||||
# calculations for how much credit they should get if they had the
|
||||
# subscription for more than 0 time.
|
||||
@mock_stripe(tested_timestamp_fields=["canceled_at"])
|
||||
def test_downgrade(self, *mocks: Mock) -> None:
|
||||
user = self.example_user('iago')
|
||||
self.login(user.email)
|
||||
self.upgrade()
|
||||
realm = get_realm('zulip')
|
||||
self.assertEqual(realm.has_seat_based_plan, True)
|
||||
self.assertEqual(realm.plan_type, Realm.STANDARD)
|
||||
RealmAuditLog.objects.filter(realm=realm).delete()
|
||||
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
self.assertEqual(stripe_customer.account_balance, 0)
|
||||
# Change subscription in Stripe, but don't pay for it
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
stripe_subscription.quantity = 123
|
||||
stripe.Subscription.save(stripe_subscription)
|
||||
|
||||
response = self.client_post("/json/billing/downgrade", {})
|
||||
self.assert_json_success(response)
|
||||
stripe_customer = stripe_get_customer(stripe_customer.id)
|
||||
self.assertEqual(stripe_customer.account_balance, self.seat_count * -8000)
|
||||
self.assertIsNone(extract_current_subscription(stripe_customer))
|
||||
stripe_subscription = stripe.Subscription.retrieve(stripe_subscription.id)
|
||||
self.assertEqual(stripe_subscription.status, "canceled")
|
||||
|
||||
realm = get_realm('zulip')
|
||||
self.assertEqual(realm.plan_type, Realm.LIMITED)
|
||||
self.assertFalse(realm.has_seat_based_plan)
|
||||
|
||||
audit_log_entries = list(RealmAuditLog.objects.filter(realm=realm)
|
||||
.values_list('event_type', 'event_time',
|
||||
'requires_billing_update').order_by('id'))
|
||||
self.assertEqual(audit_log_entries, [
|
||||
(RealmAuditLog.STRIPE_PLAN_CHANGED,
|
||||
timestamp_to_datetime(stripe_subscription.canceled_at), False),
|
||||
(RealmAuditLog.REALM_PLAN_TYPE_CHANGED, Kandra(), False),
|
||||
])
|
||||
self.assertEqual(ujson.loads(RealmAuditLog.objects.filter(
|
||||
event_type=RealmAuditLog.STRIPE_PLAN_CHANGED).values_list('extra_data', flat=True).first()),
|
||||
{'plan': None, 'quantity': 123})
|
||||
|
||||
@mock_stripe()
|
||||
def test_downgrade_with_no_subscription(self, *mocks: Mock) -> None:
|
||||
user = self.example_user("iago")
|
||||
do_create_customer(user)
|
||||
self.login(user.email)
|
||||
with patch("stripe.Customer.save") as mock_save_customer:
|
||||
with patch("corporate.lib.stripe.billing_logger.error"):
|
||||
response = self.client_post("/json/billing/downgrade", {})
|
||||
mock_save_customer.assert_not_called()
|
||||
self.assert_json_error_contains(response, 'Please reload')
|
||||
self.assertEqual(ujson.loads(response.content)['error_description'], 'downgrade without subscription')
|
||||
|
||||
@mock_stripe()
|
||||
def test_downgrade_with_money_owed(self, *mocks: Mock) -> None:
|
||||
user = self.example_user('iago')
|
||||
self.login(user.email)
|
||||
self.upgrade()
|
||||
stripe_customer = stripe_get_customer(Customer.objects.get(realm=user.realm).stripe_customer_id)
|
||||
self.assertEqual(stripe_customer.account_balance, 0)
|
||||
stripe_subscription = extract_current_subscription(stripe_customer)
|
||||
# Create a situation where customer net owes us money
|
||||
stripe.InvoiceItem.create(
|
||||
amount=100000,
|
||||
currency='usd',
|
||||
customer=stripe_customer,
|
||||
subscription=stripe_subscription)
|
||||
|
||||
response = self.client_post("/json/billing/downgrade", {})
|
||||
self.assert_json_success(response)
|
||||
stripe_customer = stripe_get_customer(stripe_customer.id)
|
||||
# Check that positive balance was forgiven
|
||||
self.assertEqual(stripe_customer.account_balance, 0)
|
||||
self.assertIsNone(extract_current_subscription(stripe_customer))
|
||||
stripe_subscription = stripe.Subscription.retrieve(stripe_subscription.id)
|
||||
self.assertEqual(stripe_subscription.status, "canceled")
|
||||
|
||||
@mock_stripe()
|
||||
def test_replace_payment_source(self, *mocks: Mock) -> None:
|
||||
user = self.example_user("hamlet")
|
||||
@ -856,6 +759,7 @@ class RequiresBillingAccessTest(ZulipTestCase):
|
||||
params = [
|
||||
("/json/billing/sources/change", "do_replace_payment_source",
|
||||
{'stripe_token': ujson.dumps('token')}),
|
||||
# TODO: second argument should be something like "process_downgrade"
|
||||
("/json/billing/downgrade", "process_downgrade", {}),
|
||||
] # type: List[Tuple[str, str, Dict[str, Any]]]
|
||||
|
||||
|
||||
@ -194,7 +194,7 @@ def billing_home(request: HttpRequest) -> HttpResponse:
|
||||
return render(request, 'corporate/billing.html', context=context)
|
||||
|
||||
@require_billing_access
|
||||
def downgrade(request: HttpRequest, user: UserProfile) -> HttpResponse:
|
||||
def downgrade(request: HttpRequest, user: UserProfile) -> HttpResponse: # nocoverage
|
||||
try:
|
||||
process_downgrade(user)
|
||||
except BillingError as e:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user