We use `error_description` in upgrade.ts to determine if the error was
related to customer's card. Doesn't seem like there is any harm
in doing so since we are explicitly handling "stripe.CardError" and
raising these errors with "card error" description.
This also now allows user to upgrade to plus plan from pricing page.
Note that since we don't pass customer_plan on pages like self-hosting
and for/business, `Current plan` status is not displayed on these pages.
Earlier, we were not verifying that the invoice which got paid is
for the fixed-price plan.
That could result in a bug where another support invoice with
collection_method = "send_invoice" got paid while a fixed-price
plam is already configured. The fixed-price plan would be falsely
activated.
This commit verifies the invoice before activating the fixed-price
plan.
For simiplicty's sake, we can avoid trying to do cache invalidation in
the variety of events that can cause the seat count to change - since
having an up to 1 day delay between users being added and the upload
limit going up is quite reasonable.
This might not be the most meaningful change of phrasing, but .is_paid()
sounds like it's a check for whether the customer has already paid their
invoice. is_a_paid_plan() reflects better the meaning that it's whether
it's a plan of a "paid" type.
We send customer an invoice at the start of free trial, if customer
pays we upgrade them to the active plan at the end of free trial,
else we downgrade them and show a custom message on the upgrade
page regarding the current status.
Tests were broken since #29221 and #28875 didn't account for
other tests failing due to changed stripe data. Also, there
was a bug where we were not fetching the correct setup intent
and stripe session for the current test, it was fixed by narrowing
the fetch to the current customer.
Also, we now run `invoice_plans` in a `while` loop until
`next_invoice_date` is greater than the provided event_time. It
makes sense to generate all the invoices for a customer that
needs to paid by them when `invoice_plans_as_needed` is called
for a `event_time`.
Earlier, when adding a new user failed due to no spare licenses
available, a message was sent to the "New user announcements"
stream.
We plan to disable the stream by default as a part of improving
onboarding experience.
Now, we send a group DM to admins when adding a new user fails
due to no spare licenses available. It makes it independent of
the "New user announcements" setting. These warning messages
are important and shouldn't be missed.
As explained in the comment, when we're moving the server plan to the
remote realm's Customer object, the realm Customer may not have
stripe_customer_id set and therefore that value needs to get moved from
the server Customer.
Earlier, if a free plan (say legacy plan) with no next plan scheduled
was invoiced, we used to send an invoice overdue email if the last
audit log update is stale.
Actually, we don't need this data as the invoice step is just going
to downgrade the current plan. We should not wait for customer to
start uploading data in this case. Skip the email sending step and
invoice the plan to downgrade.
This decorator, among other things, transforms the "event" argument
passed when calling the decorated functions into actually passing
event.content_object.
So e.g. despite having a (before the decorator is applied) signature:
```
def handle_invoice_paid_event(stripe_invoice: stripe.Invoice, invoice: Invoice) -> None:
```
these are called passing an `Event` in the second arg when calling
`handle_invoice_paid_event`:
```
handle_invoice_paid_event(stripe_invoice, event)
```
I found that kind of confusing because the @error_handler decorator
didn't sound like something that would intervene in the arguments like
that. So it feels helpful to rename it something with a less modest
name, that makes it sound like it does more than just pure
error-handling.
Adds a line to the top of the internal_billing_notice email with
the billing entity's display name.
Makes sure all internal_billng_notice email subjects also include
the billing entity's display name.
Makes small updates to the notice text for some cases.
When a customer with plan's status 'DOWNGRADE_AT_END_OF_FREE_TRIAL'
visits /billing page on the free-trial end date before the invoice
cron runs, the 'make_end_of_cycle_updates_if_needed' downgrades the
plan.
Earlier, when such a customer visited /billing page in this time window
it resulted in an assertion error.
This commit fixes the incorrect behaviour by redirecting to '/plans'
page in such cases.
No renewal ledger entry exists for a scheduled fixed-price plan.
Earlier, when a customer with scheduled fixed-price plan
visited the billing page, the 'get_next_billing_cycle' function
raised an assertion error as no renewal ledger entry exists.
We don't need the ledger entry to determine the next billing
cycle in this case, so we move the code block which determines
the last_renewal time within the else conditional i.e. where it
is needed.
The scheduled plan has NEVER_STARTED status and the
next_invoice_date set to end date of the current plan.
We use this info to determine the next billing cycle.
If the remote realm registered after the legacy plan on server
ENDED, we never migrate the plan to the remote realm. So, we need
to check the server of remote realm whenever we are check remote
realm for legacy plan.
Regardless of plan renewal schedule, we try to invoice all plans
monthly with some exceptions like free trial and fixed price plans,
which help us charge users for additional licenses used during
the previous month.
Includes has_stale_audit_log boolean in the remote support view
data so that we can style the last audit log data for the remote
server to visually highlight if it is stale or current.
Refactors the Cloud support view to pass in any sponsorship or
discount information about the Customer object for the realm,
which allows us to display any information submitted in a
sponsorship request.
Earlier, we were using 'timezone_now' (the time cron job runs)
as the event_time while invoicing plans in 'invoice_plan'.
This is not accurate as it will lead to invoicing ledger entries
created after 'next_invoice_date' and before 'timezone_now'.
We should only invoice the ledger entries created till
next_invoice_date. It should be independent of the time
at which cron job runs.
This commit updates the logic to use next_invoice_date
as the event_time while invoicing via cron.
Earlier, at few places in test_stripe we were doing
incorrect time travel. For example:
A plan was invoiced till self.next_year & while checking
the billing page, we were using the self.now datetime.
To correctly check the billing page state after plan is
invoiced, we should use a datetime greater than or equal
to the time at which plan was invoiced.
This commit fixes such logically incorrect time-travel.
While creating a LicenseLedger entry in 'create_customer_and_plan',
we should set the 'event_time' to the same time at which the plan
is created.
Earlier, the 'event_time' for ledger entry & 'billing_cycle_anchor'
of the plan were set to different values, which is not the
correct behavior.
Earlier, the code block to calculate additional licenses charge
raised assertion error when processing a fixed-price plan.
The code block shouldn't be executed for fixed-price plans as
we don't charge customers on fixed-price plans for additional
licenses.
Earlier, the 'self.on_paid_plan()' check was verifying if the
billing_session/customer is on paid plan and not the plan we
are processing.
This resulted in a bug. While processing a legacy plan, a customer
switches from legacy plan to a paid plan resulting in the
'self.on_paid_plan()' check returning True.
It leads to invoicing legacy plan which shouldn't happen.
The fix is to check if the plan we are processing is paid or not
instead of the remote_realm/remote_server plan_type.
It's best for these to just be consistent. Therefore:
1. The .../not-configured/ error page endpoint should be restricted to
.has_billing_access users only.
2. For consistency, self_hosting_auth_view_common is tweaked to also do
the .has_billing_access check as the first thing, to avoid revealing
configuration information via its redirect/error-handling behavior.
The revealed configuration information seems super harmless, but it's
simpler to not have to worry about it and just be consistent.
Just shows a config error page if the bouncer is not enabled. Uses a new
endpoint for this so that it can work nicely for both browser and
desktop app clients.
It's necessary, because the desktop app expects to get a json response
with either an error or billing_access_url to redirect to. Showing a
nice config error page can't be done via the json error mechanism, so
instead we just serve a redirect to the new error page, which the app
will open in the browser in a new window or tab.
The remote support view now returns results for deactivated remote
servers with those results sorted to the end and formatted to
visually stand out.
Forms to change sponsorship and discount fields on the customer
for the remote server or realm are not shown, but the data stored
on the customer object is shown, including any sponsorship request
information (if the customer had a sponsorship request pending when
it was deactivated).
Forms to schedule a plan are also not shown for deactivated servers
and their associated remote realms.
Forms and information for any current plan or scheduled plan, for
either the deactivated remote server or its associated remote
realms, are shown so that support staff can update those plans if
necessary.
Earlier, the 'next_invoice_date', 'invoiced_through', and
'invoicing_status' fields in 'do_change_schedule_after_free_trial'
were not set correctly.
It resulted in invoicing the ENDED plan and the ledger we create
in this function.
Multiple invoices were created depending on the number of times the
billing frequency was changed for customers who started free-trial
& changed their billing frequency.
This commit sets those fields correctly leading to create only one
invoice.
As a follow-up to commit d66b7ad853, where we send internal emails
when an active paid plan is on a locally deleted remote realm, we
need search queries in the remote support view to return results
for these deactivated remote realms, instead of excluding them.
This is a prep commit which replaces the 'invoice_overdue'
and 'reminder_to_review_plan' email templates with
'internal_billing_notice'.
This will help us to use the same template as we plan to
send an email to sales when a remote realm with paid plan
attached is locally deleted.
Because the remote support page now supports searching by UUID,
the support URL for remote billing entities, which is used for
sponsorship request emails and overdue invoice emails, can now
use the remote server or realm UUID.
Adds the remote realm UUID to the remote support view information.
Earlier, for fixed-price plans we were showing the generic
next payment info on billing page which stated that plan
will automatically renew on end_date. It is no longer correct
for fixed-price plans.
This commit fixes the next payment info for fixed-price plans.
When the next_billing_cycle is the end_date, we inform the customer
that their plan will end on end_date and zulip sales will contact
them a couple of month ago before the end_date for renewal.
Earlier, the 'renewal_amount' context variable passed to the billing
template was always a non-empty string which is a truthy value.
The expected behavior is that 'renewal_amount' should be falsy value
if the renewal_cents is 0. This commit fixes the incorrect behavior
and returns None in this case.
Adds the information returned by get_push_status_for_remote_request
for remote billing users to the support page. Note that getting
the current push status data will result in some duplicate database
queries (getting customer, plan, current billed users, next billing
cycle) when generating the remote support view.
The previous logic incorrectly used the server-level number of users
even when a (presumably smaller) realm-level count was available.
Fixes a bug introduced in 2e1ed4431a.
Instead of querying the database twice for the user counts and
for the currently used licenses, we know pass that information
to the function where we populate the plan data for the remote
server or realm.
Only get the customer information once from the database.
Extract logic for any next plan (either scheduled or an offer) data
into a separate function and do not hit database twice when getting
the number of licenses at the next plan renewal.
Removes prefetch of remote realms on the remote server query
because, by excluding the system bot realm when this is referenced,
we end up hitting the database again.
Strips whitespace from query string and makes a few small updates
for readability.
We no longer want to migrate Legacy plans from server to realms, since
Legacy plans are not really a thing in the original sense anymore, since
February 15th.
Now they're just a tool to give temporary extensions of access to the
push notification service for users, when needed. And as such, it makes
no sense to migrate like that.
The remaining code in this function is for migrating (any) plan from the
server object to the realm object, if the server has just a single
realm.
For fixed-price plans, we send a reminder email to
sales@zulip.com, 2 months before the end date, to review
the pricing and configure a new fixed-price plan accordingly.
Earlier, we were not configuring the end_date while creating
a fixed-price plan which would result in the automatic renewal
of the plan at the end of billing cycle.
Our plan is to re-evaluate the pricing when we are close to the
end date, then:
1. Schedule a new fixed-price plan with the updated or same price.
2. or Don't do anything - the plan will end on the end_date.
Now, we add an end_date of 1 year from the start date when
creating a fixed-price plan.
This is a prep commit which extracts the write_to_audit_log
code block in `update_end_date_of_current_plan` into a
function named 'write_to_audit_log_plan_property_changed'.
This would be helpful to avoid repetition as we plan to
include another such block when the plan's property
reminder_to_review_plan_email_sent is changed.
The logic in the case where there's only one realm and the function
tries to migrate the server's plan to it, had two main unhandled edge
cases that would throw exceptions:
1.
```
remote_realm = RemoteRealm.objects.get(
uuid=realm_uuids[0], plan_type=RemoteRealm.PLAN_TYPE_SELF_MANAGED
)
```
This could throw an exception if the RemoteRealm exists, but has an
active e.g. Legacy plan. Then there'd be no object matching the
plan_type in the query, raising RemoteRealm.DoesNotExist.
2. If the RemoteRealm had e.g. a Legacy plan in the past, that's now
expired, then it'd have a Customer object. Meaning that the attempt
to move the server's customer to the realm:
`server_plan.customer = remote_realm_customer`
would trigger an IntegrityError since a RemoteRealm can't have two
Customer objects.
In simple cases the situation in (2) can still be easily migrated, by
moving the plan from the server's customer to the realm's customer.
This is kind of too specific, allowing testing for only one single error
when accessing signed_auth_url. Instead, this should use a general
pattern, which will allow other tests to use this to assert other kinds
of error responses that may be returned.
It doesn't make sense to run a loop over "all" query results, when those
results are just two, and each of them has its own distinct asserts.
That for loop is there probably due to copying the structure of the
earlier test_transfer_legacy_plan_from_server_to_all_realms test, for
which the loop does make sense.
In 'invoice_plan()', the primary way to not create
an invoice for a plan is to not have any new ledger entry.
This commit adds a 'self.on_paid_plan()' check which is
an extra layer of defense to avoid creating any invoices
for customers not on paid plan. It saves a DB query too.
RemoteRealm customer takes precedence over RemoteServer
in general. But if an inactive plan is associated with
RemoteRealm and an active plan with RemoteServer, the
ACTIVE plan takes precendence.
Co-authored-by: Prakhar Pratyush <prakhar@zulip.com>
Instead of displaying the end of the day interval for the latest
count stat update for push notifications forwarded on the bouncer,
we display the start of the day interval and format it as a date
instead of a date and time.
In commit 230294c, the logic for creating the support links in the
remote activity chart were broken. Updates the constants so that
the links have the correct server information for the search query.
Earlier, during invoicing we used to send a mail to
sales@zulip.com when the last_audit_log_update was
at least one day ago.
There was an assertion that last_audit_log_update
can't be None which is incorrect as such customers exist.
This commit extends the behaviour to send an invoice
overdue email even if the data was never uploaded.
In cases where a support admin approves a sponsorship for a remote
server without any billing users having been created, note that no
emails were sent in the success message on the support page.
We already do this for remote realms and should be an infrequent
case as ideally most remote servers will submit official
sponsorship requests.
Earlier, on extending end_date for legacy plans, next_invoice_date
was not extended which resulted in invoice_plan() run for such
legacy plans before the end_date.
The intended behaviour is that the legacy plan should be invoiced
only once on the end_date to downgrade or switch to a new tier.
This commit adds logic to update next_invoice_date when end_date
is extended via /support.
Expands the main query for remote servers to get the audit log
event datetime for when the server was created/registered.
The remote realm object has a field for when the remote realm
was created on the remote server.
Adds a link on the upgrade and billing pages that opens a stripe
billing portal for the customer to update their name and address
that will appear on invoices and receipts.
On the billing page, updating the credit card information will
no longer update the customer billing address, since they can
now do this directly through the billing portal. To be consistent
with the credit card form on the upgrade page, we still require
inputting a billing address for the card.
Note that, once an invoice is paid/complete, then changes to the
customer's name and address will not be applied to those invoices.
Set the name on the stripe customer object to the name on the
credit card when one is initially attached to a customer.
Prep commit to adding a stripe billing portal so that billing
admins will be able to update both the name and address that
appears on their stripe generated invoices and receipts.