Exemplo n.º 1
0
def check_upgrade_parameters(
    billing_modality: str,
    schedule: str,
    license_management: Optional[str],
    licenses: Optional[int],
    seat_count: int,
) -> None:
    if billing_modality not in VALID_BILLING_MODALITY_VALUES:  # nocoverage
        raise BillingError("unknown billing_modality", "")
    if schedule not in VALID_BILLING_SCHEDULE_VALUES:  # nocoverage
        raise BillingError("unknown schedule")
    if license_management not in VALID_LICENSE_MANAGEMENT_VALUES:  # nocoverage
        raise BillingError("unknown license_management")
    validate_licenses(billing_modality == "charge_automatically", licenses, seat_count)
Exemplo n.º 2
0
def unsign_and_check_upgrade_parameters(user: UserProfile, plan_nickname: str,
                                        signed_seat_count: str, salt: str) -> Tuple[Plan, int]:
    if plan_nickname not in [Plan.CLOUD_ANNUAL, Plan.CLOUD_MONTHLY]:
        billing_logger.warning("Tampered plan during realm upgrade. user: %s, realm: %s (%s)."
                               % (user.id, user.realm.id, user.realm.string_id))
        raise BillingError('tampered plan', BillingError.CONTACT_SUPPORT)
    plan = Plan.objects.get(nickname=plan_nickname)

    try:
        seat_count = int(unsign_string(signed_seat_count, salt))
    except signing.BadSignature:
        billing_logger.warning("Tampered seat count during realm upgrade. user: %s, realm: %s (%s)."
                               % (user.id, user.realm.id, user.realm.string_id))
        raise BillingError('tampered seat count', BillingError.CONTACT_SUPPORT)
    return plan, seat_count
Exemplo n.º 3
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    if not settings.BILLING_ENABLED:
        return render(request, "404.html")

    user = request.user
    error_message = ""
    error_description = ""  # only used in tests

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

    if request.method == 'POST':
        try:
            plan, seat_count = unsign_and_check_upgrade_parameters(
                user, request.POST['plan'], request.POST['signed_seat_count'],
                request.POST['salt'])
            if 'invoiced_seat_count' in request.POST:
                min_required_seat_count = max(seat_count,
                                              MIN_INVOICED_SEAT_COUNT)
                if int(request.POST['invoiced_seat_count']
                       ) < min_required_seat_count:
                    raise BillingError(
                        'lowball seat count',
                        "You must invoice for at least %d users." %
                        (min_required_seat_count, ))
                seat_count = int(request.POST['invoiced_seat_count'])
            process_initial_upgrade(user, plan, seat_count,
                                    request.POST.get('stripeToken', None))
        except BillingError as e:
            error_message = e.message
            error_description = e.description
        except Exception as e:
            billing_logger.exception("Uncaught exception in billing: %s" %
                                     (e, ))
            error_message = BillingError.CONTACT_SUPPORT
            error_description = "uncaught exception during upgrade"
        else:
            return HttpResponseRedirect(
                reverse('corporate.views.billing_home'))

    seat_count = get_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context = {
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'email': user.email,
        'seat_count': seat_count,
        'signed_seat_count': signed_seat_count,
        'salt': salt,
        'plan': "Zulip Standard",
        'nickname_monthly': Plan.CLOUD_MONTHLY,
        'nickname_annual': Plan.CLOUD_ANNUAL,
        'error_message': error_message,
        'cloud_monthly_price': 8,
        'cloud_annual_price': 80,
        'cloud_annual_price_per_month': 6.67,
    }  # type: Dict[str, Any]
    response = render(request, 'corporate/upgrade.html', context=context)
    response['error_description'] = error_description
    return response
