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