Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-server/src/plans.rs

//! **ROOM SOLD AS PLANS, PRICED BY THE RULE** (owner, 2026-10-05) — the
//! server half of [`monetize::plan`] and [`monetize::pricing`], for a product
//! with `[pricing.<product>]` in `monetize.toml`.
//!
//! ```text
//!   [pricing.gunnar]                          the rule's inputs and knobs
//!   [pricing.gunnar.plans."10gb-1m"]          size_bytes, period — the offers
//!
//!   Purchase.Bundles ─▶ window()   the rows (start / prolong / upgrade /
//!                                  bump / renew), priced off the CACHED basis,
//!                                  plus the plan's gauge and banner
//!   Purchase.Quote   ─▶ select() → check_actor → quote(fresh = false)
//!   Purchase.Start   ─▶ select() → check_actor → quote(fresh = TRUE): the
//!                                  provider's price read NOW and the floor
//!                                  re-checked; then the ordinary transaction
//! ```
//!
//! # The basis is the provider's, cached and kept warm
//!
//! What the estate costs a month — its servers (with their system disks), its
//! IPv4 addresses, and a GiB on main's data tier plus the twin's replica tier
//! — is read through the cloud seam (`Priced::Resource`) and kept for
//! [`BASIS_FRESH`]; a keeper refreshes it every [`BASIS_KEEP_EVERY`] from the
//! start, so a customer never waits on the provider for a window. Two things
//! the seam cannot price are STATED in the config and say so: the IPv4
//! addresses (`base_extra_month_minor`) and the twin's replica tier
//! (`replica_gib_month_milli`, the seam prices one tier per cloud). A
//! deployment whose cloud prices nothing (the box's own iron, a test) states
//! the whole basis (`[pricing.<p>.basis]`).
//!
//! # N
//!
//! The accounts the base is split among: the distinct tenants who PAID for
//! something in the previous calendar month — ledger sales granted at a
//! price, and paid signups — never fewer than the rule's 20. Counted once a
//! month; every plan sold carries the N it was priced at.
//!
//! # Nothing here buys iron
//!
//! A purchase here raises the cap at once out of the free room, exactly as an
//! order always has; past the room it is queued at the estate that grows its own
//! set (`crate::grow::queue_past_room`).

use std::collections::{BTreeMap, BTreeSet};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};

use monetize::order::Period;
use monetize::plan::{self, Current, PlanSpec, PlanTerms, Row, Verb, SIZE_METER};
use monetize::pricing::{CostBasis, Fx, PriceRule, GIB};
use monetize::transaction::Refusal;
use monetize::{Quote, TxError};
use monetize_cloud::{Resource, ServerId, ServerRole, Zone};
use monetize_embed::civil::{iso_date, DAY_MS};
use monetize_pay::{Currency, Market, Money};
use monetize_product::{State, TenantId};
use monetize_proto as pb;
use tonic::Status;

use crate::state::AppState;

/// How long a read basis is used for a WINDOW (owner: at least 10 minutes).
/// A purchase never uses it: `Start` reads the provider afresh.
pub const BASIS_FRESH: Duration = Duration::from_secs(10 * 60);
/// The keeper refreshes every basis this often, before it goes stale.
pub const BASIS_KEEP_EVERY: Duration = Duration::from_secs(8 * 60);

/// **`[pricing.<product>]`** — the price rule's inputs for one product.
///
/// ```toml
/// [pricing.gunnar]
/// currency                = "Eur"     # what the estate is paid for in
/// base_extra_month_minor  = 0         # what the seam cannot price (each machine's IPv4 is read from it)
/// ipv4_fallback_month_minor = 351     # one address a month, only when the provider cannot price it
/// replica_gib_month_milli = 5990      # the twin's replica tier, per GiB-month
/// fx                      = { SEK = 110000, DKK = 74600 }  # per 1 EUR, basis points
/// [pricing.gunnar.rule]               # the owner's numbers, when absent
/// [pricing.gunnar.plans."10gb-1m"]
/// size_bytes = 10737418240
/// period     = "month"
/// [pricing.gunnar.plans."10gb-12m"]
/// size_bytes = 10737418240
/// period     = "year"
/// target     = { SEK = 89000 }        # the owner's year; the floor still wins
/// ```
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct PricingConfig {
    /// The currency the estate is paid for in — the cost currency. Every
    /// price is computed in it and converted for a market that sells in
    /// another ([`PricingConfig::fx`]).
    pub currency: Currency,
    pub rule: PriceRule,
    /// What the cloud seam cannot price, per month, minor units. NOT the
    /// public IPv4 addresses any more: each machine's is read from its
    /// cloud's price list (`Cloud::catalog`, owner 2026-10-05).
    pub base_extra_month_minor: u64,
    /// **One public IPv4 address a month, minor units, when the provider
    /// cannot price it** (a cloud guest built before `price_ipv4`, a list
    /// without the item). 351 = UpCloud's `ipv4_address`, se-sto1, read
    /// 2026-10-05 (0.4812 c/h × 730). Only a fallback: the list wins.
    pub ipv4_fallback_month_minor: u64,
    /// The twin's replica tier, per GiB-month, milli-minor units (the seam
    /// prices one disk tier per cloud — main's).
    pub replica_gib_month_milli: u64,
    /// Units of a market's currency per unit of [`PricingConfig::currency`],
    /// basis points. A market with no rate is not sold plans.
    pub fx: Fx,
    /// The offers.
    pub plans: BTreeMap<String, PlanSpec>,
    /// **The whole basis, stated**, for a deployment whose cloud prices
    /// nothing. Absent: read from the cloud.
    pub basis: Option<StatedBasis>,
}

impl Default for PricingConfig {
    fn default() -> Self {
        PricingConfig {
            currency: Currency::Eur,
            rule: PriceRule::default(),
            base_extra_month_minor: 0,
            ipv4_fallback_month_minor: IPV4_FALLBACK_MONTH_MINOR,
            replica_gib_month_milli: 0,
            fx: Fx::default(),
            plans: BTreeMap::new(),
            basis: None,
        }
    }
}

/// [`PricingConfig::ipv4_fallback_month_minor`]'s default: UpCloud's
/// `ipv4_address` in se-sto1 on 2026-10-05, 3.51 EUR a month.
pub const IPV4_FALLBACK_MONTH_MINOR: u64 = 351;

/// `[pricing.<product>.basis]`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct StatedBasis {
    pub base_month_minor: u64,
    pub storage_gib_month_milli: u64,
}