Exemplo n.º 4
0
def upgrade(
    request: HttpRequest,
    user: UserProfile,
    plan: str = REQ(validator=check_string),
    signed_seat_count: str = REQ(validator=check_string),
    salt: str = REQ(validator=check_string),
    billing_modality: str = REQ(validator=check_string),
    invoiced_seat_count: int = REQ(validator=check_int, default=-1),
    stripe_token: str = REQ(validator=check_string, default=None)
) -> HttpResponse:
    try:
        plan, seat_count = unsign_and_check_upgrade_parameters(
            user, plan, signed_seat_count, salt, billing_modality)
        if billing_modality == 'send_invoice':
            min_required_seat_count = max(seat_count, MIN_INVOICED_SEAT_COUNT)
            if invoiced_seat_count < min_required_seat_count:
                raise BillingError(
                    'lowball seat count',
                    "You must invoice for at least %d users." %
                    (min_required_seat_count, ))
            seat_count = invoiced_seat_count
        process_initial_upgrade(user, plan, seat_count, stripe_token)
    except BillingError as e:
        return json_error(e.message, data={'error_description': e.description})
    except Exception as e:
        billing_logger.exception("Uncaught exception in billing: %s" % (e, ))
        error_message = BillingError.CONTACT_SUPPORT
        error_description = "uncaught exception during upgrade"
        return json_error(error_message,
                          data={'error_description': error_description})
    else:
        return json_success()
Exemplo n.º 5
0
def upgrade(
    request: HttpRequest,
    user: UserProfile,
    billing_modality: str = REQ(str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)),
    schedule: str = REQ(str_validator=check_string_in(VALID_BILLING_SCHEDULE_VALUES)),
    signed_seat_count: str = REQ(),
    salt: str = REQ(),
    license_management: Optional[str] = REQ(
        default=None, str_validator=check_string_in(VALID_LICENSE_MANAGEMENT_VALUES)
    ),
    licenses: Optional[int] = REQ(json_validator=check_int, default=None),
    stripe_token: Optional[str] = REQ(default=None),
) -> HttpResponse:

    try:
        seat_count = unsign_seat_count(signed_seat_count, salt)
        if billing_modality == "charge_automatically" and license_management == "automatic":
            licenses = seat_count
        if billing_modality == "send_invoice":
            schedule = "annual"
            license_management = "manual"
        check_upgrade_parameters(
            billing_modality,
            schedule,
            license_management,
            licenses,
            stripe_token is not None,
            seat_count,
        )
        assert licenses is not None
        automanage_licenses = license_management == "automatic"

        billing_schedule = {"annual": CustomerPlan.ANNUAL, "monthly": CustomerPlan.MONTHLY}[
            schedule
        ]
        process_initial_upgrade(user, licenses, automanage_licenses, billing_schedule, stripe_token)
    except BillingError as e:
        if not settings.TEST_SUITE:  # nocoverage
            billing_logger.warning(
                "BillingError during upgrade: %s. user=%s, realm=%s (%s), billing_modality=%s, "
                "schedule=%s, license_management=%s, licenses=%s, has stripe_token: %s",
                e.error_description,
                user.id,
                user.realm.id,
                user.realm.string_id,
                billing_modality,
                schedule,
                license_management,
                licenses,
                stripe_token is not None,
            )
        raise
    except Exception:
        billing_logger.exception("Uncaught exception in billing:", stack_info=True)
        error_message = BillingError.CONTACT_SUPPORT.format(email=settings.ZULIP_ADMINISTRATOR)
        error_description = "uncaught exception during upgrade"
        raise BillingError(error_description, error_message)
    else:
        return json_success()
Exemplo n.º 6
0
def check_upgrade_parameters(
        billing_modality: str, schedule: str, license_management: str, licenses: int,
        has_stripe_token: bool, seat_count: int) -> None:
    if billing_modality not in ['send_invoice', 'charge_automatically']:
        raise BillingError('unknown billing_modality')
    if schedule not in ['annual', 'monthly']:
        raise BillingError('unknown schedule')
    if license_management not in ['automatic', 'manual', 'mix']:
        raise BillingError('unknown license_management')

    if billing_modality == 'charge_automatically':
        if not has_stripe_token:
            raise BillingError('autopay with no card')

    min_licenses = seat_count
    if billing_modality == 'send_invoice':
        min_licenses = max(seat_count, MIN_INVOICED_LICENSES)
    if licenses is None or licenses < min_licenses:
        raise BillingError('not enough licenses',
                           _("You must invoice for at least {} users.".format(min_licenses)))
