Commit Graph

293 Commits

Author SHA1 Message Date
Aman Agrawal 775a90771e stripe: Modify error message for less than min licenses. 2023-11-13 10:35:39 -08:00
Prakhar Pratyush b5a6742adc stripe: Move `process_initial_upgrade` to BillingSession.
Moves the 'process_initial_upgrade' function to the
'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-11-13 08:11:27 -08:00
Lauryn Menard 928cb7e09e corporate: Pass plan_tier parameter to process_initial_upgrade.
Updates `process_initial_upgrade` to take a plan_tier parameter,
so that information can be specific to the type of BillingSession.

Note that ideally the plan tier would be passed as metadata to the
stripe.checkout.Session, but in order to do so, we need to be able
to update the generated stripe fixtures for tests. So for now, we
set the plan tier directly in the stripe event handler code.
2023-11-10 11:26:18 -08:00
Lauryn Menard 3e550364c6 corporate: Use plan.name for stripe.Invoice statement descriptor.
Instead of hard coding the string for the stripe.InvoiceItem and
stripe.Invoice statement_descriptor, we use the name of the plan
that was just created.
2023-11-10 11:26:18 -08:00
Lauryn Menard d29c454e80 corporate: Make BillingSession abstract method for changing plan type.
Creates `do_change_plan_type` as a BillingSession abstract method
as an alias for updating the plan_type for a realm (and eventually
remote server).

This removes the last direct reference to a customer's realm in
`process_initial_upgrade`.
2023-11-10 11:26:18 -08:00
Lauryn Menard 26ad2e29c6 corporate: Make BillingSession alias for latest seat count.
Creates `current_count_for_billed_licenses` as an abstract method
in BillingSession class, to get the latest seat count for the realm
(and eventually remote server) connected to the customer.
2023-11-10 11:26:18 -08:00
Prakhar Pratyush 31f048d054 stripe: Move `setup_upgrade_checkout_session_and...` to BillingSession.
Moves the 'setup_upgrade_checkout_session_and_payment_intent'
function to the 'BillingSession' abstract class.

This refactoring will help in minimizing duplicate code while
supporting both realm and remote_server customers.
2023-11-10 09:15:10 -08:00
Lauryn Menard 717d40475e corporate: Use the stripe checkout session metadata for free trials.
Instead of using the data from the stripe.SetupIntent for processing
an initial upgrade for the free trial session types, use the metadata
from the stripe.checkout.Session that was returned.