impl PricingConfig {
    /// Refused by name rather than sold wrong: a 1-month plan above the rule's
    /// 1-month size, a plan of no size, and a rule that would sell under its
    /// own floor.
    pub fn check(&self, product: &str) -> Result<(), String> {
        for (id, p) in &self.plans {
            if p.size_bytes == 0 {
                return Err(format!("[pricing.{product}.plans.{id:?}] size_bytes is 0: a plan of no room sells nothing"));
            }
            if p.period == Period::Month && p.size_bytes > self.rule.month_max_bytes {
                return Err(format!(
                    "[pricing.{product}.plans.{id:?}] is a 1-month plan of {} — above the rule's month_max_bytes ({}). A 1-month plan is at most that (owner, 2026-10-05: a provider disk never shrinks, and a month of a big plan is money lost)",
                    plan::gib_words(p.size_bytes),
                    plan::gib_words(self.rule.month_max_bytes)
                ));
            }
        }
        let r = &self.rule;
        if r.floor_bp < 10_000 || r.markup_bp * r.yearly_bp / 10_000 < r.floor_bp {
            return Err(format!(
                "[pricing.{product}.rule] markup {} × yearly {} is under the floor {} (basis points): every 12-month plan would be refused at the buy",
                r.markup_bp, r.yearly_bp, r.floor_bp
            ));
        }
        Ok(())
    }
}

/// **The plans, per product, and what was last read about them.** On
/// [`AppState::plans`].
#[derive(Default)]
pub struct PlanBook {
    /// `[pricing.<product>]`, as loaded.
    pub products: RwLock<BTreeMap<String, PricingConfig>>,
    basis: Mutex<BTreeMap<String, (Instant, CostBasis)>>,
    reading: Mutex<BTreeSet<String>>,
    accounts: Mutex<BTreeMap<String, (u64, u32)>>,
    keeper: AtomicBool,
}

impl PlanBook {
    pub fn new(products: BTreeMap<String, PricingConfig>) -> PlanBook {
        PlanBook {
            products: RwLock::new(products),
            ..PlanBook::default()
        }
    }

    /// The product's pricing, when it sells plans.
    pub fn config(&self, product: &str) -> Option<PricingConfig> {
        self.products
            .read()
            .unwrap_or_else(|e| e.into_inner())
            .get(product)
            .cloned()
    }

    /// The last basis read for `product`, while it is fresh.
    pub fn cached(&self, product: &str) -> Option<CostBasis> {
        let held = self.basis.lock().unwrap_or_else(|e| e.into_inner());
        held.get(product)
            .filter(|(at, _)| at.elapsed() < BASIS_FRESH)
            .map(|(_, b)| *b)
    }

    fn store(&self, product: &str, b: CostBasis) {
        self.basis
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .insert(product.to_owned(), (Instant::now(), b));
    }

    /// A test stands at the end of a basis's life without waiting for it.
    pub fn age_for_test(&self, by: Duration) {
        for (at, _) in self
            .basis
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .values_mut()
        {
            *at = at.checked_sub(by).unwrap_or(*at);
        }
    }
}

// ── the basis ───────────────────────────────────────────────────────────

/// **Read what the estate costs, from the provider, now.** Stores it.
pub fn read_basis(s: &AppState, product: &str) -> Result<CostBasis, String> {
    let cfg = s
        .plans
        .config(product)
        .ok_or_else(|| format!("{product} sells no plans"))?;
    let b = basis_of(s, product, &cfg)?;
    s.plans.store(product, b);
    Ok(b)
}

fn basis_of(s: &AppState, product: &str, cfg: &PricingConfig) -> Result<CostBasis, String> {
    if let Some(b) = cfg.basis {
        return Ok(CostBasis {
            currency: cfg.currency,
            base_month_minor: b.base_month_minor,
            storage_gib_month_milli: b.storage_gib_month_milli,
        });
    }
    let config = s.config.read().unwrap_or_else(|e| e.into_inner()).clone();
    let machines: Vec<_> = config
        .machines
        .values()
        .filter(|m| (m.product.is_empty() || m.product == product) && !m.plan.is_empty())
        .cloned()
        .collect();
    if machines.is_empty() {
        return Err(format!(
            "the base of {product} cannot be priced: [machines] names no server with a plan for it, and [pricing.{product}.basis] states none"
        ));
    }
    let cloud_named = |name: &str| s.registry.clouds().find(|c| c.name() == name);
    let check = |p: monetize_cloud::Price, what: &str| -> Result<u64, String> {
        if p.currency != cfg.currency {
            return Err(format!(
                "{what} is priced in {:?} and [pricing.{product}] currency is {:?}",
                p.currency, cfg.currency
            ));
        }
        Ok(p.minor_per_month)
    };
    let mut base = cfg.base_extra_month_minor;
    // One catalog read per cloud and zone: every machine's address is priced from it.
    let mut ipv4_of: std::collections::BTreeMap<
        (String, String),
        Result<Option<monetize_cloud::Price>, String>,
    > = std::collections::BTreeMap::new();
    for m in &machines {
        let cloud = cloud_named(&m.cloud).ok_or_else(|| {
            format!(
                "machine {} names cloud {:?}, which is not linked",
                m.hostname, m.cloud
            )
        })?;
        let role = if m.role == "twin" {
            ServerRole::Replica
        } else {
            ServerRole::Primary
        };
        // The cloud's own zone: a provider's price list is per zone, and an
        // empty one is not in it (MEASURED estate-box 2026-10-05: "upcloud:
        // zone  is not in the price list").
        let zone = cloud
            .zones()
            .first()
            .cloned()
            .unwrap_or_else(|| Zone(String::new()));
        let p = monetize_cloud::Cloud::price(
            &cloud,
            &monetize_cloud::cloud::Priced::Resource(&Resource::Server {
                plan: m.plan.clone(),
                zone: zone.clone(),
                role,
            }),
        )
        .map_err(|e| {
            format!(
                "{} could not price server plan {:?} ({}): {e}",
                m.cloud, m.plan, m.hostname
            )
        })?;
        base = base.saturating_add(check(p, &format!("server plan {:?}", m.plan))?);
        // ★ Its public IPv4 address, from the SAME price list (owner,
        // 2026-10-05: the base counted 3 × a typed 3.00 EUR; UpCloud's list
        // said 3.51). One per machine; a provider that cannot price it is
        // the stated fallback, said in the journal.
        let listed = ipv4_of
            .entry((m.cloud.clone(), zone.0.clone()))
            .or_insert_with(|| {
                monetize_cloud::ask::price_ipv4(&cloud, &zone).map_err(|e| e.to_string())
            })
            .clone();
        let ipv4 = match listed.and_then(|p| {
            p.ok_or_else(|| format!("{} prices no public IPv4 address of its own", m.cloud))
        }) {
            Ok(p) => check(p, &format!("the public IPv4 address of {}", m.hostname))?,
            Err(e) => {
                eprintln!(
                    "monetize-server: plans of {product}: {} did not price the public IPv4 address of {} ({e}); the base counts the stated fallback {} a month",
                    m.cloud,
                    m.hostname,
                    Money { minor: cfg.ipv4_fallback_month_minor, currency: cfg.currency }.in_words()
                );
                cfg.ipv4_fallback_month_minor
            }
        };
        base = base.saturating_add(ipv4);
    }
    // Main's data tier: a 100 GiB disk on the set's server, through the
    // set's cloud — the one the batcher grows.
    let (cloud, server) = match config.products.get(product).and_then(|p| p.grow.clone()) {
        Some(g) => (g.cloud, g.server),
        None => {
            let main = machines
                .iter()
                .find(|m| m.role == "appliance")
                .unwrap_or(&machines[0]);
            (main.cloud.clone(), main.server.clone())
        }
    };
    let c = cloud_named(&cloud)
        .ok_or_else(|| format!("the data set's cloud {cloud:?} is not linked"))?;
    let p = monetize_cloud::Cloud::price(
        &c,
        &monetize_cloud::cloud::Priced::Resource(&Resource::Disk {
            server: ServerId(server),
            gib: 100,
        }),
    )
    .map_err(|e| format!("{cloud} could not price a data disk: {e}"))?;
    let disk_100 = check(p, "a data disk")?;
    Ok(CostBasis {
        currency: cfg.currency,
        base_month_minor: base,
        storage_gib_month_milli: disk_100 * 10 + cfg.replica_gib_month_milli,
    })
}

/// **The basis for a WINDOW**: the cached one, or — with none — a reading
/// started in the background (one per product at a time) and `None`, which
/// the window answers as `reading`.
fn basis_for_window(s: &Arc<AppState>, product: &str) -> Option<CostBasis> {
    if let Some(b) = s.plans.cached(product) {
        return Some(b);
    }
    start_reading(s, product);
    // As the wallet reading's window does (`grow::WINDOW_WAIT`): a short wait
    // on the ONE reading being made, so a provider that answers at once is
    // an answer and not a `reading` the console has to ask again for.
    let until = Instant::now() + crate::grow::WINDOW_WAIT;
    while Instant::now() < until {
        std::thread::sleep(Duration::from_millis(50));
        if let Some(b) = s.plans.cached(product) {
            return Some(b);
        }
    }
    None
}

fn start_reading(s: &Arc<AppState>, product: &str) {
    if !s
        .plans
        .reading
        .lock()
        .unwrap_or_else(|e| e.into_inner())
        .insert(product.to_owned())
    {
        return;
    }
    let (s2, p) = (Arc::clone(s), product.to_owned());
    // A jera job on the product's lane (A32-14): a reading that fails or panics is an ALERT line
    // and a FAILED row, and the product is let go on every path (a panicked raw thread left it
    // "reading" for good: its price was never read again).
    let started = crate::jobs::once(BASIS_JOB, &crate::jobs::of(s, product), move || {
        let (s3, p3) = (Arc::clone(&s2), p.clone());
        let _let_go = crate::jobs::Finally(move || {
            s3.plans
                .reading
                .lock()
                .unwrap_or_else(|e| e.into_inner())
                .remove(&p3);
        });
        read_basis(&s2, &p)
            .map(|_| ())
            .map_err(|e| format!("plans {p}: the provider's price could not be read: {e}"))
    });
    if !matches!(started, Ok(true)) {
        s.plans
            .reading
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .remove(product);
    }
}

/// The jera job kind of one reading of a product's plan basis (A32-14).
pub const BASIS_JOB: &str = "monetize_plan_basis";
/// The jera duty that keeps every product's basis warm (A32-14).
pub const BASIS_KEEPER: &str = "monetize_plan_keeper";

/// **Warm every product's basis now and keep it warm** — called once at
/// start (`cli::serve`, the bench).
pub fn arm(s: &Arc<AppState>) {
    let products: Vec<String> = s
        .plans
        .products
        .read()
        .unwrap_or_else(|e| e.into_inner())
        .keys()
        .cloned()
        .collect();
    for p in &products {
        start_reading(s, p);
    }
    if products.is_empty() || s.plans.keeper.swap(true, Ordering::SeqCst) {
        return;
    }
    // A jera duty (A32-14): it ends with the state (held weakly); one that cannot start is said.
    let weak = Arc::downgrade(s);
    let mut first = true;
    let started = crate::jobs::every(BASIS_KEEPER, BASIS_KEEP_EVERY, move || {
        // The first look only waits, as the thread's first sleep did.
        if std::mem::take(&mut first) {
            return Ok(crate::jobs::Round::Again);
        }
        let Some(s2) = weak.upgrade() else {
            return Ok(crate::jobs::Round::Done);
        };
        for p in &products {
            start_reading(&s2, p);
        }
        Ok(crate::jobs::Round::Again)
    });
    if let Err(e) = started {
        eprintln!("monetize-server: ALERT jobs.unstarted {BASIS_KEEPER}: the plan keeper could not start: {e}; a basis is read when a customer asks");
    }
}

// ── N ───────────────────────────────────────────────────────────────────

/// The first millisecond of the calendar month `at` is in.
fn month_start(at: u64) -> u64 {
    let (y, m, _) = monetize_embed::civil::civil_from_unix_ms(at);
    monetize_embed::civil::unix_ms_from_civil(y, m, 1)
}

/// **The distinct tenants who paid for `product` in the calendar month
/// before `now`'s** — granted ledger sales at a price, and paid signups.
pub fn paying_last_month(s: &AppState, product: &str, now: u64) -> u32 {
    let this = month_start(now);
    let prev = month_start(this.saturating_sub(1));
    let mut tenants = BTreeSet::new();
    if let Ok(sales) = s.ledger.sales() {
        for sale in sales {
            if sale.product == product
                && sale.outcome == monetize::ledger::SaleOutcome::Granted
                && sale.price.minor > 0
                && (prev..this).contains(&sale.started_unix_ms)
            {
                tenants.insert(sale.tenant);
            }
        }
    }
    if let Ok(signups) = s.signups.export_all() {
        for rec in signups {
            let crate::signup::SignupState::Created { namespace, .. } = &rec.state else {
                continue;
            };
            let paid = u64::try_from(rec.signup.paid_at_unix_ms).unwrap_or(0);
            if rec.signup.product == product
                && rec.signup.amount.minor > 0
                && (prev..this).contains(&paid)
            {
                tenants.insert(namespace.clone());
            }
        }
    }
    u32::try_from(tenants.len()).unwrap_or(u32::MAX)
}

/// **N** for `product` this month — counted once a month.
pub fn accounts(s: &AppState, product: &str, rule: &PriceRule) -> u32 {
    let now = s.now();
    let key = month_start(now);
    if let Some((k, n)) = s
        .plans
        .accounts
        .lock()
        .unwrap_or_else(|e| e.into_inner())
        .get(product)
    {
        if *k == key {
            return *n;
        }
    }
    let n = rule.accounts(paying_last_month(s, product, now));
    s.plans
        .accounts
        .lock()
        .unwrap_or_else(|e| e.into_inner())
        .insert(product.to_owned(), (key, n));
    n
}

// ── the tenant's plan ───────────────────────────────────────────────────

/// **The plan `tenant` holds**, read back off the purchase behind their
/// current fact. `None`: nothing paid, a hand-set fact, or Retention (they
/// start over).
pub fn current(
    s: &AppState,
    product: &str,
    tenant: &str,
) -> Result<Option<(Current, monetize_product::EntitlementFact)>, Status> {
    let Some(fact) = s
        .ledger
        .entitlement(product, tenant)
        .map_err(crate::services::ledger_err)?
    else {
        return Ok(None);
    };
    let Some(paid_until) = fact.paid_until_unix_ms else {
        return Ok(None);
    };
    if matches!(fact.state, State::Free | State::Retention) {
        return Ok(None);
    }
    let terms = s
        .ledger
        .attempt(&fact.plan)
        .ok()
        .flatten()
        .and_then(|a| a.purchase.plan);
    let c = Current {
        period: terms.as_ref().map(|t| t.period).unwrap_or(Period::Month),
        size_bytes: fact.caps.get(SIZE_METER).copied().unwrap_or(0),
        paid_until_unix_ms: paid_until,
        state: fact.state,
        month_price: terms.map(|t| t.month_price),
    };
    Ok(Some((c, fact)))
}

/// **What a request asks of the plan.**
#[derive(Clone, Copy, Debug)]
pub enum Ask<'a> {
    /// A window row by id.
    Row(&'a str),
    /// A typed target (`caps`): the 12-month size field.
    Typed(&'a BTreeMap<String, u64>),
    /// `renew = true`: prolong a 1-month plan, renew a 12-month one.
    Renew,
}

/// **The row a request resolved to, before anything is priced** — and the
/// ticket it needs.
#[derive(Clone, Debug)]
pub struct Selected {
    pub tenant: String,
    pub row: Row,
    pub current: Option<Current>,
    /// The caps the tenant holds now.
    pub held: BTreeMap<String, u64>,
    /// The caps after; empty for a renewal.
    pub target: BTreeMap<String, u64>,
    /// What the actor ticket must cover: the caps after, for a window row
    /// (as a bundle's resolved caps always were); the caps the request named,
    /// for a typed size (as a typed order's always were); none for a renewal.
    pub ticket_caps: BTreeMap<String, u64>,
    /// `renew` or `order`.
    pub purpose: &'static str,
}

/// **Resolve a request into a plan row** — pure but for the ledger read, so
/// the actor ticket is checked before anything is priced or dialled.
pub fn select(
    s: &AppState,
    cfg: &PricingConfig,
    product: &str,
    tenant: &str,
    ask: Ask<'_>,
) -> Result<Selected, Status> {
    if tenant.is_empty() {
        return Err(Status::invalid_argument("tenant is required"));
    }
    let now = s.now();
    let cur = current(s, product, tenant)?;
    let held = s
        .ledger
        .entitlement(product, tenant)
        .map_err(crate::services::ledger_err)?
        .map(|f| f.caps)
        .unwrap_or_default();
    let current = cur.map(|(c, _)| c);
    let rows = plan::rows(&cfg.rule, current.as_ref(), &cfg.plans, now);
    let row = match ask {
        Ask::Row(id) => rows.into_iter().find(|r| r.id == id).ok_or_else(|| {
            Status::failed_precondition(format!(
                "{id:?} is not on sale for this account now; ask Purchase.Bundles for what is"
            ))
        })?,
        Ask::Renew => rows
            .into_iter()
            .find(|r| r.verb.keeps_caps())
            .ok_or_else(|| {
                refused(&Refusal::NothingToRenew {
                    tenant: tenant.to_owned(),
                    why: "no plan to prolong or renew right now".into(),
                })
            })?,
        Ask::Typed(caps) => {
            if let Some(other) = caps.keys().find(|k| k.as_str() != SIZE_METER) {
                return Err(Status::invalid_argument(format!(
                    "a plan is sized in {SIZE_METER}; {other:?} is not sold as a plan"
                )));
            }
            let Some(size) = caps.get(SIZE_METER).copied() else {
                return Err(Status::invalid_argument(format!(
                    "name the size, in {SIZE_METER}"
                )));
            };
            plan::typed(&cfg.rule, current.as_ref(), size, now)
        }
    };
    let (target, purpose) = if row.verb.keeps_caps() {
        (BTreeMap::new(), monetize_embed::PURPOSE_RENEW)
    } else {
        let mut t = held.clone();
        t.insert(SIZE_METER.to_owned(), row.size_bytes);
        (t, monetize_embed::PURPOSE_ORDER)
    };
    let ticket_caps = match ask {
        Ask::Typed(caps) if !row.verb.keeps_caps() => caps.clone(),
        _ => target.clone(),
    };
    Ok(Selected {
        tenant: tenant.to_owned(),
        row,
        current,
        held,
        target,
        ticket_caps,
        purpose,
    })
}

fn refused(r: &Refusal) -> Status {
    crate::services::purchase::refused_status(r)
}

// ── prices ──────────────────────────────────────────────────────────────

/// **One row, priced**: what is charged now (in the market's currency), the
/// price per month it locks, what it costs the estate, the credit taken off.
#[derive(Clone, Debug)]
pub struct Priced {
    pub price: Money,
    pub month_price: Money,
    pub cost_month_milli: u128,
    /// The span the charge pays for (the floor's).
    pub covered_ms: u64,
    pub credit: Option<Money>,
    pub accounts: u32,
}

fn convert(cfg: &PricingConfig, minor: u64, to: Currency) -> Option<Money> {
    cfg.fx.convert(
        Money {
            minor,
            currency: cfg.currency,
        },
        to,
    )
}

/// A price in a market's currency, back in the cost currency (rounded down —
/// the floor is checked against the LESS favourable figure).
fn back(cfg: &PricingConfig, price: Money) -> Option<u64> {
    if price.currency == cfg.currency {
        return Some(price.minor);
    }
    let rate = *cfg.fx.0.get(price.currency.code())?;
    (rate > 0).then(|| {
        u64::try_from(u128::from(price.minor) * 10_000 / u128::from(rate)).unwrap_or(u64::MAX)
    })
}

/// **Price a row** under `basis`, for a buyer paying in `to`. `Err` names why
/// it cannot be priced (no rate for the market).
/// **The owner's TARGET for a 12-month plan of `size_bytes`, in `to`** —
/// `[pricing.<p>.plans."<id>"] target` (owner, 2026-10-05: "lower 12 months
/// to 890"), or `None`. The market's own figure when one is named; otherwise
/// a named one, back in the cost currency (rounded down) and on to `to`.
fn plan_target(
    cfg: &PricingConfig,
    period: Period,
    size_bytes: u64,
    to: Currency,
) -> Option<Money> {
    let target = cfg
        .plans
        .values()
        .find(|s| s.period == period && s.size_bytes == size_bytes)?
        .target
        .as_ref()?;
    if let Some(minor) = target.get(to.code()) {
        return Some(Money {
            minor: *minor,
            currency: to,
        });
    }
    let (code, minor) = target
        .iter()
        .find(|(c, _)| Currency::from_code(c).is_some())?;
    let in_cost = back(
        cfg,
        Money {
            minor: *minor,
            currency: Currency::from_code(code)?,
        },
    )?;
    convert(cfg, in_cost, to)
}

pub fn price_row(
    cfg: &PricingConfig,
    basis: &CostBasis,
    accounts: u32,
    to: Currency,
    row: &Row,
    current: Option<&Current>,
    now: u64,
) -> Result<Priced, String> {
    let r = &cfg.rule;
    let in_market = |minor: u64| {
        convert(cfg, minor, to).ok_or_else(|| format!("no [pricing] fx rate for {}", to.code()))
    };
    let cost = r.cost_month_milli(basis, row.size_bytes, accounts);
    let covered = row.until_unix_ms.saturating_sub(row.starts_unix_ms);
    // ★ THE OWNER'S TARGETS (2026-10-05, "the professional choice"), in
    // the market's currency, the floor converted UP into it so it still
    // clears once back in the cost currency:
    // * a 1-month plan: max(target, rule, floor) — the target raises it;
    // * a 12-month plan: max(floor, min(rule, target)) — the target lowers
    //   it, never under the floor; its month is the year's twelfth, rounded
    //   up (the rule's own month when the rule's year stands).
    // Without a target, the rule's price.
    let month_of = |size_bytes: u64| -> Result<Money, String> {
        let rule = in_market(r.monthly_minor(basis, size_bytes, accounts))?;
        let Some(target) = plan_target(cfg, Period::Month, size_bytes, to) else {
            return Ok(rule);
        };
        let floor = in_market(r.floor_minor(
            r.cost_month_milli(basis, size_bytes, accounts),
            monetize::pricing::MONTH_MS,
        ))?;
        Ok(Money {
            minor: monetize::pricing::month_at_target(target.minor, rule.minor, floor.minor),
            currency: to,
        })
    };
    let year_of = |rule_total: Money, rule_month: Money| -> Result<(Money, Money), String> {
        let Some(target) = plan_target(cfg, Period::Year, row.size_bytes, to) else {
            return Ok((rule_total, rule_month));
        };
        let floor = in_market(r.floor_minor(cost, covered))?;
        let total = monetize::pricing::year_at_target(target.minor, rule_total.minor, floor.minor);
        if total == rule_total.minor {
            return Ok((rule_total, rule_month));
        }
        Ok((
            Money {
                minor: total,
                currency: to,
            },
            Money {
                minor: total.div_ceil(12),
                currency: to,
            },
        ))
    };
    let rule_year = || -> Result<(Money, Money), String> {
        Ok((
            in_market(r.yearly_total_minor(basis, row.size_bytes, accounts))?,
            in_market(r.yearly_month_minor(basis, row.size_bytes, accounts))?,
        ))
    };
    // A locked month price is honoured while it is in this market's currency
    // and still clears the floor at today's cost; otherwise today's price.
    let locked_money = |today: Money| -> Result<Money, String> {
        match current.and_then(|c| c.month_price) {
            Some(m)
                if m.currency == to
                    && back(cfg, m)
                        .is_some_and(|c| r.floor_ok(c, cost, monetize::pricing::MONTH_MS)) =>
            {
                Ok(m)
            }
            _ => Ok(today),
        }
    };
    let locked = |today: u64| -> Result<Money, String> { locked_money(in_market(today)?) };
    let p = match row.verb {
        Verb::Start => match row.period {
            Period::Month => {
                let m = month_of(row.size_bytes)?;
                Priced {
                    price: m,
                    month_price: m,
                    cost_month_milli: cost,
                    covered_ms: covered,
                    credit: None,
                    accounts,
                }
            }
            Period::Year => {
                let (rule_total, rule_month) = rule_year()?;
                let (total, month) = year_of(rule_total, rule_month)?;
                Priced {
                    price: total,
                    month_price: month,
                    cost_month_milli: cost,
                    covered_ms: covered,
                    credit: None,
                    accounts,
                }
            }
        },
        // The locked month while it clears the floor; else today's (at the
        // plan's target, if it has one).
        Verb::Prolong => {
            let m = locked_money(month_of(row.size_bytes)?)?;
            Priced {
                price: m,
                month_price: m,
                cost_month_milli: cost,
                covered_ms: covered,
                credit: None,
                accounts,
            }
        }
        // The locked month, twelve times — at the target clamp when the plan
        // has one (never above that rule, never under the floor).
        Verb::Renew => {
            let m = locked(r.yearly_month_minor(basis, row.size_bytes, accounts))?;
            let (total, month) = year_of(
                Money {
                    minor: m.minor.saturating_mul(12),
                    currency: m.currency,
                },
                m,
            )?;
            Priced {
                price: total,
                month_price: month,
                cost_month_milli: cost,
                covered_ms: covered,
                credit: None,
                accounts,
            }
        }
        Verb::Upgrade => {
            let (rule_total, rule_month) = rule_year()?;
            let (total, month) = year_of(rule_total, rule_month)?;
            let c = current.ok_or("an upgrade needs a plan to upgrade")?;
            let paid_month = locked_money(month_of(c.size_bytes)?)?;
            let credit = plan::upgrade_credit(r, c, paid_month, now);
            let charged = Money {
                minor: total.minor.saturating_sub(credit.minor),
                currency: total.currency,
            };
            Priced {
                price: charged,
                month_price: month,
                cost_month_milli: cost,
                covered_ms: covered,
                credit: (credit.minor > 0).then_some(credit),
                accounts,
            }
        }
        Verb::Bump => {
            let from = current.map(|c| c.size_bytes).unwrap_or(0);
            let bump = in_market(r.bump_minor(basis, from, row.size_bytes, covered))?;
            let month = in_market(r.yearly_month_minor(basis, row.size_bytes, accounts))?;
            // The floor of a bump is about the room it ADDS: the base share
            // was paid by the plan it bumps.
            let added = cost.saturating_sub(r.cost_month_milli(basis, from, accounts));
            Priced {
                price: bump,
                month_price: month,
                cost_month_milli: added,
                covered_ms: covered,
                credit: None,
                accounts,
            }
        }
    };
    Ok(p)
}

/// **The floor, at `basis`**: the charge (with any credit, which was paid
/// before) back in the cost currency, after the fee, ≥ cost × 1.10 over the
/// span it pays for.
pub fn clears_floor(cfg: &PricingConfig, p: &Priced) -> bool {
    let charged = Money {
        minor: p.price.minor + p.credit.map(|c| c.minor).unwrap_or(0),
        currency: p.price.currency,
    };
    back(cfg, charged).is_some_and(|c| cfg.rule.floor_ok(c, p.cost_month_milli, p.covered_ms))
}

/// **The label of a row**, in the customer's words.
pub fn label(row: &Row) -> String {
    match row.verb {
        Verb::Start => plan::label(row.size_bytes, row.period),
        Verb::Prolong => format!("Prolong 1 month ({})", plan::gib_words(row.size_bytes)),
        Verb::Renew => format!("Renew 12 months ({})", plan::gib_words(row.size_bytes)),
        Verb::Upgrade => format!("Upgrade to {}", plan::label(row.size_bytes, row.period)),
        Verb::Bump => format!(
            "Grow to {}, until {}",
            plan::gib_words(row.size_bytes),
            iso_date(row.until_unix_ms)
        ),
    }
}

// ── sign-up by the room rule (round 5.3.1) ───────────────────────────────
//
// The owner on test2 2026-10-08: room is ONE bar, at sign-up too — "1 month"
// at 10 GiB only (a provider disk never shrinks, 2026-10-05) and "12 months"
// from 10 to 1000 GiB, each priced by the room rule at the market's price, and
// the checkout charging exactly what was shown. A NEW account's plans are the
// declared [pricing.<product>.plans] a tenant with no plan may START with
// (`plan::rows(…, None, …)`), priced now; each is sold under a PRICED id —
// `<product>/<plan>@<minor><CUR>` — so the amount the checkout charged is in
// the id the vendor hands back, and the paid signup is checked against it.

/// The separator between a declared plan and its price in a priced sign-up id.
pub const PRICED_AT: char = '@';

/// Pure: the priced id of `row_id` of `product` at `price`.
pub fn priced_id(product: &str, row_id: &str, price: &Money) -> String {
    format!(
        "{product}/{row_id}{PRICED_AT}{}{}",
        price.minor,
        price.currency.code()
    )
}

/// Pure: `(product, plan, price)` out of a priced id; `None` for any other id.
pub fn parse_priced_id(id: &str) -> Option<(String, String, Money)> {
    let (head, priced) = id.rsplit_once(PRICED_AT)?;
    let (product, plan) = head.split_once('/')?;
    let digits = priced.trim_end_matches(|c: char| c.is_ascii_alphabetic());
    let currency = Currency::from_code(&priced[digits.len()..])?;
    let minor: u64 = digits.parse().ok()?;
    (!product.is_empty() && !plan.is_empty()).then(|| {
        (
            product.to_owned(),
            plan.to_owned(),
            Money { minor, currency },
        )
    })
}

/// One room plan a new account may start with, priced now.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignupRoom {
    pub id: String,
    pub name: String,
    pub plan: crate::config::SignupPlan,
}