Exemplo n.º 7
0
def check_upgrade_parameters(billing_modality: str, schedule: str,
                             license_management: Optional[str],
                             licenses: Optional[int], has_stripe_token: bool,
                             seat_count: int) -> None:
    if billing_modality not in ['send_invoice', 'charge_automatically']:
        raise BillingError('unknown billing_modality')
    if schedule not in ['annual', 'monthly']:
        raise BillingError('unknown schedule')
    if license_management not in ['automatic', 'manual']:
        raise BillingError('unknown license_management')

    if billing_modality == 'charge_automatically':
        if not has_stripe_token:
            raise BillingError('autopay with no card')

    min_licenses = seat_count
    max_licenses = None
    if billing_modality == 'send_invoice':
        min_licenses = max(seat_count, MIN_INVOICED_LICENSES)
        max_licenses = MAX_INVOICED_LICENSES

    if licenses is None or licenses < min_licenses:
        raise BillingError(
            'not enough licenses',
            _("You must invoice for at least {} users.").format(min_licenses))

    if max_licenses is not None and licenses > max_licenses:
        message = _(
            "Invoices with more than {} licenses can't be processed from this page. To complete "
            "the upgrade, please contact {}.").format(
                max_licenses, settings.ZULIP_ADMINISTRATOR)
        raise BillingError('too many licenses', message)
Exemplo n.º 8
0
def check_upgrade_parameters(
    billing_modality: str,
    schedule: str,
    license_management: Optional[str],
    licenses: Optional[int],
    has_stripe_token: bool,
    seat_count: int,
) -> None:
    if billing_modality not in VALID_BILLING_MODALITY_VALUES:  # nocoverage
        raise BillingError("unknown billing_modality")
    if schedule not in VALID_BILLING_SCHEDULE_VALUES:  # nocoverage
        raise BillingError("unknown schedule")
    if license_management not in VALID_LICENSE_MANAGEMENT_VALUES:  # nocoverage
        raise BillingError("unknown license_management")

    charge_automatically = False
    if billing_modality == "charge_automatically":
        charge_automatically = True
        if not has_stripe_token:
            raise BillingError("autopay with no card")

    validate_licenses(charge_automatically, licenses, seat_count)
Exemplo n.º 9
0
def check_upgrade_parameters(
    billing_modality: str,
    schedule: str,
    license_management: Optional[str],
    licenses: Optional[int],
    has_stripe_token: bool,
    seat_count: int,
) -> None:
    if billing_modality not in VALID_BILLING_MODALITY_VALUES:  # nocoverage
        raise BillingError("unknown billing_modality")
    if schedule not in VALID_BILLING_SCHEDULE_VALUES:  # nocoverage
        raise BillingError("unknown schedule")
    if license_management not in VALID_LICENSE_MANAGEMENT_VALUES:  # nocoverage
        raise BillingError("unknown license_management")

    if billing_modality == "charge_automatically":
        if not has_stripe_token:
            raise BillingError("autopay with no card")

    min_licenses = seat_count
    max_licenses = None
    if billing_modality == "send_invoice":
        min_licenses = max(seat_count, MIN_INVOICED_LICENSES)
        max_licenses = MAX_INVOICED_LICENSES

    if licenses is None or licenses < min_licenses:
        raise BillingError(
            "not enough licenses",
            _("You must invoice for at least {} users.").format(min_licenses))

    if max_licenses is not None and licenses > max_licenses:
        message = _(
            "Invoices with more than {} licenses can't be processed from this page. To complete "
            "the upgrade, please contact {}.").format(
                max_licenses, settings.ZULIP_ADMINISTRATOR)
        raise BillingError("too many licenses", message)
