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.
This commit is contained in:
Vishnu KS
2021-06-18 19:10:45 +00:00
committed by Tim Abbott
parent 127d3de125
commit 1d579ec567
17 changed files with 934 additions and 42 deletions

View File

@@ -319,6 +319,7 @@ def do_replace_payment_source(
) -> stripe.Customer:
customer = get_customer_by_realm(user.realm)
assert customer is not None # for mypy
assert customer.stripe_customer_id is not None # for mypy
stripe_customer = stripe_get_customer(customer.stripe_customer_id)
stripe_customer.source = stripe_token
@@ -533,6 +534,8 @@ def process_initial_upgrade(
) -> None:
realm = user.realm
customer = update_or_create_stripe_customer(user, stripe_token=stripe_token)
assert customer.stripe_customer_id is not None # for mypy
charge_automatically = stripe_token is not None
free_trial = is_free_trial_offer_enabled()
@@ -710,6 +713,11 @@ def update_license_ledger_if_needed(realm: Realm, event_time: datetime) -> None:
def invoice_plan(plan: CustomerPlan, event_time: datetime) -> None:
if plan.invoicing_status == CustomerPlan.STARTED:
raise NotImplementedError("Plan with invoicing_status==STARTED needs manual resolution.")
if not plan.customer.stripe_customer_id:
raise BillingError(
f"Realm {plan.customer.realm.string_id} has a paid plan without a Stripe customer."
)
make_end_of_cycle_updates_if_needed(plan, event_time)
if plan.invoicing_status == CustomerPlan.INITIAL_INVOICE_TO_BE_SENT:
@@ -957,6 +965,8 @@ def void_all_open_invoices(realm: Realm) -> int:
customer = get_customer_by_realm(realm)
if customer is None:
return 0
if customer.stripe_customer_id is None:
return 0
invoices = stripe.Invoice.list(customer=customer.stripe_customer_id)
voided_invoices_count = 0
for invoice in invoices: