Fix not charging existing relays on reactivation
Docker / build-and-push-image (push) Successful in 50m58s

This commit is contained in:
Jon Staab
2026-06-03 11:29:24 -07:00
parent ffb1491f00
commit a9f66dc3e5
3 changed files with 15 additions and 10 deletions
+6 -4
View File
@@ -89,13 +89,15 @@ impl Billing {
) -> Result<()> { ) -> Result<()> {
let mut tenant = tenant.clone(); let mut tenant = tenant.clone();
let activities = query::list_billable_activity(&tenant.pubkey).await?; let mut activities = query::list_billable_activity(&tenant.pubkey).await?;
// A churned tenant with fresh billable activity is using the service // A churned tenant with fresh billable activity is using the service
// again: re-activate billing (and restore their relays) before billing it. // again: re-activate billing (and restore their relays) before billing it.
// Reactivation records a billable activity for each restored relay; fold
// those into this pass so their prorated charges land on the same invoice.
if tenant.churned_at.is_some() && !activities.is_empty() { if tenant.churned_at.is_some() && !activities.is_empty() {
let relays = query::list_relays_for_tenant(&tenant.pubkey).await?; let relays = query::list_relays_for_tenant(&tenant.pubkey).await?;
command::reactivate_tenant(&tenant.pubkey, &relays).await?; activities.extend(command::reactivate_tenant(&tenant.pubkey, &relays).await?);
tenant.churned_at = None; tenant.churned_at = None;
} }
@@ -154,12 +156,12 @@ impl Billing {
self.make_prorated_item(tenant, activity, 1, "New relay created") self.make_prorated_item(tenant, activity, 1, "New relay created")
.await? .await?
} }
"activate_relay" => { "activate_relay" | "unmark_relay_delinquent" => {
self.make_prorated_item(tenant, activity, 1, "Relay reactivated") self.make_prorated_item(tenant, activity, 1, "Relay reactivated")
.await? .await?
} }
"deactivate_relay" => { "deactivate_relay" => {
self.make_prorated_item(tenant, activity, -1, "Relay deactivated (prorated credit)") self.make_prorated_item(tenant, activity, -1, "Relay deactivated")
.await? .await?
} }
"update_relay" => self.make_plan_change_item(tenant, activity).await?, "update_relay" => self.make_plan_change_item(tenant, activity).await?,
+7 -5
View File
@@ -114,8 +114,10 @@ pub async fn churn_tenant(tenant_pubkey: &str, now: i64, relays: &[Relay]) -> Re
} }
/// Atomically re-activate a churned tenant: clear the churn marker, restore every /// Atomically re-activate a churned tenant: clear the churn marker, restore every
/// delinquent relay to active, and void any still-open invoices. /// delinquent relay to active, and void any still-open invoices. Returns the
pub async fn reactivate_tenant(tenant_pubkey: &str, relays: &[Relay]) -> Result<()> { /// `unmark_relay_delinquent` activities recorded for the restored relays, so the
/// caller can fold their prorated charges into the same reconcile pass.
pub async fn reactivate_tenant(tenant_pubkey: &str, relays: &[Relay]) -> Result<Vec<Activity>> {
let activities = with_tx(async |tx| { let activities = with_tx(async |tx| {
set_tenant_churned_at_tx(tx, tenant_pubkey, None).await?; set_tenant_churned_at_tx(tx, tenant_pubkey, None).await?;
@@ -135,10 +137,10 @@ pub async fn reactivate_tenant(tenant_pubkey: &str, relays: &[Relay]) -> Result<
}) })
.await?; .await?;
for activity in activities { for activity in &activities {
publish(activity); publish(activity.clone());
} }
Ok(()) Ok(activities)
} }
// --- Relays --- // --- Relays ---
+2 -1
View File
@@ -233,7 +233,8 @@ pub async fn list_billable_activity(tenant_pubkey: &str) -> Result<Vec<Activity>
"WHERE tenant_pubkey = ? "WHERE tenant_pubkey = ?
AND billed_at IS NULL AND billed_at IS NULL
AND activity_type IN ( AND activity_type IN (
'create_relay', 'update_relay', 'activate_relay', 'deactivate_relay' 'create_relay', 'update_relay', 'activate_relay', 'deactivate_relay',
'unmark_relay_delinquent'
) )
ORDER BY created_at ASC", ORDER BY created_at ASC",
)) ))