Exemplo n.º 10
0
def unsign_seat_count(signed_seat_count: str, salt: str) -> int:
    try:
        return int(unsign_string(signed_seat_count, salt))
    except signing.BadSignature:
        raise BillingError("tampered seat count")
Exemplo n.º 11
0
def sponsorship(
        request: HttpRequest,
        user: UserProfile,
        organization_type: str = REQ("organization-type"),
        website: str = REQ(),
        description: str = REQ(),
) -> HttpResponse:
    realm = user.realm

    requested_by = user.full_name
    user_role = user.get_role_name()
    support_url = get_support_url(realm)

    post_data = request.POST.copy()
    # We need to do this because the field name in the template
    # for organization type contains a hyphen and the form expects
    # an underscore.
    post_data.update(organization_type=organization_type)
    form = SponsorshipRequestForm(post_data)

    if form.is_valid():
        with transaction.atomic():
            sponsorship_request = ZulipSponsorshipRequest(
                realm=realm,
                requested_by=user,
                org_website=form.cleaned_data["website"],
                org_description=form.cleaned_data["description"],
                org_type=form.cleaned_data["organization_type"],
            )
            sponsorship_request.save()

            org_type = form.cleaned_data["organization_type"]
            if realm.org_type != org_type:
                realm.org_type = org_type
                realm.save(update_fields=["org_type"])

            update_sponsorship_status(realm, True, acting_user=user)
            do_make_user_billing_admin(user)

            org_type_display_name = get_org_type_display_name(org_type)

        context = {
            "requested_by": requested_by,
            "user_role": user_role,
            "string_id": realm.string_id,
            "support_url": support_url,
            "organization_type": org_type_display_name,
            "website": website,
            "description": description,
        }
        send_email(
            "zerver/emails/sponsorship_request",
            to_emails=[FromAddress.SUPPORT],
            from_name="Zulip sponsorship",
            from_address=FromAddress.tokenized_no_reply_address(),
            reply_to_email=user.delivery_email,
            context=context,
        )

        return json_success(request)
    else:
        messages = []
        for error_list in form.errors.get_json_data().values():
            for error in error_list:
                messages.append(error["message"])
        message = " ".join(messages)
        raise BillingError("Form validation error", message=message)
Exemplo n.º 12
0
def upgrade(
    request: HttpRequest,
    user: UserProfile,
    billing_modality: str = REQ(
        str_validator=check_string_in(VALID_BILLING_MODALITY_VALUES)),
    schedule: str = REQ(
        str_validator=check_string_in(VALID_BILLING_SCHEDULE_VALUES)),
    signed_seat_count: str = REQ(),
    salt: str = REQ(),
    onboarding: bool = REQ(default=False, json_validator=check_bool),
    license_management: Optional[str] = REQ(
        default=None,
        str_validator=check_string_in(VALID_LICENSE_MANAGEMENT_VALUES)),
    licenses: Optional[int] = REQ(json_validator=check_int, default=None),
) -> HttpResponse:
    ensure_realm_does_not_have_active_plan(user.realm)
    try:
        seat_count = unsign_seat_count(signed_seat_count, salt)
        if billing_modality == "charge_automatically" and license_management == "automatic":
            licenses = seat_count
        if billing_modality == "send_invoice":
            schedule = "annual"
            license_management = "manual"
        check_upgrade_parameters(billing_modality, schedule,
                                 license_management, licenses, seat_count)
        assert licenses is not None and license_management is not None
        automanage_licenses = license_management == "automatic"
        charge_automatically = billing_modality == "charge_automatically"

        billing_schedule = {
            "annual": CustomerPlan.ANNUAL,
            "monthly": CustomerPlan.MONTHLY
        }[schedule]
        if charge_automatically:
            stripe_checkout_session = setup_upgrade_checkout_session_and_payment_intent(
                user,
                seat_count,
                licenses,
                license_management,
                billing_schedule,
                billing_modality,
                onboarding,
            )
            return json_success(
                request,
                data={
                    "stripe_session_url": stripe_checkout_session.url,
                    "stripe_session_id": stripe_checkout_session.id,
                },
            )
        else:
            process_initial_upgrade(
                user,
                licenses,
                automanage_licenses,
                billing_schedule,
                False,
                is_free_trial_offer_enabled(),
            )
            return json_success(request)

    except BillingError as e:
        billing_logger.warning(
            "BillingError during upgrade: %s. user=%s, realm=%s (%s), billing_modality=%s, "
            "schedule=%s, license_management=%s, licenses=%s",
            e.error_description,
            user.id,
            user.realm.id,
            user.realm.string_id,
            billing_modality,
            schedule,
            license_management,
            licenses,
        )
        raise e
    except Exception:
        billing_logger.exception("Uncaught exception in billing:",
                                 stack_info=True)
        error_message = BillingError.CONTACT_SUPPORT.format(
            email=settings.ZULIP_ADMINISTRATOR)
        error_description = "uncaught exception during upgrade"
        raise BillingError(error_description, error_message)
