Ejemplo n.º 1
0
def change_plan_at_end_of_cycle(request: HttpRequest, user: UserProfile,
                                status: int=REQ("status", validator=check_int)) -> HttpResponse:
    assert(status in [CustomerPlan.ACTIVE, CustomerPlan.DOWNGRADE_AT_END_OF_CYCLE])
    plan = get_current_plan(Customer.objects.get(realm=user.realm))
    assert(plan is not None)  # for mypy
    do_change_plan_status(plan, status)
    return json_success()
Ejemplo n.º 2
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    if not settings.BILLING_ENABLED:
        return render(request, "404.html")

    user = request.user
    customer = Customer.objects.filter(realm=user.realm).first()
    if customer is not None and get_current_plan(customer) is not None:
        return HttpResponseRedirect(reverse('corporate.views.billing_home'))

    percent_off = 0
    if customer is not None and customer.default_discount is not None:
        percent_off = customer.default_discount

    seat_count = get_latest_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context = {
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'email': user.delivery_email,
        'seat_count': seat_count,
        'signed_seat_count': signed_seat_count,
        'salt': salt,
        'min_invoiced_licenses': max(seat_count, MIN_INVOICED_LICENSES),
        'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE,
        'plan': "Zulip Standard",
        'page_params': {
            'seat_count': seat_count,
            'annual_price': 8000,
            'monthly_price': 800,
            'percent_off': float(percent_off),
        },
    }  # type: Dict[str, Any]
    response = render(request, 'corporate/upgrade.html', context=context)
    return response
Ejemplo n.º 3
0
def update_license_ledger_if_needed(realm: Realm, event_time: datetime) -> None:
    customer = Customer.objects.filter(realm=realm).first()
    if customer is None:
        return
    plan = get_current_plan(customer)
    if plan is None:
        return
    if not plan.automanage_licenses:
        return
    update_license_ledger_for_automanaged_plan(realm, plan, event_time)
Ejemplo n.º 4
0
def billing_home(request: HttpRequest) -> HttpResponse:
    user = request.user
    customer = Customer.objects.filter(realm=user.realm).first()
    if customer is None:
        return HttpResponseRedirect(reverse('corporate.views.initial_upgrade'))
    if not CustomerPlan.objects.filter(customer=customer).exists():
        return HttpResponseRedirect(reverse('corporate.views.initial_upgrade'))

    if not user.is_realm_admin and not user.is_billing_admin:
        context = {'admin_access': False}  # type: Dict[str, Any]
        return render(request, 'corporate/billing.html', context=context)
    context = {'admin_access': True}

    plan_name = "Zulip Free"
    licenses = 0
    renewal_date = ''
    renewal_cents = 0
    payment_method = ''
    charge_automatically = False

    stripe_customer = stripe_get_customer(customer.stripe_customer_id)
    plan = get_current_plan(customer)
    if plan is not None:
        plan_name = {
            CustomerPlan.STANDARD: 'Zulip Standard',
            CustomerPlan.PLUS: 'Zulip Plus',
        }[plan.tier]
        now = timezone_now()
        last_ledger_entry = make_end_of_cycle_updates_if_needed(plan, now)
        if last_ledger_entry is not None:
            licenses = last_ledger_entry.licenses
            licenses_used = get_seat_count(user.realm)
            # Should do this in javascript, using the user's timezone
            renewal_date = '{dt:%B} {dt.day}, {dt.year}'.format(
                dt=start_of_next_billing_cycle(plan, now))
            renewal_cents = renewal_amount(plan, now)
            charge_automatically = plan.charge_automatically
            if charge_automatically:
                payment_method = payment_method_string(stripe_customer)
            else:
                payment_method = 'Billed by invoice'

    context.update({
        'plan_name': plan_name,
        'licenses': licenses,
        'licenses_used': licenses_used,
        'renewal_date': renewal_date,
        'renewal_amount': '{:,.2f}'.format(renewal_cents / 100.),
        'payment_method': payment_method,
        'charge_automatically': charge_automatically,
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'stripe_email': stripe_customer.email,
    })
    return render(request, 'corporate/billing.html', context=context)