/// Pure: a declared plan's sign-up terms at `price` (30 days for a month, 365 for a year).
pub fn signup_terms(size_bytes: u64, period: Period, price: Money) -> crate::config::SignupPlan {
    crate::config::SignupPlan {
        amount_minor: price.minor,
        currency: price.currency,
        cap_bytes: size_bytes,
        period_days: match period {
            Period::Month => 30,
            Period::Year => 365,
        },
    }
}

/// **The room plans a NEW account may start with, priced now in `market`'s
/// currency** — the 1-month plan(s) the rule allows, then the 12-month ones by
/// size. `None` when this deployment prices no plans for `product` or the
/// provider's price is still being read (the caller keeps its own table).
pub fn signup_rooms(s: &Arc<AppState>, product: &str, market: Market) -> Option<Vec<SignupRoom>> {
    let has_plans = s
        .plans
        .products
        .read()
        .unwrap_or_else(|p| p.into_inner())
        .get(product)
        .is_some_and(|c| !c.plans.is_empty());
    if !has_plans {
        return None;
    }
    let basis = basis_for_window(s, product)?;
    rooms_on(s, product, market, &basis)
}

/// [`signup_rooms`] on the basis already read (the START of a sign-up: the page
/// read the prices moments before; a basis no longer held is "ask again").
pub fn signup_rooms_cached(s: &AppState, product: &str, market: Market) -> Option<Vec<SignupRoom>> {
    let basis = s.plans.cached(product)?;
    rooms_on(s, product, market, &basis)
}

