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.') add_plan_renewal_to_license_ledger_if_needed(plan, event_time) assert(plan.invoiced_through is not None) licenses_base = plan.invoiced_through.licenses invoice_item_created = False for ledger_entry in LicenseLedger.objects.filter(plan=plan, id__gt=plan.invoiced_through.id, event_time__lte=event_time).order_by('id'): price_args = {} # type: Dict[str, int] if ledger_entry.is_renewal: if plan.fixed_price is not None: price_args = {'amount': plan.fixed_price} else: assert(plan.price_per_license is not None) # needed for mypy price_args = {'unit_amount': plan.price_per_license, 'quantity': ledger_entry.licenses} description = "Zulip Standard - renewal" elif ledger_entry.licenses != licenses_base: assert(plan.price_per_license) last_renewal = LicenseLedger.objects.filter( plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time) \ .order_by('-id').first().event_time period_end = next_renewal_date(plan, ledger_entry.event_time) proration_fraction = (period_end - ledger_entry.event_time) / (period_end - last_renewal) price_args = {'unit_amount': int(plan.price_per_license * proration_fraction + .5), 'quantity': ledger_entry.licenses - licenses_base} description = "Additional license ({} - {})".format( ledger_entry.event_time.strftime('%b %-d, %Y'), period_end.strftime('%b %-d, %Y')) if price_args: plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.STARTED plan.save(update_fields=['invoicing_status', 'invoiced_through']) idempotency_key = 'ledger_entry:{}'.format(ledger_entry.id) # type: Optional[str] if settings.TEST_SUITE: idempotency_key = None stripe.InvoiceItem.create( currency='usd', customer=plan.customer.stripe_customer_id, description=description, discountable=False, period = {'start': datetime_to_timestamp(ledger_entry.event_time), 'end': datetime_to_timestamp(next_renewal_date(plan, ledger_entry.event_time))}, idempotency_key=idempotency_key, **price_args) invoice_item_created = True plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.DONE plan.save(update_fields=['invoicing_status', 'invoiced_through']) licenses_base = ledger_entry.licenses if invoice_item_created: if plan.charge_automatically: billing_method = 'charge_automatically' days_until_due = None else: billing_method = 'send_invoice' days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, billing=billing_method, customer=plan.customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor='Zulip Standard') stripe.Invoice.finalize_invoice(stripe_invoice) plan.next_invoice_date = next_invoice_date(plan) plan.save(update_fields=['next_invoice_date'])
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.') make_end_of_cycle_updates_if_needed(plan, event_time) assert (plan.invoiced_through is not None) licenses_base = plan.invoiced_through.licenses invoice_item_created = False for ledger_entry in LicenseLedger.objects.filter( plan=plan, id__gt=plan.invoiced_through.id, event_time__lte=event_time).order_by('id'): price_args: Dict[str, int] = {} if ledger_entry.is_renewal: if plan.fixed_price is not None: price_args = {'amount': plan.fixed_price} else: assert (plan.price_per_license is not None) # needed for mypy price_args = { 'unit_amount': plan.price_per_license, 'quantity': ledger_entry.licenses } description = "Zulip Standard - renewal" elif ledger_entry.licenses != licenses_base: assert (plan.price_per_license) last_renewal = LicenseLedger.objects.filter( plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time) \ .order_by('-id').first().event_time period_end = start_of_next_billing_cycle(plan, ledger_entry.event_time) proration_fraction = (period_end - ledger_entry.event_time) / ( period_end - last_renewal) price_args = { 'unit_amount': int(plan.price_per_license * proration_fraction + .5), 'quantity': ledger_entry.licenses - licenses_base } description = "Additional license ({} - {})".format( ledger_entry.event_time.strftime('%b %-d, %Y'), period_end.strftime('%b %-d, %Y')) if price_args: plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.STARTED plan.save(update_fields=['invoicing_status', 'invoiced_through']) idempotency_key: Optional[str] = 'ledger_entry:{}'.format( ledger_entry.id) if settings.TEST_SUITE: idempotency_key = None stripe.InvoiceItem.create( currency='usd', customer=plan.customer.stripe_customer_id, description=description, discountable=False, period={ 'start': datetime_to_timestamp(ledger_entry.event_time), 'end': datetime_to_timestamp( start_of_next_billing_cycle(plan, ledger_entry.event_time)) }, idempotency_key=idempotency_key, **price_args) invoice_item_created = True plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.DONE plan.save(update_fields=['invoicing_status', 'invoiced_through']) licenses_base = ledger_entry.licenses if invoice_item_created: if plan.charge_automatically: billing_method = 'charge_automatically' days_until_due = None else: billing_method = 'send_invoice' days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, billing=billing_method, customer=plan.customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor='Zulip Standard') stripe.Invoice.finalize_invoice(stripe_invoice) plan.next_invoice_date = next_invoice_date(plan) plan.save(update_fields=['next_invoice_date'])
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: assert plan.customer.realm is not None 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: invoiced_through_id = -1 licenses_base = None else: assert plan.invoiced_through is not None licenses_base = plan.invoiced_through.licenses invoiced_through_id = plan.invoiced_through.id invoice_item_created = False for ledger_entry in LicenseLedger.objects.filter( plan=plan, id__gt=invoiced_through_id, event_time__lte=event_time ).order_by("id"): price_args: Dict[str, int] = {} if ledger_entry.is_renewal: if plan.fixed_price is not None: price_args = {"amount": plan.fixed_price} else: assert plan.price_per_license is not None # needed for mypy price_args = { "unit_amount": plan.price_per_license, "quantity": ledger_entry.licenses, } description = f"{plan.name} - renewal" elif licenses_base is not None and ledger_entry.licenses != licenses_base: assert plan.price_per_license last_ledger_entry_renewal = ( LicenseLedger.objects.filter( plan=plan, is_renewal=True, event_time__lte=ledger_entry.event_time ) .order_by("-id") .first() ) assert last_ledger_entry_renewal is not None last_renewal = last_ledger_entry_renewal.event_time billing_period_end = start_of_next_billing_cycle(plan, ledger_entry.event_time) plan_renewal_or_end_date = get_plan_renewal_or_end_date(plan, ledger_entry.event_time) proration_fraction = (plan_renewal_or_end_date - ledger_entry.event_time) / ( billing_period_end - last_renewal ) price_args = { "unit_amount": int(plan.price_per_license * proration_fraction + 0.5), "quantity": ledger_entry.licenses - licenses_base, } description = "Additional license ({} - {})".format( ledger_entry.event_time.strftime("%b %-d, %Y"), plan_renewal_or_end_date.strftime("%b %-d, %Y"), ) if price_args: plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.STARTED plan.save(update_fields=["invoicing_status", "invoiced_through"]) stripe.InvoiceItem.create( currency="usd", customer=plan.customer.stripe_customer_id, description=description, discountable=False, period={ "start": datetime_to_timestamp(ledger_entry.event_time), "end": datetime_to_timestamp( get_plan_renewal_or_end_date(plan, ledger_entry.event_time) ), }, idempotency_key=get_idempotency_key(ledger_entry), **price_args, ) invoice_item_created = True plan.invoiced_through = ledger_entry plan.invoicing_status = CustomerPlan.DONE plan.save(update_fields=["invoicing_status", "invoiced_through"]) licenses_base = ledger_entry.licenses if invoice_item_created: if plan.charge_automatically: collection_method = "charge_automatically" days_until_due = None else: collection_method = "send_invoice" days_until_due = DEFAULT_INVOICE_DAYS_UNTIL_DUE stripe_invoice = stripe.Invoice.create( auto_advance=True, collection_method=collection_method, customer=plan.customer.stripe_customer_id, days_until_due=days_until_due, statement_descriptor=plan.name, ) stripe.Invoice.finalize_invoice(stripe_invoice) plan.next_invoice_date = next_invoice_date(plan) plan.save(update_fields=["next_invoice_date"])