The metadata is the same for both the checkout session and the setup
intent.
2023-11-09 12:03:33 -08:00
Lauryn Menard 1f28837bd7 corporate: Update create_stripe_customer to not attach payment method.
According to stripe's documentation for attaching payment methods to
customers (see https://stripe.com/docs/api/payment_methods/attach),
payment methods should be attached to customers through a SetupIntent
or PaymentIntent, which is what we do as we process new customers
and accounts.

Updates create_stripe_customer so that it is clear that the payment
method should not be added when we directly create a new  stripe
customer.
2023-11-09 12:03:33 -08:00
Lauryn Menard 4dd6f105a0 coporate: Change statement descriptor for Zulip Cloud Credit.
There is a 22 char limit on the stripe.Invoice.statement_descriptor
field. The current value for that field in this error case, "Zulip
Cloud Standard Credit" is 27 chars.

Updates the value for this error case to be "Cloud Standard Credit".
2023-11-09 12:03:33 -08:00
Lauryn Menard 6f7b75c9a3 corporate: Add `create_stripe_payment_intent` to BillingSession class.
Adds `create_stripe_payment_intent` to the BillingSession abstract
class for the initial upgrade process, which is used only in
`setup_upgrade_checkout_session_and_payment_intent`.

Adds a helper dataclass for the data used to create the stripe
payment intent, StripePaymentIntentData, for the implementation of
more than one child class of BillingSession.

Also adds two abstract helper functions for getting the above stripe
payment intent data as well as updating that data for a stripe
checkout session that is associated with a payment intent:
update_data_for_checkout_session_and_payment_intent,
get_data_for_stripe_payment_intent.
2023-11-06 14:39:36 -08:00
Lauryn Menard 600fea23bb corporate: Add create_stripe_checkout_session to BillingSession class.
Adds `create_stripe_checkout_session` to BillingSession abstract
class, which is then used in all places where a stripe.checkout.Session
and Session were created: start_retry_payment_intent_session,
start_card_update_stripe_session, and
setup_upgrade_checkout_session_and_payment_intent.

Adds a `billing_session_url`  abstract property to the BillingSession
abstract class and for the RealmBillingSession child class sets that
value to `self.realm.uri`.
2023-11-06 14:39:36 -08:00
Lauryn Menard ec2b00e8c4 corporate: Move `update_billing_method...` to BillingSession class.
Moves `update_billing_method_of_current_plan` to the BillingSession
abstract class.

Adds a helper function for support views for the realm case:
`update_realm_billing_method`.
2023-11-04 17:20:49 -07:00
Lauryn Menard 5d07666362 corporate: Move `update_sponsorship_status` to BillingSession class.
Moves `update_sponsorship_status` to BillingSession abstract class
as `update_customer_sponsorship_status`.

Updates the support views to have a helper for updating this on a
realm: `update_realm_sponsorship_status`.
2023-11-04 17:20:49 -07:00
Lauryn Menard d06062c179 corporate: Move `approve_sponsorship` to BillingSession class.
Makes `approve_sponshorship` an abstract method in BillingSession
abstract base class and moves the implementation for realms to the
RealmBillingSession child class.

Adds `approve_realm_sponsorship` helper function that's used in
the support view and initiates the billing session.
2023-11-04 17:20:49 -07:00
Lauryn Menard c8021925c8 corporate: Create AuditLogEventType enum for BillingSession audit logs.
Creates an enum class, AuditLogEventType, and an abstract method in
BillingSession, get_audit_log_event, so that we have an abstraction
for getting the audit log event type since it might be different for
Customer objects with a realm vs a remote_server.
2023-11-04 17:20:49 -07:00
Lauryn Menard ee19a9c274 corporate: Move `attach_realm_discount` to BillingSession class.
This moves the logic for `attach_realm_discount`, which is used in
the support view, to be in the BillingSession class.

Updates the function name to be `attach_discount_to_customer` so
that the context is generalized vs realm specific.

Updates RealmBillingSession implementation to account for actions
that are initiated by a support admin user.

Also moves the helper function `get_discount_for_realm` that is
only used in support views to `corporate/lib/support.py`.
2023-11-04 17:20:49 -07:00
Lauryn Menard 63abf063b7 corporate: Use StripeCustomerData dataclass to create stripe Customer.
So that all child classes of BillingSession generate the same data
structure for customers that are created in Stripe, revise
`get_data_for_stripe_customer` to return a specific dataclass:
StripeCustomerData.
2023-11-04 17:20:49 -07:00
Lauryn Menard ce6f1f8c18 stripe: Create BillingSession class for different customer types.
So that `update_or_create_stripe_customer` can work for Customer
objects with either a realm or remote_server, we create an abstract
base class, BillingSession, and implement a child class for the
current implementation of Customer objects with a realm.

Refactoring `update_or_create_stripe_customer` also moves
`create_stripe_customer` and `replace_payment_method` to the
BillingSession class.
2023-11-04 17:20:49 -07:00
Lauryn Menard d1e2e2d857 corporate: Check for no CustomerPlan on Customer instead of Realm.
In ensure_customer_does_not_have_active_plan, we were already going
through the Customer table to get/check for an active CustomerPlan.

Now we directly get/check for an active CustomerPlan with via the
Customer, which allows for reusing this function for Customer
objects without a Realm set.
2023-10-30 16:09:52 -07:00
Lauryn Menard 37b70a8e24 corporate: Exclude non-realm Customer objects in downgrade small realms.
Updates query in downgrade_small_realms_behind_on_payments_as_needed
to exclude Customer objects with realm=None.
2023-10-30 10:09:17 -07:00
Lauryn Menard c996bb3c19 corporate: Move functions for realm installation data to new file.
Moves two functions in corporate/lib/stripe.py that are used to
get data for the main installation activity analytics page to a
separate file: corporate/lib/analytics.py.

Also, updates these functions for the possibility of realm being
None for a Customer object.
2023-10-30 10:08:45 -07:00
Mateusz Mandera 4fc2b54eed stripe: Set .api_version to make integration work again.
Upgrading stripe to 6.0.0 in e32366638a
breaks our Stripe integration due to API version change making us fail
to finalize creating an invoice and charge the customer.

For the upstream details see:
60ab6ac7d7/CHANGELOG.md (600---2023-08-16)

6.0.0 uses 2023-08-16 Stripe API version unless specified otherwise. We
want to use 2020-08-27.

Setting stripe.api_version in corporate/lib/stripe.py is sufficient for
it to be set everywhere else. This is supported by the fact that we also
only set stripe.api_key in that file.
2023-10-10 16:44:28 -07:00
Mateusz Mandera 07e99eace9 billing: Fix license counting in exempt_from_license_number_check case.
Fixes two bugs involving organization with
exempt_from_license_number_check enabled:
1. If the organization had e.g. 100 users and upgraded their plan,
   specifying 50 licenses, the generated LicenseLedger and thus the
   corresponding invoice was still for 100 users.
2. The organization was unable to use the billing/plan endpoint (update
   plan endpoint) to make their number of licenses less than the current
   number of users.

Organizations with exempt_from_license_number_check are supposed to be
able to declare whatever license number they want, as this attribute
allows having pricing schemes where an organization only pays us for a
subset of their users.
2023-09-26 09:59:15 -07:00
Zixuan James Li 30495cec58 migration: Rename extra_data_json to extra_data in audit log models.
This migration applies under the assumption that extra_data_json has
been populated for all existing and coming audit log entries.

- This removes the manual conversions back and forth for extra_data
throughout the codebase including the orjson.loads(), orjson.dumps(),
and str() calls.

- The custom handler used for converting Decimal is removed since
DjangoJSONEncoder handles that for extra_data.

- We remove None-checks for extra_data because it is now no longer
nullable.

- Meanwhile, we want the bouncer to support processing RealmAuditLog entries for
remote servers before and after the JSONField migration on extra_data.

- Since now extra_data should always be a dict for the newer remote
server, which is now migrated, the test cases are updated to create
RealmAuditLog objects by passing a dict for extra_data before
sending over the analytics data. Note that while JSONField allows for
non-dict values, a proper remote server always passes a dict for
extra_data.

- We still test out the legacy extra_data format because not all
remote servers have migrated to use JSONField extra_data.
This verifies that support for extra_data being a string or None has not
been dropped.

Co-authored-by: Siddharth Asthana <siddharthasthana31@gmail.com>
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2023-08-16 17:18:14 -07:00
Anders Kaseorg 143baa4243 python: Convert translated positional {} fields to {named} fields.
Translators benefit from the extra information in the field names, and
need the reordering freedom that isn’t available with multiple
positional fields.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-07-18 15:19:07 -07:00
Zixuan Li e39e04c3ce
migration: Add `extra_data_json` for audit log models.
Note that we use the DjangoJSONEncoder so that we have builtin support
for parsing Decimal and datetime.

During this intermediate state, the migration that creates
extra_data_json field has been run. We prepare for running the backfilling
migration that populates extra_data_json from extra_data.

This change implements double-write, which is important to keep the
state of extra data consistent. For most extra_data usage, this is
handled by the overriden `save` method on `AbstractRealmAuditLog`, where
we either generates extra_data_json using orjson.loads or
ast.literal_eval.

While backfilling ensures that old realm audit log entries have
extra_data_json populated, double-write ensures that any new entries
generated will also have extra_data_json set. So that we can then safely
rename extra_data_json to extra_data while ensuring the non-nullable
invariant.

For completeness, we additionally set RealmAuditLog.NEW_VALUE for
the USER_FULL_NAME_CHANGED event. This cannot be handled with the
overridden `save`.

This addresses: https://github.com/zulip/zulip/pull/23116#discussion_r1040277795

Note that extra_data_json at this point is not used yet. So the test
cases do not need to switch to testing extra_data_json. This is later
done after we rename extra_data_json to extra_data.

Double-write for the remote server audit logs is special, because we only
get the dumped bytes from an external source. Luckily, none of the
payload carries extra_data that is not generated using orjson.dumps for
audit logs of event types in SYNC_BILLING_EVENTS. This can be verified
by looking at:

`git grep -A 6 -E "event_type=.*(USER_CREATED|USER_ACTIVATED|USER_DEACTIVATED|USER_REACTIVATED|USER_ROLE_CHANGED|REALM_DEACTIVATED|REALM_REACTIVATED)"`

Therefore, we just need to populate extra_data_json doing an
orjson.loads call after a None-check.

Co-authored-by: Zixuan James Li <p359101898@gmail.com>
2023-06-07 12:14:43 -07:00
Anders Kaseorg 13545ff885 stripe: Fix invoice_plans_as_needed default value.
Python evaluates default values once when the function is defined, not
when it is called.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-04-27 16:43:33 -07:00
Mateusz Mandera ef42065cec billing: Allow exempt_from_license_number_check any number of licenses.
exempt_from_license_number_check was initially added allowing
organizations with it enabled to invite new users above their number of
licenses.

However, an organization with this permission enabled,
cannot upgrade if they weren't on a plan already - because when choosing
Manual license management, you cannot enter a number of licenses lower
than the current seat count. However, an organization like that probably
already has some users that they get free of charge - and thus they need
to be able to enter a lower number of licenses in order to upgrade.
2023-04-13 15:26:44 -07:00
Mateusz Mandera be208f73f7 billing: Fix exempt_from_from_license_number_check column name.
exempt_from_from_license_number_check was clearly not intended.
2023-04-13 15:26:44 -07:00
Anders Kaseorg 2976d2c2fa stripe: Fix non-translatable computed string.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-03-27 17:14:46 -07:00
Mateusz Mandera 89a72c92ea stripe: Change realm.plan_type when upgrading a realm to Plus plan. 2023-02-08 10:28:13 -08:00
Anders Kaseorg 59eca10a43 ruff: Fix G004 Logging statement uses f-string.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-04 16:36:20 -08:00
Anders Kaseorg da3cf5ea7a ruff: Fix RSE102 Unnecessary parentheses on raised exception.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-02-04 16:34:55 -08:00
Lauryn Menard 6b2d531dfd notification-bot: Update message for approved full sponsorship.
Updates the message sent by the notification bot when an
organization is approved for full sponsorship on Zulip
Cloud Standard to include a request to list and link to
Zulip on any acknowledgement or sponsorship pages.
2023-01-27 12:40:22 -08:00
Anders Kaseorg ff1971f5ad ruff: Fix SIM105 Use `contextlib.suppress` instead of try-except-pass.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-01-23 11:18:36 -08:00
Anders Kaseorg 73c4da7974 ruff: Fix N818 exception name should be named with an Error suffix.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-11-17 16:52:00 -08:00
Lauryn Menard a3f6220fe4 analytics: Add summary statistic for guest users in realm.
Adds the count of users with the role of guest to the stats view
`page_params` via a database query. This information is then added
to the summary statistics section of the analytics page after being
formatted by `stats.js`.

Creates Bassanio as a guest user in the database for the analytics
realm.

Fixes #20162.
2022-10-17 11:53:59 -07:00
Mateusz Mandera 2c693f3bd9 billing: Fix licenses amount check during user signup/invitation.
Our seat count calculation is different for guest user than normal users
(a number of initial guests are free, and additional marginal guests are
worth 1/5 of a seat) - so these checks we apply when a user is being
invited or signing up need to know whether it's a guest or non-guest
being added.
2022-08-18 11:56:54 -07:00
Mateusz Mandera a41cecb695 billing: Extract get_seat_count function.
This is a simple generalization of get_latest_seat_count and is useful
for calculating "what will be the realm's license count if this
number of (guest) users is added?" without duplicating any of the math
logic. Will be used in the next commits.
2022-08-18 11:56:54 -07:00
Mateusz Mandera 6582a002e7 billing: Add comment about the calculation in get_latest_seat_count.
Our billing FAQ says:
"For an organization with N other users, 5*N guest users are included at
no extra charge. After that, you will be charged at 1/5 of your regular
per-user pricing for each additional guest.".

It wasn't quite intuitive to me that
max(non_guests, math.ceil(guests / 5)) achieves that pricing, so it's
worth mentioning in a comment that it does and that that's why that
formula is used.
2022-08-18 11:56:54 -07:00
Zixuan James Li ad17096c9c realm_audit_log: Explicitly stringify dict before insertion.
`extra_data` as a `TextField` expects a `str`, but we had been passing
`dict` instead. This is a temporary solution before #18391 to fix the
type annotation.

Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-07-26 09:48:33 -07:00
Zixuan James Li 6277f09296 corporate: Fix type annotations for payment failed handler.
When being called, the wrapped function is passed `PaymentIntent`
(the `content_object` of `Event`). With that, since `customer` can be
`None`, an assertion is also required.

Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-07-15 14:56:22 -07:00
Zixuan James Li fd9a0f4274 typing: Apply trivial none-checks with assertions as necessary.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-06-23 19:25:48 -07:00
Zixuan James Li 4cf3ba5744 typing: Fix typical typing typos.
Signed-off-by: Zixuan James Li <p359101898@gmail.com>
2022-06-23 19:25:48 -07:00
Zixuan James Li c572d9be5a typing: Add none-checks for db queries.
Signed-off-by: Zixuan James Li <359101898@qq.com>
2022-05-31 09:43:55 -07:00
Zixuan James Li 4a5043dd6e typing: Add none-checks for miscellaneous cases.
Signed-off-by: Zixuan James Li <359101898@qq.com>
2022-05-31 09:43:55 -07:00
Anders Kaseorg 59f6b090c7 actions: Split out zerver.actions.realm_settings.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-04-14 17:14:37 -07:00
Anders Kaseorg cbad5739ab actions: Split out zerver.actions.create_user.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-04-14 17:14:35 -07:00
Anders Kaseorg 975066e3f0 actions: Split out zerver.actions.message_send.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-04-14 17:14:34 -07:00
Anders Kaseorg d7981dad62 actions: Split out zerver.actions.users.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-04-14 17:14:34 -07:00
Zixuan James Li b1fbba0577 stripe: Strengthen decorator types using ParamSpec.
Signed-off-by: Zixuan James Li <359101898@qq.com>
2022-04-14 12:44:35 -07:00
Aman Agrawal 7614f2203a pricing: Replace "Zulip Standard" with "Zulip Cloud Standard".
Case sensitive replace.
2022-02-09 11:00:24 -08:00
Anders Kaseorg b0ce4f1bce docs: Fix many spelling mistakes.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2022-02-07 18:51:06 -08:00
Eeshan Garg 0b5324f345 corporate: Add helper for deactivating remote server registrations. 2022-01-03 14:02:48 -08:00
Eeshan Garg 79e9ba13e2 billing: Add do_change_remote_server_plan_type.
This is a part of the plumbing we need to support billing for
self-hosted customers.

With documentation changes from tabbott.
2021-12-07 10:25:37 -08:00
Eeshan Garg 2cdaae681d actions: Rename do_change_plan_type -> do change_realm_plan_type.
We will soon be adding an equivalent function for RemoteZulipServer,
so it makes sense to rename this function to be more descriptive.
2021-12-06 16:18:53 -08:00
Eeshan Garg 7c9f587563 billing: Stop using UserProfile.delivery_email in Stripe handler.
Now that we pass in the UserProfile.id in the metadata to Stripe's
PaymentIntent objects, we no longer need to retrieve the user via
delivery_email. It makes more sense to just fetch the user by ID
and then get the latest delivery_email directly.

Note that an update to Stripe's fixtures is not necessary here
since a previous commit already modified the metadata passed to
both stripe.Session/PaymentIntent objects.
2021-12-03 14:57:30 -08:00
Eeshan Garg 88fd399bec billing: Use UserProfile.id to ascertain customer's email.
Previously, our Stripe webhook event handler code retrieved the
user's email from Stripe using the stripe.Customer.email attribute.
This led to situations such that whenever the email that Stripe had
did not correspond to a UserProfile in Zulip, the payment flow
failed since we couldn't find a UserProfile associated with the
given email.

Now, we pass in the UserProfile.id in the metadata to Stripe's
checkout Session object, so that we can fetch the correct email
in future Stripe requests.
2021-12-03 14:57:30 -08:00
Eeshan Garg a3095331eb corporate/models: Modify Customer to accommodate self-hosted customers.
This is a part of our efforts to introduce billing for our on-premise
customers.
2021-11-30 15:20:33 -08:00
Vishnu KS 6c06858e02 billing: Migrate to Stripe hosted checkout page. 2021-11-05 17:23:10 -07:00
Vishnu KS 1a1b9b28ff corporate: Store the Stripe API version. 2021-11-05 17:23:10 -07:00
Eeshan Garg b325a4f1be realm: Rename plan type constants to be more descriptive.
It is confusing to have the plan type constants not be namespaced
by the thing they represent. We already have a namespacing
convention in place for constants, so we should use it for
Realm.plan_type as well.
2021-10-19 12:20:39 -07:00
Vishnu KS fcab2ea5f7 billing: Add command for switching plans from Standard to Plus. 2021-10-15 17:27:50 -07:00
Vishnu KS 87c1b9e3bc billing: Simplify start_of_next_billing_cycle function. 2021-10-15 17:27:50 -07:00
Vishnu KS a86ab18a7b billing: Use plan.name in invoice description instead of Zulip Standard. 2021-10-15 17:27:50 -07:00
Vishnu KS 194258a721 billing: Make compute_plan_paramaters support Plus as a tier.
compute_plan_parameters assumed that the value of
tier is always CustomerPlan.STANDARD. We change that behavior
to make the function handle CustomerPlan.PLUS as well.
2021-10-15 17:27:50 -07:00
Vishnu KS 641402856d billing: Make compute_plan_paramaters take tier as arg.
This is a prep commit to add support for switching the plan
type from Standard to Plus.
2021-10-15 17:27:50 -07:00
Eeshan Garg 763b3c27d6 corporate: Add contact support page. 2021-10-01 17:30:01 -07:00
PIG208 7386918539 typing: Use accurate type hints for dictionaries.
This fixes the mypy errors related to dictionaries with django-stubs.
2021-08-20 06:02:28 -07:00
Mateusz Mandera ce4eb6f203 bots: Pass realm to get_system_bot calls in registration.py. 2021-07-26 15:33:13 -07:00
Mateusz Mandera a3cd3d6865 bots: Pass realm to get_system_bot calls in stripe. 2021-07-26 15:33:13 -07:00
PIG208 7d1c475f69 typing: Use assertions for function arguments.
Utilize the assert_is_not_None helper to eliminate errors of
'Argument x to "Foo" has incompatible type "Optional[Bar]"...'
2021-07-26 14:48:45 -07:00
PIG208 66b1a4e7ca backend: Add None-checks with assertions and if-elses.
This fixes a batch of mypy errors of the following format:
'Item "None" of "Optional[Something]" has no attribute "abc"'
2021-07-24 15:00:21 -07:00
Vishnu KS 158cec84ec stripe: Upgrade stripe API to 2020-08-27 version.
This upgrades the Stripe API to the most recent version. Going through
the Git history, it looks like our current API version is at 2019-03-14.

The API version should be manually changed in Stripe dashboard at the same
time as the commit is deployed in production.

Backward incompatible changes that are relevant to our codebase between
(2019-03-14, 2020-08-27].
* 2020-08-27 - The `sources` property on Customers is no longer included by
  default.
* 2020-03-02 - Nothing applicable
* 2019-12-03 - The `id` field of all invoice line items have changed and are
  now prefixed
  with `il_`. We only rely on this while we normalize the fixtures.
* 2019-11-05 - Nothing applicable
* 2019-10-17 - The `billing` attribute on invoices, subscriptions, and
  subscription schedules is renamed to`collection_method`. The invoice
  change is the one that is relevant to us.
* The customer object’s `account_balance` value has been renamed to
  `balance`. Only used for the stubs at the moment.
* 2019-10-08 - Nothing applicable
* 2019-09-09 - Nothing applicable
* 2019-08-14 - Nothing applicable
* 2019-05-16 - Nothing applicable

https://stripe.com/docs/upgrades

Also normalize the following IDs in stripe fixtures

* price_[A-Za-z0-9]{24}
* prod_[A-Za-z0-9]{14}
* pi_[A-Za-z0-9]{24}
* il_[A-Za-z0-9]{24}
2021-07-23 21:44:41 -07:00
Vishnu KS 50950de24c billing: Void open invoices of small realms without an active plan.
Previously, we only downgraded and voided small organizations behind
payments only if they had an active plan.

This left us with a bunch of invoices from small realms which used to
have an active plan. It doesn't make much sense for us to get these
realms to pay the invoices so we have decided to just void them. This
commit voids the open invoices of all the small realms without an active
plan and has the last invoice open. Unlike, the realms with an active
plan we don't email them about us voiding the invoice. It's not super
obvious whether Stripe sends an email to the customer when the Invoice
is voided. But they do get the message that the invoice is voided if
they try to pay the invoice through the hosted invoice page.
2021-07-22 17:56:35 -07:00
Vishnu KS b3d3539306 billing: Only fetch customers with stripe customer id in autodowngrade.
The customers without a value for stripe_customer_id never had an active
plan. So we don't have to consider them for autodowngrade.
2021-07-22 17:56:35 -07:00
Vishnu KS eee02a3403 billing: Create customer_has_last_n_invoices_open function.
An additional check for whether customer.stripe_customer_id is
None is added to the function. That check was not really required before
since all the customers with a plan also have a valid value for
stripe_customer_id. So all the calls to stripe.Invoice.list would have
non None value for customer argument.

Even though that is the case, mypy should still have complained about
the possibility of customer.stripe_customer_id being None when passed to
stripe.Invoice.list as customer paramater since mypy don't know that
customers with a plan will always have a non empty value for
stripe_customer_id. Our stripe stubs expect a non empty value for
the customer parameter of stripe.Invoice.list. This is despite the
fact that stripe.Invoice.list can actually be called with customer set
to None. This returns the invoices from the entire organization.
Though, we still decided to ensure that the value of customer should be
non empty since there is no reason for us to ever call this function
with customer set to None. You can just call the function wuthout the
customer argument instead. So this requirement of a non None customer
paramater is useful for catching bugs.

The reason mypy didn't complain was because the type of
Customer.objects.all() is Any and not QuerySet[Customer]. So mypy has no
idea that customer.stripe_customer_id can be theoratically None even
though it was not possible in this [articular case as explained before.
I verified that this was the reason mypy didn't complain by using the
reveal_type function on Customer.objects.all() and the customer object.
After the refactoring it's super to obvious to mypy that the type of the
customer is Customer since it's mentioned in the function defintion. So it
was able to complain about the possibility of customer.stripe_customer_id
being None after the refactoring.
2021-07-22 17:56:35 -07:00
PIG208 72f9f964a1 billing: Refactor BillingError to subclass JsonableError.
This lets us remove unnecessary BillingError to json_error conversion
code.
2021-07-05 10:43:49 -07:00
Vishnu KS e0f5fadb79 billing: Downgrade small realms that are behind on payments.
An organization with at most 5 users that is behind on payments isn't
worth spending time on investigating the situation.

For larger organizations, we likely want somewhat different logic that
at least does not void invoices.
2021-07-02 13:19:12 -07:00
Vishnu KS cb64a19edf models: Rename get_human_billing_admin_users to be more explicit. 2021-07-02 12:04:41 -07:00
Vishnu KS e64296b3e9 stripe: Create get_all_invoices function.
stripe.Invoice.list by default would only get 10 invoices at a
time. So a function like this would be really handy if we have
to go through a lot of invoices.

This also means void_all_open_invoices used to void only the last
10 invoices. The main reason we implemented this function was to
void the invoices generated by realms on free trial so I don't
think there were cases where we had to void realms with more than
10 invoices.
2021-07-02 12:04:41 -07:00
Vishnu KS 1d579ec567 billing: Fix the type annotation of Customer.stripe_customer_id.
This also fixes a bug in void_all_open_invoices function. If a realm
with a local Customer object but without an associated stripe.Customer
is passed to void_all_open_invoices, then the function will end up
voiding the last 10 invoices created by billing system instead of voiding
no invoices at all. This is because stripe.Invoice.list(customer=None)
return last 10 invoices across all customers.

But this bug won't cauuse any issue in production since
void_all_open_invoices can be only invoked from /support page. And we
show the option to void invoices in support page only if the realm
has a paid plan. And it's not really possible for a realm to have
a paid plan without having an associated stripe_customer_id. Plus I
went through the void events in stripe stream since the PR to add
void invoices was merged and there does not seems to be any suspicious
events.
2021-07-02 12:04:41 -07:00
Mateusz Mandera bae86ad3da billing: Move exempt_from_from_license_number_check to Customer model.
This belongs more on the Customer model, since this is a similar
attribute to default_discount.
2021-06-18 14:05:42 -07:00
Vishnu KS 203ddfc546 billing: Create cents_to_dollar_string helper function. 2021-06-14 16:56:18 -07:00
Vishnu KS 1eb83cbadc billing: Create is_sponsored_realm function. 2021-06-14 16:49:53 -07:00
Vishnu KS cdf683e36f billing: Create customer_has_credit_card_as_default_source function. 2021-06-14 16:49:53 -07:00
Vishnu KS 6e3d4e7e75 billing: Create is_free_trial_offer_enabled function. 2021-06-14 16:49:53 -07:00
Vishnu KS f55dbe33bb billing: Create is_realm_on_free_trial helper function. 2021-06-14 16:49:53 -07:00
Vishnu KS 06b5f9feae billing: Create is_free_trial function in CustomerPlan model. 2021-06-14 16:49:53 -07:00
Vishnu KS 40ab415005 activity: Show effective rate of realms in /activity page. 2021-06-11 07:41:02 -07:00
Vishnu KS 42119c136b billing: Allow to exclude realms from license limit check. 2021-06-09 17:42:38 -07:00
Vishnu KS 1938076f67 billing: Enforce license limit for plans on manual license management. 2021-06-09 17:42:38 -07:00
Vishnu KS 3d5ee69b21 stripe: Create make_user_billing_admin helper function. 2021-06-03 10:13:59 -07:00
Vishnu KS 20d2e00cf9 billing: Create update_license_ledger_for_manual_plan. 2021-05-14 15:10:02 -07:00
Vishnu KS c9f4439850 billing: Extract out validate_licenses function. 2021-05-14 15:10:02 -07:00
Anders Kaseorg e7ed907cf6 python: Convert deprecated Django ugettext alias to gettext.
django.utils.translation.ugettext is a deprecated alias of
django.utils.translation.gettext as of Django 3.0, and will be removed
in Django 4.0.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-04-15 18:01:34 -07:00
Siddharth Asthana 80c9243c6a support: Create RealmAuditLog when updating sponsorship status. 2021-04-03 08:01:46 -07:00
Siddharth Asthana 233c4d520c support: Create RealmAuditLog when updating billing_method.
This commit also makes acting_user as a mandantory argument and fixes
the tests accordingly.
2021-04-03 08:01:46 -07:00
Siddharth Asthana 44c34cb39a support: Create RealmAuditLog when approving sponsorship. 2021-04-03 08:01:46 -07:00
Siddharth Asthana 6945ed3587 support: Pass acting_user to attach_discount_to_realm. 2021-04-03 08:01:46 -07:00
Siddharth Asthana c3f37c2a64 support: Pass acting_user to do_change_plan_type.
acting_user is now a mandatory field, so wherever this function is used,
we are passing acting_user as well.
2021-04-03 08:01:46 -07:00
Mateusz Mandera 09fc79f911 actions: Remove realm argument to internal_send_private_message.
The argument is redundant.
2021-02-23 15:26:47 -08:00
Anders Kaseorg 6e4c3e41dc python: Normalize quotes with Black.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-12 13:11:19 -08:00
Anders Kaseorg 11741543da python: Reformat with Black, except quotes.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2021-02-12 13:11:19 -08:00
Vishnu KS bd2642a7b8 billing: Make attach discount update the current price per license. 2020-12-17 17:09:20 -08:00
Vishnu KS 480288643c billing: Create calculate_discounted_price_per_license. 2020-12-17 17:09:20 -08:00
Vishnu KS 71efcca679 stripe: Create get_price_per_license function. 2020-12-17 17:09:20 -08:00
Anders Kaseorg f461a64a6b i18n: Fix some ineffective calls to ugettext at top level.
Translation has no effect when we don’t yet know what language we’re
translating for.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-10-18 14:31:15 -07:00
Vishnu KS 510efbc1a8 support: Add option to change billing method. 2020-09-28 15:37:49 -07:00
Vishnu KS dbaea757ae billing: Create downgrade_at_the_end_of_billing_cycle. 2020-09-28 15:37:49 -07:00
Vishnu KS b8b2e443bc stripe: Create void_all_open_invoices. 2020-09-28 15:37:49 -07:00
Vishnu KS 0d30f59c97 billing: downgrade_now -> downgrade_now_without_creating_additional_invoice. 2020-09-28 15:37:49 -07:00
Alex Vandiver ab4f6b54ea stripe: Do not log credit card issues as errors.
Problems with the card itself should not be logged as errors -- while
perhaps notable in aggregate, they are not worthy of being logged to
Sentry, for instance.

Downgrade these to `info`; continue to log other problems at the
`error` level.  This updates tests for this change, and in so doing
corrects a test that does not do its job, due to a missing
`reset_mock`.
2020-09-23 15:29:47 -07:00
Anders Kaseorg b7b7475672 python: Use standard secrets module to generate random tokens.
There are three functional side effects:

• Correct an insignificant but mathematically offensive bias toward
repeated characters in generate_api_key introduced in commit
47b4283c4b4c70ecde4d3c8de871c90ee2506d87; its entropy is increased
from 190.52864 bits to 190.53428 bits.

• Use the base32 alphabet in confirmation.models.generate_key; its
entropy is reduced from 124.07820 bits to the documented 120 bits, but
now it uses 1 syscall instead of 24.

• Use the base32 alphabet in get_bigbluebutton_url; its entropy is
reduced from 51.69925 bits to 50 bits, but now it uses 1 syscall
instead of 10.

(The base32 alphabet is A-Z 2-7.  We could probably replace all of
these with plain secrets.token_urlsafe, since I expect most callers
can handle the full urlsafe_b64 alphabet A-Z a-z 0-9 - _ without
problems.)

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-09-09 15:52:57 -07:00
Anders Kaseorg 61d0417e75 python: Replace ujson with orjson.
Fixes #6507.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-08-11 10:55:12 -07:00
Anders Kaseorg 60a25b2721 docs: Fix spelling errors caught by codespell.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-08-11 10:23:06 -07:00
Vishnu KS 5b0b1efb15 support: Add functionality to approve sponsorship requests.
This should make it much easier to process these requests.
2020-07-24 17:55:38 -07:00
Vishnu KS 4c6350fa4b billing: Add option to request a sponsorship in /upgrade. 2020-07-01 16:45:38 -07:00
Anders Kaseorg f2e7076e2a decorator: Replace type: ignore with cast, avoid Any.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-30 18:58:23 -07:00
Anders Kaseorg 8667e632c8 python: Use isinstance with a tuple for testing multiple types.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-30 18:58:23 -07:00
Anders Kaseorg b95fc4858d corporate: Replace unchecked cast calls with assert.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-22 17:11:42 -07:00
Vishnu KS 508ba663dc billing: Wrap make_end_of_cycle_updates_if_needed with transaction.atomic.
Otherwise, the plan would be left in an inconsistent state if one of the
queries fail.
2020-06-18 17:34:03 -07:00
Vishnu KS cde4486f8c billing: Support switching from monthly to annual plan. 2020-06-18 17:34:03 -07:00
Anders Kaseorg f364d06fb5 python: Convert percent formatting to .format for translated strings.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-15 16:24:46 -07:00
Anders Kaseorg 5dc9b55c43 python: Manually convert more percent-formatting to f-strings.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-14 23:27:22 -07:00
Anders Kaseorg 69c0959f34 python: Fix misuse of Optional types for optional parameters.
There seems to have been a confusion between two different uses of the
word “optional”:

• An optional parameter may be omitted and replaced with a default
  value.
• An Optional type has None as a possible value.

Sometimes an optional parameter has a default value of None, or None
is otherwise a meaningful value to provide, in which case it makes
sense for the optional parameter to have an Optional type.  But in
other cases, optional parameters should not have Optional type.  Fix
them.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-13 15:31:27 -07:00
Anders Kaseorg 365fe0b3d5 python: Sort imports with isort.
Fixes #2665.

Regenerated by tabbott with `lint --fix` after a rebase and change in
parameters.

Note from tabbott: In a few cases, this converts technical debt in the
form of unsorted imports into different technical debt in the form of
our largest files having very long, ugly import sequences at the
start.  I expect this change will increase pressure for us to split
those files, which isn't a bad thing.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-11 16:45:32 -07:00
Anders Kaseorg 67e7a3631d python: Convert percent formatting to Python 3.6 f-strings.
Generated by pyupgrade --py36-plus.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-10 15:02:09 -07:00
Anders Kaseorg 8dd83228e7 python: Convert "".format to Python 3.6 f-strings.
Generated by pyupgrade --py36-plus --keep-percent-format, but with the
NamedTuple changes reverted (see commit
ba7906a3c6, #15132).

Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-06-08 15:31:20 -07:00
Vishnu Ks 66b1ad7002 billing: User FREE_TRIAL_DAYS instead of FREE_TRIAL_MONTHS. 2020-05-16 14:52:01 -07:00
Vishnu KS f1b1bf5a0d billing: Add support for Zulip Standard free trial. 2020-05-11 17:20:54 -07:00
Vishnu Ks 66a437bbf1 billing: Restrict max licenses to 1000 during upgrade via invoice. 2020-05-11 17:09:49 -07:00
Anders Kaseorg 7271fb68aa logging: Pass format arguments to unconventionally-named loggers too.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2020-05-02 16:37:07 -07:00
Vishnu KS 3f69500765 billing: Rename downgrade_realm_for_deactivation to downgrade_now. 2020-04-28 22:05:49 -07:00
Anders Kaseorg fead14951c python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.

We can likely further refine the remaining pieces with some testing.

Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:

-    invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+    invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(

-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None

-    notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
-    signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+    notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+    signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)

-    author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+    author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)

-    bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+    bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)

-    default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-    default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+    default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+    default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)

