Exemple #1
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
Exemple #2
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()
Exemple #3
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()
Exemple #4
0
def upgrade(
    request: HttpRequest,
    user: UserProfile,
    billing_modality: str = REQ(validator=check_string),
    schedule: str = REQ(validator=check_string),
    license_management: Optional[str] = REQ(validator=check_string,
                                            default=None),
    licenses: Optional[int] = REQ(validator=check_int, default=None),
    stripe_token: Optional[str] = REQ(validator=check_string, default=None),
    signed_seat_count: str = REQ(validator=check_string),
    salt: str = REQ(validator=check_string)
) -> 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.description,
                user.id,
                user.realm.id,
                user.realm.string_id,
                billing_modality,
                schedule,
                license_management,
                licenses,
                stripe_token is not None,
            )
        return json_error(e.message, data={'error_description': e.description})
    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"
        return json_error(error_message,
                          data={'error_description': error_description})
    else:
        return json_success()
Exemple #5
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
Exemple #6
0
def handle_checkout_session_completed_event(
        stripe_session: stripe.checkout.Session, session: Session) -> None:
    session.status = Session.COMPLETED
    session.save()

    stripe_setup_intent = stripe.SetupIntent.retrieve(
        stripe_session.setup_intent)
    assert session.customer.realm is not None
    user_id = stripe_session.metadata.get("user_id")
    assert user_id is not None
    user = get_active_user_profile_by_id_in_realm(user_id,
                                                  session.customer.realm)
    payment_method = stripe_setup_intent.payment_method

    if session.type in [
            Session.UPGRADE_FROM_BILLING_PAGE,
            Session.RETRY_UPGRADE_WITH_ANOTHER_PAYMENT_METHOD,
    ]:
        ensure_realm_does_not_have_active_plan(user.realm)
        update_or_create_stripe_customer(user, payment_method)
        assert session.payment_intent is not None
        session.payment_intent.status = PaymentIntent.PROCESSING
        session.payment_intent.last_payment_error = ()
        session.payment_intent.save(
            update_fields=["status", "last_payment_error"])
        try:
            stripe.PaymentIntent.confirm(
                session.payment_intent.stripe_payment_intent_id,
                payment_method=payment_method,
                off_session=True,
            )
        except stripe.error.CardError:
            pass
    elif session.type in [
            Session.FREE_TRIAL_UPGRADE_FROM_BILLING_PAGE,
            Session.FREE_TRIAL_UPGRADE_FROM_ONBOARDING_PAGE,
    ]:
        ensure_realm_does_not_have_active_plan(user.realm)
        update_or_create_stripe_customer(user, payment_method)
        process_initial_upgrade(
            user,
            int(stripe_setup_intent.metadata["licenses"]),
            stripe_setup_intent.metadata["license_management"] == "automatic",
            int(stripe_setup_intent.metadata["billing_schedule"]),
            charge_automatically=True,
            free_trial=True,
        )
    elif session.type in [Session.CARD_UPDATE_FROM_BILLING_PAGE]:
        update_or_create_stripe_customer(user, payment_method)
Exemple #7
0
def upgrade(
    request: HttpRequest,
    user: UserProfile,
    billing_modality: str = REQ(validator=check_string),
    schedule: str = REQ(validator=check_string),
    license_management: str = REQ(validator=check_string, default=None),
    licenses: int = REQ(validator=check_int, default=None),
    stripe_token: str = REQ(validator=check_string, default=None),
    signed_seat_count: str = REQ(validator=check_string),
    salt: str = REQ(validator=check_string)
) -> 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)
        automanage_licenses = license_management in ['automatic', 'mix']

        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.info((
                "BillingError during upgrade: %s. user=%s, billing_modality=%s, schedule=%s, "
                "license_management=%s, licenses=%s, has stripe_token: %s") %
                                (e.description, user.id, billing_modality,
                                 schedule, license_management, licenses,
                                 stripe_token is not None))
        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()
Exemple #8
0
def handle_payment_intent_succeeded_event(
        stripe_payment_intent: stripe.PaymentIntent,
        payment_intent: PaymentIntent) -> None:
    payment_intent.status = PaymentIntent.SUCCEEDED
    payment_intent.save()
    metadata: Dict[str, Any] = stripe_payment_intent.metadata
    assert payment_intent.customer.realm is not None
    user_id = metadata.get("user_id")
    assert user_id is not None
    user = get_active_user_profile_by_id_in_realm(
        user_id, payment_intent.customer.realm)

    description = ""
    for charge in stripe_payment_intent.charges:
        description = f"Payment (Card ending in {charge.payment_method_details.card.last4})"
        break

    stripe.InvoiceItem.create(
        amount=stripe_payment_intent.amount * -1,
        currency="usd",
        customer=stripe_payment_intent.customer,
        description=description,
        discountable=False,
    )
    try:
        ensure_realm_does_not_have_active_plan(user.realm)
    except UpgradeWithExistingPlanError as e:
        stripe_invoice = stripe.Invoice.create(
            auto_advance=True,
            collection_method="charge_automatically",
            customer=stripe_payment_intent.customer,
            days_until_due=None,
            statement_descriptor="Zulip Cloud Standard Credit",
        )
        stripe.Invoice.finalize_invoice(stripe_invoice)
        raise e

    process_initial_upgrade(
        user,
        int(metadata["licenses"]),
        metadata["license_management"] == "automatic",
        int(metadata["billing_schedule"]),
        True,
        False,
    )
Exemple #9
0
def upgrade(request: HttpRequest, user: UserProfile,
            billing_modality: str=REQ(validator=check_string),
            schedule: str=REQ(validator=check_string),
            license_management: str=REQ(validator=check_string, default=None),
            licenses: int=REQ(validator=check_int, default=None),
            stripe_token: str=REQ(validator=check_string, default=None),
            signed_seat_count: str=REQ(validator=check_string),
            salt: str=REQ(validator=check_string)) -> 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)
        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.description, user.id, user.realm.id, user.realm.string_id, billing_modality,
                   schedule, license_management, licenses, stripe_token is not None))
        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()
Exemple #10
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)
Exemple #11
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