fn rooms_on(
    s: &AppState,
    product: &str,
    market: Market,
    basis: &CostBasis,
) -> Option<Vec<SignupRoom>> {
    let cfg = s
        .plans
        .products
        .read()
        .unwrap_or_else(|p| p.into_inner())
        .get(product)
        .cloned()?;
    if cfg.plans.is_empty() {
        return None;
    }
    let basis = *basis;
    let now = s.now();
    let n = accounts(s, product, &cfg.rule);
    let to = market.currency();
    let mut out = Vec::new();
    for row in plan::rows(&cfg.rule, None, &cfg.plans, now) {
        if row.verb != Verb::Start || row.refused.is_some() {
            continue;
        }
        match price_row(&cfg, &basis, n, to, &row, None, now) {
            Ok(p) => out.push(SignupRoom {
                id: priced_id(product, &row.id, &p.price),
                name: plan::label(row.size_bytes, row.period),
                plan: signup_terms(row.size_bytes, row.period, p.price),
            }),
            Err(why) => eprintln!(
                "monetize-server: sign-up plan {:?} of {product} not offered in {market:?}: {why}",
                row.id
            ),
        }
    }
    (!out.is_empty()).then_some(out)
}

/// **The terms a priced sign-up id stands for**, read against THIS
/// deployment's declared plans: the size and the period from
/// `[pricing.<product>.plans.<plan>]`, the price from the id (what the checkout
/// charged, and what the vendor must have taken). `None` when the id names no
/// declared plan a new account may start with.
pub fn priced_signup_plan(s: &AppState, id: &str) -> Option<crate::config::SignupPlan> {
    let (product, plan_id, price) = parse_priced_id(id)?;
    let cfg = s
        .plans
        .products
        .read()
        .unwrap_or_else(|p| p.into_inner())
        .get(&product)
        .cloned()?;
    let spec = cfg.plans.get(&plan_id)?;
    if spec.period == Period::Month && spec.size_bytes > cfg.rule.month_max_bytes {
        return None;
    }
    Some(signup_terms(spec.size_bytes, spec.period, price))
}

/// **The price a priced id's plan has NOW** in `market`, for the start of a
/// sign-up — `Err` names why it cannot be sold, `Ok(true)` when the id's price
/// is today's, `Ok(false)` when the price moved since the page read it.
pub fn priced_id_is_current(s: &AppState, id: &str, market: Market) -> Result<bool, String> {
    let (product, _, price) =
        parse_priced_id(id).ok_or_else(|| format!("{id:?} is not a priced plan"))?;
    let rooms = signup_rooms_cached(s, &product, market)
        .ok_or_else(|| "the room's price is being read; ask again in a moment".to_owned())?;
    let base = id.rsplit_once(PRICED_AT).map(|(h, _)| h).unwrap_or(id);
    let now = rooms
        .iter()
        .find(|r| r.id.rsplit_once(PRICED_AT).map(|(h, _)| h) == Some(base))
        .ok_or_else(|| format!("{base:?} is not sold to a new account here"))?;
    Ok(now.plan.amount_minor == price.minor && now.plan.currency == price.currency)
}

// ── the quote a purchase runs ───────────────────────────────────────────