-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}

-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}

-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 11:02:32 -07:00
Anders Kaseorg f8c95cda51 mypy: Add specific codes to type: ignore annotations.
https://mypy.readthedocs.io/en/stable/error_codes.html

Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 10:46:33 -07:00
Stefan Weil d2fa058cc1
text: Fix some typos (most of them found and fixed by codespell).
Signed-off-by: Stefan Weil <sw@weilnetz.de>
2020-03-27 17:25:56 -07:00
Vishnu KS 8b24d40585 billing: Create get_current_plan_by_realm helper function. 2020-03-25 10:54:10 -07:00
Vishnu KS 83da23c0d4 billing: Rename get_current_plan to get_current_plan_by_customer.
Also add tests.
2020-03-25 10:54:10 -07:00
Vishnu KS 9a2c64f3f4 billing: Create get_customer_by_realm helper function. 2020-03-25 10:54:10 -07:00
Vishnu KS f8ddab58ba billing: Downgrade plan to Limited during realm deactivation.
The realm would be instantly downgraded to Limited plan when
deactivated. Any extra users that were added in the final month
would not be charged.
2020-03-25 10:54:10 -07:00
Tim Abbott f83871c182 corporate: Consistently use delivery_email for billing.
This is yet another corner case where EMAIL_ADDRESS_VISIBILITY_ADMINS
broke existing code.
2019-11-18 17:02:57 -08:00
Anders Kaseorg 93b1c3d94b settings: Extract config file functions to a module.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-11-13 12:38:45 -08:00
Rishi Gupta 0b39263ec0 billing: Rename get_seat_count to get_latest_seat_count.
This will help as our billing system becomes more async to accommodate
on-prem billing.
2019-10-21 16:06:18 -07:00
Rishi Gupta e10361a832 models: Replace is_guest and is_realm_admin with UserProfile.role.
This new data model will be more extensible for future work on
features like a primary administrator.
2019-10-06 16:24:37 -07:00
Anders Kaseorg 9a9de156c3 lint: Fix calls to _() on computed strings.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-04-23 15:23:03 -07:00
Rishi Gupta 1a7a449572 billing: Add backend support for downgrading. 2019-04-11 23:27:42 -07:00
Rishi Gupta a529080f01 billing: Rename add_plan_renewal_to_license_ledger_if_needed. 2019-04-11 23:20:29 -07:00
Rishi Gupta 03d21c6317 billing: Refactor renewal_amount to return 0 instead of None. 2019-04-11 23:20:29 -07:00