Exemplo n.º 13
0
def initial_upgrade(request: HttpRequest) -> HttpResponse:
    if not settings.BILLING_ENABLED:
        return render(request, "404.html")

    user = request.user
    error_message = ""
    error_description = ""  # only used in tests

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

    percent_off = 0
    if customer is not None:
        stripe_customer = stripe_get_customer(customer.stripe_customer_id)
        if stripe_customer.discount is not None:
            percent_off = stripe_customer.discount.coupon.percent_off

    if request.method == 'POST':
        try:
            plan, seat_count = unsign_and_check_upgrade_parameters(
                user, request.POST['plan'], request.POST['signed_seat_count'], request.POST['salt'],
                request.POST['billing_modality'])
            if request.POST['billing_modality'] == 'send_invoice':
                try:
                    invoiced_seat_count = int(request.POST['invoiced_seat_count'])
                except (KeyError, ValueError):
                    invoiced_seat_count = -1
                min_required_seat_count = max(seat_count, MIN_INVOICED_SEAT_COUNT)
                if invoiced_seat_count < min_required_seat_count:
                    raise BillingError(
                        'lowball seat count',
                        "You must invoice for at least %d users." % (min_required_seat_count,))
                seat_count = invoiced_seat_count
            process_initial_upgrade(user, plan, seat_count, request.POST.get('stripe_token', None))
        except BillingError as e:
            error_message = e.message
            error_description = e.description
        except Exception as e:
            billing_logger.exception("Uncaught exception in billing: %s" % (e,))
            error_message = BillingError.CONTACT_SUPPORT
            error_description = "uncaught exception during upgrade"
        else:
            return HttpResponseRedirect(reverse('corporate.views.billing_home'))

    seat_count = get_seat_count(user.realm)
    signed_seat_count, salt = sign_string(str(seat_count))
    context = {
        'publishable_key': STRIPE_PUBLISHABLE_KEY,
        'email': user.email,
        'seat_count': seat_count,
        'signed_seat_count': signed_seat_count,
        'salt': salt,
        'min_seat_count_for_invoice': max(seat_count, MIN_INVOICED_SEAT_COUNT),
        'default_invoice_days_until_due': DEFAULT_INVOICE_DAYS_UNTIL_DUE,
        'plan': "Zulip Standard",
        'nickname_monthly': Plan.CLOUD_MONTHLY,
        'nickname_annual': Plan.CLOUD_ANNUAL,
        'error_message': error_message,
        'page_params': JSONEncoderForHTML().encode({
            'seat_count': seat_count,
            'nickname_annual': Plan.CLOUD_ANNUAL,
            'nickname_monthly': Plan.CLOUD_MONTHLY,
            'annual_price': 8000,
            'monthly_price': 800,
            'percent_off': percent_off,
        }),
    }  # type: Dict[str, Any]
    response = render(request, 'corporate/upgrade.html', context=context)
    response['error_description'] = error_description
    return response