/// **The quote a selected row becomes** — the ordinary quote (`quote_order`
/// for a raise, `Transaction::renewal` for a prolong or renewal), with the
/// rule's price, the plan's period and the [`PlanTerms`] laid on it.
///
/// The price is the one the WINDOW showed: priced off the cached basis. With
/// `fresh` — the BUY — the provider's price is then read NOW and the floor
/// re-checked against it: a shown price that no longer leaves cost + 10 %
/// after the fee is refused ([`Refusal::UnderFloor`]) and nothing is
/// charged; the fresh basis is kept, so the window shows today's price.
pub fn quote(
    s: &AppState,
    product: monetize_product::Served<'_>,
    cfg: &PricingConfig,
    sel: &Selected,
    market: Market,
    payer: Option<String>,
    fresh: bool,
) -> Result<Quote, Status> {
    // A row the rule refuses: the BUY is refused before anything is priced;
    // a QUOTE answers it as every refused quote is answered — `can_serve =
    // false` with the reason and the customer's words — so an operator's
    // "what would this come to" reads the rule rather than an error.
    let rule_says_no = sel.row.refused.as_ref().map(|words| Refusal::PlanRefused {
        tenant: sel.tenant.clone(),
        why: format!("the plan rule's limit — {words}"),
        words: words.clone(),
    });
    if let (true, Some(r)) = (fresh, &rule_says_no) {
        return Err(refused(r));
    }
    let read_now = || {
        read_basis(s, product.id()).map_err(|e| {
            Status::unavailable(format!("the provider's price could not be read: {e}"))
        })
    };
    let shown = match s.plans.cached(product.id()) {
        Some(b) => b,
        None => read_now()?,
    };
    let n = accounts(s, product.id(), &cfg.rule);
    let to = market.currency();
    let now = s.now();
    let priced = price_row(cfg, &shown, n, to, &sel.row, sel.current.as_ref(), now)
        .map_err(Status::failed_precondition)?;
    let under = if fresh {
        let today = read_now()?;
        let at_today = price_row(cfg, &today, n, to, &sel.row, sel.current.as_ref(), now)
            .map_err(Status::failed_precondition)?;
        let check = Priced {
            cost_month_milli: at_today.cost_month_milli,
            ..priced.clone()
        };
        (!clears_floor(cfg, &check)).then_some(at_today.cost_month_milli)
    } else {
        None
    };
    let mut q = build(
        s,
        product,
        sel,
        market,
        payer,
        Price {
            basis: &shown,
            priced,
            under_floor_at: under,
        },
        fresh,
    )?;
    if let Some(r) = rule_says_no {
        q.serve = Err(r);
    }
    Ok(q)
}

/// **A row's price, as quoted**: the cost basis it was read from, what it
/// came to, and — for a buy — today's cost when that no longer clears the
/// floor.
struct Price<'a> {
    basis: &'a CostBasis,
    priced: Priced,
    under_floor_at: Option<u128>,
}

fn build(
    s: &AppState,
    product: monetize_product::Served<'_>,
    sel: &Selected,
    market: Market,
    payer: Option<String>,
    price: Price<'_>,
    buying: bool,
) -> Result<Quote, Status> {
    let Price {
        basis,
        priced,
        under_floor_at,
    } = price;
    let tenant = sel.tenant.clone();
    // A row that raises nothing — a prolong, a renewal, and an UPGRADE to a
    // 12-month plan of the size already held (MEASURED estate-box 2026-10-05:
    // "10 GiB · 12 months" for a 10 GiB month was refused as "not an
    // increase") — is a renewal of the caps held, for the row's period, to
    // the row's end; anything else is an order for the row's caps.
    let raises = !sel.row.verb.keeps_caps() && sel.target != sel.held;
    let mut q = if !raises {
        let terms = s.terms.read().unwrap_or_else(|e| e.into_inner()).clone();
        let host = terms.host(product.id());
        let mut t = terms.terms(product.id(), market, &host);
        t.policy.period = sel.row.period;
        s.transaction(product, monetize::no_vendor())
            .renewal(&TenantId(tenant.clone()), &t, payer)
            .map_err(|e| match e {
                TxError::Refused(r) => refused(&r),
                TxError::Ledger(l) => crate::services::ledger_err(l),
                other => Status::internal(other.to_string()),
            })?
    } else {
        let q = crate::services::purchase::quote_order(
            s,
            product,
            &tenant,
            sel.target.clone(),
            market,
            0,
            payer,
        )
        .map_err(crate::services::purchase::quote_status)?;
        if let Err(r) = &q.serve {
            eprintln!(
                "monetize-server: plan {} refused for {}/{}: {r}",
                sel.row.id,
                product.id(),
                tenant
            );
            if buying {
                return Err(refused(r));
            }
        }
        q
    };
    if let Some(cost_now) = under_floor_at {
        eprintln!(
            "monetize-server: plan {} for {}/{} at {} is UNDER THE FLOOR at the provider's price read now (cost {} milli/month, was {}; N {}); refused, nothing charged",
            sel.row.id,
            product.id(),
            tenant,
            priced.price.in_words(),
            cost_now,
            priced.cost_month_milli,
            priced.accounts
        );
        return Err(refused(&Refusal::UnderFloor {
            tenant,
            price: priced.price,
        }));
    }
    let months = priced
        .covered_ms
        .div_ceil(monetize::pricing::MONTH_MS)
        .max(1);
    let cost_minor = u64::try_from(priced.cost_month_milli.div_ceil(1000)).unwrap_or(u64::MAX);
    q.purchase.price = priced.price;
    q.purchase.cost = Money {
        minor: cost_minor.saturating_mul(months),
        currency: basis.currency,
    };
    q.purchase.policy.period = sel.row.period;
    q.purchase.plan = Some(PlanTerms {
        verb: sel.row.verb,
        period: sel.row.period,
        size_bytes: sel.row.size_bytes,
        month_price: priced.month_price,
        accounts: priced.accounts,
        cost_month: Money {
            minor: cost_minor,
            currency: basis.currency,
        },
        // A bump keeps the plan's end; an upgrade runs a year from TODAY even
        // when it is sold as a renewal of the caps held (whose period would
        // otherwise start at the current end).
        until_unix_ms: matches!(sel.row.verb, Verb::Bump | Verb::Upgrade)
            .then_some(sel.row.until_unix_ms),
        credit: priced.credit,
    });
    Ok(q)
}

// ── the window ──────────────────────────────────────────────────────────

