Esempio n. 1
0
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'])
Esempio n. 2
0
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'])
Esempio n. 3
0
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"])