Ejemplo n.º 5
0
def process_initial_upgrade(user: UserProfile, licenses: int, automanage_licenses: bool,
                            billing_schedule: int, stripe_token: Optional[str]) -> None:
    realm = user.realm
    customer = update_or_create_stripe_customer(user, stripe_token=stripe_token)
    if get_current_plan(customer) is not None:
        # Unlikely race condition from two people upgrading (clicking "Make payment")
        # at exactly the same time. Doesn't fully resolve the race condition, but having
        # a check here reduces the likelihood.
        billing_logger.warning(
            "Customer {} trying to upgrade, but has an active subscription".format(customer))
        raise BillingError('subscribing with existing subscription', BillingError.TRY_RELOADING)

    billing_cycle_anchor, next_invoice_date, period_end, price_per_license = compute_plan_parameters(
        automanage_licenses, billing_schedule, customer.default_discount)
    # The main design constraint in this function is that if you upgrade with a credit card, and the
    # charge fails, everything should be rolled back as if nothing had happened. This is because we
    # expect frequent card failures on initial signup.
    # Hence, if we're going to charge a card, do it at the beginning, even if we later may have to
    # adjust the number of licenses.
    charge_automatically = stripe_token is not None
    if charge_automatically:
        stripe_charge = stripe.Charge.create(
            amount=price_per_license * licenses,
            currency='usd',
            customer=customer.stripe_customer_id,
            description="Upgrade to Zulip Standard, ${} x {}".format(price_per_license/100, licenses),
            receipt_email=user.email,
            statement_descriptor='Zulip Standard')
        # Not setting a period start and end, but maybe we should? Unclear what will make things
        # most similar to the renewal case from an accounting perspective.
        stripe.InvoiceItem.create(
            amount=price_per_license * licenses * -1,
            currency='usd',
            customer=customer.stripe_customer_id,
            description="Payment (Card ending in {})".format(cast(stripe.Card, stripe_charge.source).last4),
            discountable=False)

    # TODO: The correctness of this relies on user creation, deactivation, etc being
    # in a transaction.atomic() with the relevant RealmAuditLog entries
    with transaction.atomic():
        # billed_licenses can greater than licenses if users are added between the start of
        # this function (process_initial_upgrade) and now
        billed_licenses = max(get_seat_count(realm), licenses)
        plan_params = {
            'automanage_licenses': automanage_licenses,
            'charge_automatically': charge_automatically,
            'price_per_license': price_per_license,
            'discount': customer.default_discount,
            'billing_cycle_anchor': billing_cycle_anchor,
            'billing_schedule': billing_schedule,
            'tier': CustomerPlan.STANDARD}
        plan = CustomerPlan.objects.create(
            customer=customer,
            next_invoice_date=next_invoice_date,
            **plan_params)
        ledger_entry = LicenseLedger.objects.create(
            plan=plan,
            is_renewal=True,
            event_time=billing_cycle_anchor,
            licenses=billed_licenses,
            licenses_at_next_renewal=billed_licenses)
        plan.invoiced_through = ledger_entry
        plan.save(update_fields=['invoiced_through'])
        RealmAuditLog.objects.create(
            realm=realm, acting_user=user, event_time=billing_cycle_anchor,
            event_type=RealmAuditLog.CUSTOMER_PLAN_CREATED,
            extra_data=ujson.dumps(plan_params))
    stripe.InvoiceItem.create(
        currency='usd',
        customer=customer.stripe_customer_id,
        description='Zulip Standard',
        discountable=False,
        period = {'start': datetime_to_timestamp(billing_cycle_anchor),
                  'end': datetime_to_timestamp(period_end)},
        quantity=billed_licenses,
        unit_amount=price_per_license)

    if 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=customer.stripe_customer_id,
        days_until_due=days_until_due,
        statement_descriptor='Zulip Standard')
    stripe.Invoice.finalize_invoice(stripe_invoice)

    from zerver.lib.actions import do_change_plan_type
    do_change_plan_type(realm, Realm.STANDARD)