Renames the remote_server_legacy_plan parameter in
process_initial_upgrade to instead be complimentary_access_plan,
as well as some relevant code comments in that function.
Adds ability to configure a fixed-price plan and to delete a
configured fixed-price plan in the Cloud support view.
Updates the invoice processing to send reminder emails to the
billing support email for these Cloud fixed-price plans about
renewals since we now are able to configure them via our support
panel.
Updates function to get the billing session for stripe webhook
events to handle an intial upgrade for a custom generated invoice
for a fixed-price plan for a Cloud organization, which won't have
a user_id in the invoice metadata.
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.
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.
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.
Instead of charging the customer using the attached payment
method and then creating the invoice, we create an invoice and
force an immediate payment for the invoice via the attached
payment method.
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.
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.
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.
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".
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.
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.
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>
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.
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.