/// **THE SHOP WINDOW for a tenant of a product that sells plans**: the
/// rows, priced off the cached basis, and the plan's gauge. `reading` when no
/// basis has been read yet (one is being read; the console asks again).
pub fn window(
    s: &Arc<AppState>,
    product: monetize_product::Served<'_>,
    cfg: &PricingConfig,
    tenant: &str,
    market: Market,
) -> Result<pb::ListBundlesResponse, Status> {
    let Some(basis) = basis_for_window(s, product.id()) else {
        eprintln!("monetize-server: the plans of {} for {tenant} wait on the provider's price (being read); answered `reading`", product.id());
        return Ok(pb::ListBundlesResponse {
            bundles: Vec::new(),
            reading: true,
            plan: None,
        });
    };
    let now = s.now();
    let n = accounts(s, product.id(), &cfg.rule);
    let to = market.currency();
    let cur = current(s, product.id(), tenant)?;
    let held = s
        .ledger
        .entitlement(product.id(), tenant)
        .map_err(crate::services::ledger_err)?
        .map(|f| f.caps)
        .unwrap_or_default();
    let current = cur.as_ref().map(|(c, _)| c.clone());
    let mut bundles = Vec::new();
    for row in plan::rows(&cfg.rule, current.as_ref(), &cfg.plans, now) {
        let priced = match price_row(cfg, &basis, n, to, &row, current.as_ref(), now) {
            Ok(p) => p,
            Err(why) => {
                eprintln!(
                    "monetize-server: plan {:?} not offered for {}/{tenant} in {market:?}: {why}",
                    row.id,
                    product.id()
                );
                continue;
            }
        };
        let (target, delta) = if row.verb.keeps_caps() {
            (BTreeMap::new(), BTreeMap::new())
        } else {
            let mut t = held.clone();
            t.insert(SIZE_METER.to_owned(), row.size_bytes);
            let d: BTreeMap<String, u64> = [(
                SIZE_METER.to_owned(),
                row.size_bytes
                    .saturating_sub(held.get(SIZE_METER).copied().unwrap_or(0)),
            )]
            .into();
            (t, d)
        };
        let (can_serve, reason) = match &row.refused {
            // The OFFER's words on load (cov-10): never an order's.
            Some(words) => (false, plan::words::on_offer(words)),
            None if !row.verb.keeps_caps() && delta.values().any(|v| *v > 0) => {
                match crate::services::purchase::quote_order(
                    s,
                    product,
                    tenant,
                    target.clone(),
                    market,
                    0,
                    None,
                ) {
                    Ok(q) => {
                        match &q.serve {
                            Ok(()) => (true, String::new()),
                            // The OFFER's words — larger than the room left here,
                            // on hand and growable — never the order's refusal:
                            // nobody has ordered it (owner, test2 2026-10-05, the
                            // 500 GiB plan's "We cannot raise this account …
                            // Nothing has been charged" on a panel just opened).
                            // The buy is re-checked when it is placed.
                            Err(r) => {
                                eprintln!("monetize-server: plan {:?} cannot be served for {}/{tenant}: {r}", row.id, product.id());
                                (false, r.offer_words())
                            }
                        }
                    }
                    Err(TxError::Refused(r)) => (false, r.offer_words()),
                    Err(e) => return Err(crate::services::purchase::quote_status(e)),
                }
            }
            None => (true, String::new()),
        };
        bundles.push(pb::Bundle {
            id: row.id.clone(),
            label: label(&row),
            price: priced.price.in_words(),
            // The period the PRICE is for: a month, a year — and nothing for
            // an upgrade (the year less a credit) or a bump (to the plan's
            // end), whose price is one charge and not a rate; the label says
            // what they run to.
            period: match row.verb {
                Verb::Upgrade | Verb::Bump => String::new(),
                _ => row.period.name().to_owned(),
            },
            delta,
            can_serve,
            reason,
            action: row.verb.name().to_owned(),
            target,
        });
    }
    let status = status(s, product.id(), cfg, &basis, to, cur.as_ref(), now);
    Ok(pb::ListBundlesResponse {
        bundles,
        reading: false,
        plan: Some(status),
    })
}

/// **The gauge and the banner.**
fn status(
    s: &AppState,
    product: &str,
    cfg: &PricingConfig,
    basis: &CostBasis,
    to: Currency,
    cur: Option<&(Current, monetize_product::EntitlementFact)>,
    now: u64,
) -> pb::PlanStatus {
    let Some((c, fact)) = cur else {
        return pb::PlanStatus {
            state: "none".into(),
            ..Default::default()
        };
    };
    let terms = s.terms.read().unwrap_or_else(|e| e.into_inner()).clone();
    let end = c.paid_until_unix_ms;
    let left = plan::days_left(end, now);
    let state = match c.state {
        State::Paid => "paid",
        State::Grace => "grace",
        State::Suspended => "read_only",
        State::Retention => "retention",
        State::Free => "none",
    };
    let ends_words = match c.state {
        State::Paid => format!(
            "Paid until {} — {left} day{} left",
            iso_date(end),
            if left == 1 { "" } else { "s" }
        ),
        _ => format!("The paid time ended on {}", iso_date(end)),
    };
    let action = if c.period == Period::Month {
        plan::PROLONG_ID
    } else {
        plan::RENEW_ID
    };
    let delete_on = s
        .nonpay
        .lapse(product, &fact.tenant.0, end)
        .and_then(|l| l.delete_on_unix_ms)
        .or_else(|| {
            terms
                .nonpay
                .deletes()
                .then(|| end + u64::from(terms.nonpay.delete_after_days) * DAY_MS)
        });
    let grace_end = end + u64::from(terms.policy.grace_days) * DAY_MS;
    let deleted = delete_on
        .map(|d| {
            format!(
                " Unless it is paid, everything in it is deleted on {}.",
                iso_date(d)
            )
        })
        .unwrap_or_default();
    let reminders = terms.nonpay.reminder_days();
    let (banner, tone) = match c.state {
        State::Paid if reminders.iter().any(|d| u64::from(*d) * DAY_MS >= end.saturating_sub(now)) => (
            format!(
                "Your paid time ends on {} — in {left} day{}. {} to keep writing after that.",
                iso_date(end),
                if left == 1 { "" } else { "s" },
                if c.period == Period::Month { "Prolong 1 month" } else { "Renew for 12 months" }
            ),
            "warn",
        ),
        State::Grace => (
            format!(
                "Your paid time ended on {}. Pay by {} to keep pushing — after that the account is read-only.{deleted}",
                iso_date(end),
                iso_date(grace_end)
            ),
            "bad",
        ),
        State::Suspended => (
            format!(
                "This account is read-only since {}: clone and fetch work, pushes are refused, and nothing in it is lost yet.{deleted} Paying restores it at once.",
                iso_date(grace_end)
            ),
            "bad",
        ),
        _ => (String::new(), ""),
    };
    // A 12-month plan's size field: the per-GiB yearly rate, to the end.
    let (bump_words, bump_min_bytes) =
        if c.period == Period::Year && c.state == State::Paid && end > now {
            let rate = u64::try_from(cfg.rule.yearly_gib_month_milli(basis).div_ceil(1000))
                .unwrap_or(u64::MAX);
            match convert(cfg, rate, to) {
                Some(m) => (
                    format!(
                        "Any bigger size: {} per GiB per month, until {}",
                        m.in_words(),
                        iso_date(end)
                    ),
                    c.size_bytes + GIB,
                ),
                None => (String::new(), 0),
            }
        } else {
            (String::new(), 0)
        };
    let progress = s
        .ledger
        .attempt(&fact.plan)
        .ok()
        .flatten()
        .filter(|a| a.purchase.plan.is_some())
        .map(|_| crate::services::purchase::room_progress(s, product, &fact.plan));
    let (progress, progress_reference) = match progress {
        Some(p) if p.state != "done" => (Some(p), fact.plan.clone()),
        _ => (None, String::new()),
    };
    pb::PlanStatus {
        state: state.into(),
        period: c.period.name().into(),
        label: plan::label(c.size_bytes, c.period),
        cap_bytes: c.size_bytes,
        paid_until_unix_ms: end,
        days_left: left,
        days_total: plan::days_total(c.period, end, now),
        ends_words,
        price_words: c
            .month_price
            .map(|m| format!("{} per month", m.in_words()))
            .unwrap_or_default(),
        banner: banner.clone(),
        banner_action: if banner.is_empty() {
            String::new()
        } else {
            action.to_owned()
        },
        banner_tone: tone.into(),
        bump_words,
        bump_min_bytes,
        progress,
        progress_reference,
    }
}

#[cfg(test)]
mod tests;