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)
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
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
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()
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()
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)))
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)
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)
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)
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")
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)
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)
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