Track payment method

This commit is contained in:
Jon Staab
2026-05-29 12:24:39 -07:00
parent d5047dedb1
commit ae3e1c316e
8 changed files with 14 additions and 7 deletions
+1
View File
@@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS tenant (
created_at INTEGER NOT NULL,
billing_anchor INTEGER,
stripe_customer_id TEXT NOT NULL,
stripe_payment_method_id TEXT,
renewed_at INTEGER,
churned_at INTEGER
);
+3 -3
View File
@@ -233,7 +233,7 @@ impl Billing {
/// a relay created/activated *within* the period isn't active before the
/// boundary, so it's covered by its own prorated charge instead.
async fn reconcile_renewal(&self, tenant: &Tenant, period: &BillingPeriod) -> Result<()> {
let relays = query::list_relays(&tenant.pubkey).await?;
let relays = query::list_relays_for_tenant(&tenant.pubkey).await?;
let mut line_items = Vec::new();
for relay in relays {
@@ -419,7 +419,7 @@ impl Billing {
async fn churn_tenant(&self, tenant: &Tenant, now: i64) -> Result<()> {
command::set_tenant_churned_at(&tenant.pubkey, Some(now)).await?;
for relay in query::list_relays(&tenant.pubkey).await? {
for relay in query::list_relays_for_tenant(&tenant.pubkey).await? {
if relay.status == RELAY_STATUS_ACTIVE {
command::mark_relay_delinquent(&relay).await?;
}
@@ -434,7 +434,7 @@ impl Billing {
async fn reactivate_tenant(&self, tenant: &Tenant) -> Result<()> {
command::set_tenant_churned_at(&tenant.pubkey, None).await?;
for relay in query::list_relays(&tenant.pubkey).await? {
for relay in query::list_relays_for_tenant(&tenant.pubkey).await? {
if relay.status == RELAY_STATUS_DELINQUENT {
command::unmark_relay_delinquent(&relay).await?;
}
+3
View File
@@ -46,6 +46,9 @@ pub struct Tenant {
pub created_at: i64,
pub billing_anchor: Option<i64>,
pub stripe_customer_id: String,
/// The tenant's saved Stripe payment method, or `None` if they have not set
/// up a card yet. Set when the tenant adds a card via the Stripe portal.
pub stripe_payment_method_id: Option<String>,
/// `period_start` of the most recent period this tenant was renewed for, or
/// `None` if never renewed. The per-period renewal idempotency marker.
pub renewed_at: Option<i64>,
+1 -1
View File
@@ -84,7 +84,7 @@ pub async fn list_relays_pending_sync() -> Result<Vec<Relay>> {
)
}
pub async fn list_relays(tenant_pubkey: &str) -> Result<Vec<Relay>> {
pub async fn list_relays_for_tenant(tenant_pubkey: &str) -> Result<Vec<Relay>> {
Ok(sqlx::query_as::<_, Relay>(&select_relay("WHERE tenant_pubkey = ?"))
.bind(tenant_pubkey)
.fetch_all(pool())
+3 -1
View File
@@ -22,6 +22,7 @@ pub struct TenantResponse {
pub created_at: i64,
pub billing_anchor: Option<i64>,
pub stripe_customer_id: String,
pub stripe_payment_method_id: Option<String>,
/// Set when billing has churned the tenant; the UI uses it to warn that the
/// account is delinquent until billing is re-activated.
pub churned_at: Option<i64>,
@@ -37,6 +38,7 @@ impl From<Tenant> for TenantResponse {
created_at: t.created_at,
billing_anchor: t.billing_anchor,
stripe_customer_id: t.stripe_customer_id,
stripe_payment_method_id: t.stripe_payment_method_id,
churned_at: t.churned_at,
}
}
@@ -142,7 +144,7 @@ pub async fn list_tenant_relays(
) -> ApiResult {
api.require_admin_or_tenant(&auth, &pubkey)?;
let relays = query::list_relays(&pubkey)
let relays = query::list_relays_for_tenant(&pubkey)
.await
.map_err(internal)?;
ok(relays)
+1
View File
@@ -98,6 +98,7 @@ export type Tenant = {
nwc_is_set: boolean
created_at: number
stripe_customer_id: string
stripe_payment_method_id: string | null
nwc_error: string | null
stripe_error: string | null
churned_at: number | null
+1 -1
View File
@@ -135,7 +135,7 @@ export const reactivateRelayById = (id: string) => reactivateRelay(id)
export async function tenantNeedsPaymentSetup(): Promise<boolean> {
const tenant = await getTenant(account()!.pubkey)
return !tenant.nwc_is_set
return !tenant.nwc_is_set && !tenant.stripe_payment_method_id
}
export async function getLatestOpenInvoice(): Promise<Invoice | null> {
+1 -1
View File
@@ -59,7 +59,7 @@ export default function RelayDetail() {
if (!isPaidRelay()) return false
const t = tenant()
if (!t) return false
return !t.nwc_is_set
return !t.nwc_is_set && !t.stripe_payment_method_id
})
return (