Add overdue invoices check (#31290)

- Rename current_user_account_too_young to account_too_young for
consistency
- Add has_overdue_invoices field to track billing status
- Block edit predictions when user has overdue invoices
- Add overdue invoice warning to inline completion menu

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
Ben Brandt 2025-05-23 22:02:02 +02:00 committed by GitHub
parent cb112a4012
commit ca72efe701
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 65 additions and 7 deletions

View file

@ -1973,7 +1973,7 @@ impl AgentPanel {
return None;
}
if self.user_store.read(cx).current_user_account_too_young() {
if self.user_store.read(cx).account_too_young() {
Some(self.render_young_account_upsell(cx).into_any_element())
} else {
Some(self.render_trial_upsell(cx).into_any_element())

View file

@ -109,6 +109,7 @@ pub struct UserStore {
edit_predictions_usage_limit: Option<proto::UsageLimit>,
is_usage_based_billing_enabled: Option<bool>,
account_too_young: Option<bool>,
has_overdue_invoices: Option<bool>,
current_user: watch::Receiver<Option<Arc<User>>>,
accepted_tos_at: Option<Option<DateTime<Utc>>>,
contacts: Vec<Arc<Contact>>,
@ -176,6 +177,7 @@ impl UserStore {
edit_predictions_usage_limit: None,
is_usage_based_billing_enabled: None,
account_too_young: None,
has_overdue_invoices: None,
accepted_tos_at: None,
contacts: Default::default(),
incoming_contact_requests: Default::default(),
@ -350,6 +352,7 @@ impl UserStore {
.and_then(|trial_started_at| DateTime::from_timestamp(trial_started_at as i64, 0));
this.is_usage_based_billing_enabled = message.payload.is_usage_based_billing_enabled;
this.account_too_young = message.payload.account_too_young;
this.has_overdue_invoices = message.payload.has_overdue_invoices;
if let Some(usage) = message.payload.usage {
this.model_request_usage_amount = Some(usage.model_requests_usage_amount);
@ -755,11 +758,16 @@ impl UserStore {
self.current_user.clone()
}
/// Check if the current user's account is too new to use the service
pub fn current_user_account_too_young(&self) -> bool {
/// Returns whether the user's account is too new to use the service.
pub fn account_too_young(&self) -> bool {
self.account_too_young.unwrap_or(false)
}
/// Returns whether the current user has overdue invoices and usage should be blocked.
pub fn has_overdue_invoices(&self) -> bool {
self.has_overdue_invoices.unwrap_or(false)
}
pub fn current_user_has_accepted_terms(&self) -> Option<bool> {
self.accepted_tos_at
.map(|accepted_tos_at| accepted_tos_at.is_some())

View file

@ -1,5 +1,5 @@
use crate::db::billing_subscription::SubscriptionKind;
use crate::db::{billing_subscription, user};
use crate::db::{billing_customer, billing_subscription, user};
use crate::llm::AGENT_EXTENDED_TRIAL_FEATURE_FLAG;
use crate::{Config, db::billing_preference};
use anyhow::{Context as _, Result};
@ -32,6 +32,8 @@ pub struct LlmTokenClaims {
pub enable_model_request_overages: bool,
pub model_request_overages_spend_limit_in_cents: u32,
pub can_use_web_search_tool: bool,
#[serde(default)]
pub has_overdue_invoices: bool,
}
const LLM_TOKEN_LIFETIME: Duration = Duration::from_secs(60 * 60);
@ -40,6 +42,7 @@ impl LlmTokenClaims {
pub fn create(
user: &user::Model,
is_staff: bool,
billing_customer: billing_customer::Model,
billing_preferences: Option<billing_preference::Model>,
feature_flags: &Vec<String>,
subscription: billing_subscription::Model,
@ -99,6 +102,7 @@ impl LlmTokenClaims {
.map_or(0, |preferences| {
preferences.model_request_overages_spend_limit_in_cents as u32
}),
has_overdue_invoices: billing_customer.has_overdue_invoices,
};
Ok(jsonwebtoken::encode(

View file

@ -2748,6 +2748,7 @@ async fn make_update_user_plan_message(
Ok(proto::UpdateUserPlan {
plan: plan.into(),
trial_started_at: billing_customer
.as_ref()
.and_then(|billing_customer| billing_customer.trial_started_at)
.map(|trial_started_at| trial_started_at.and_utc().timestamp() as u64),
is_usage_based_billing_enabled: if is_staff {
@ -2762,6 +2763,8 @@ async fn make_update_user_plan_message(
}
}),
account_too_young: Some(account_too_young),
has_overdue_invoices: billing_customer
.map(|billing_customer| billing_customer.has_overdue_invoices),
usage: usage.map(|usage| {
let plan = match plan {
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
@ -4077,6 +4080,7 @@ async fn get_llm_api_token(
let token = LlmTokenClaims::create(
&user,
session.is_staff(),
billing_customer,
billing_preferences,
&flags,
billing_subscription,

View file

@ -745,7 +745,7 @@ impl InlineCompletionButton {
})
})
.separator();
} else if self.user_store.read(cx).current_user_account_too_young() {
} else if self.user_store.read(cx).account_too_young() {
menu = menu
.custom_entry(
|_window, _cx| {
@ -785,6 +785,46 @@ impl InlineCompletionButton {
},
)
.separator();
} else if self.user_store.read(cx).has_overdue_invoices() {
menu = menu
.custom_entry(
|_window, _cx| {
h_flex()
.gap_1()
.child(
Icon::new(IconName::Warning)
.size(IconSize::Small)
.color(Color::Warning),
)
.child(
Label::new("You have an outstanding invoice")
.size(LabelSize::Small)
.color(Color::Warning),
)
.into_any_element()
},
|window, cx| {
window.dispatch_action(
Box::new(OpenZedUrl {
url: zed_urls::account_url(cx),
}),
cx,
);
},
)
.entry(
"Check your payment status or contact us at billing-support@zed.dev to continue using this feature.",
None,
|window, cx| {
window.dispatch_action(
Box::new(OpenZedUrl {
url: zed_urls::account_url(cx),
}),
cx,
);
},
)
.separator();
}
self.build_language_settings_menu(menu, window, cx).when(

View file

@ -28,6 +28,7 @@ message UpdateUserPlan {
optional SubscriptionUsage usage = 4;
optional SubscriptionPeriod subscription_period = 5;
optional bool account_too_young = 6;
optional bool has_overdue_invoices = 7;
}
message SubscriptionPeriod {

View file

@ -1578,8 +1578,9 @@ impl inline_completion::EditPredictionProvider for ZetaInlineCompletionProvider
.zeta
.read(cx)
.user_store
.read(cx)
.current_user_account_too_young()
.read_with(cx, |user_store, _| {
user_store.account_too_young() || user_store.has_overdue_invoices()
})
{
return;
}