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/test_purchase.rs

//! **The operator's TEST BUTTON** (owner 2026-10-11): "monetize needs a test
//! button that creates a real account, fires up exactly like from the signup
//! page … logs from setup need to go to monetize … when this works, no AI is
//! involved setting up".
//!
//! ```text
//!  console ──TestPurchase.Start{product, action}──▶ run(…)
//!     the PRODUCT declared the action (Product::test_actions, data only)
//!     a test identity: test-<stamp> (+ tb<stamp> for an instance), the configured test mailbox or none
//!     hosted_plan      ──▶ services::signup::start_signup(PayBy::TestPayment)   ← THE sign-up page's path
//!     private_instance ──▶ a visitor's order: its account made when it is paid (A33-3)
//!                          services::estate_order::start_visitor_order (A33: EstateOrder.Start{new_account}'s path)
//!     the money: a TEST key — the vendor's test payment method; a LIVE key (ST-8) — the SAME
//!                real flow, a real checkout the operator pays himself ("LIVE — you will be
//!                charged", confirmed on the press); a key that cannot say — refused
//!     the site: one offer per (action, site); a press sells as the site it names, only
//!     the setup: the order's phases and the installer's lines (estate_order::setup_log)
//! ```
//!
//! Generic on purpose: nothing here names a product. What a product offers is
//! its own declaration; what it costs is the shop's config; how it is paid is
//! the routed vendor's.

use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use monetize_product::{TestAction, TestActionKind};
use monetize_proto as pb;
use tonic::Status;

use crate::estate_order::{self, EstateSizes};
use crate::services::signup::{start_signup, PayBy, NOT_A_TEST_KEY};
use crate::state::AppState;

/// The file the runs are kept in, under `data_dir`.
pub const RUNS_FILE: &str = "test-purchases.json";

/// At most this many runs are kept (oldest dropped).
pub const MAX_RUNS: usize = 200;

/// Where the test identity's mail goes when no mailbox is configured: a domain
/// that by RFC 2606 never receives mail. Never a real person's address.
pub const NO_MAILBOX_DOMAIN: &str = "test-purchase.invalid";

/// One line of what a run did.
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RunLine {
    pub at_unix_ms: u64,
    pub source: String,
    pub line: String,
}

/// **One press of a test button**, as kept.
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct TestRun {
    pub id: String,
    pub product: String,
    pub action: String,
    pub kind: String,
    pub label: String,
    pub username: String,
    pub company: String,
    pub vendor: String,
    pub session: String,
    /// The account's own signup session (a private instance's account step).
    pub account_session: String,
    pub started_unix_ms: u64,
    /// Set when the run could not even start: the refusal, in words.
    pub refused: String,
    pub mailbox: String,
    pub lines: Vec<RunLine>,
    /// The site (estate) the run sold as; empty: the product's home (no declared site).
    pub site: String,
    /// LIVE (ST-8): a real purchase; [`TestRun::pay_url`] is the vendor's own checkout page.
    pub live: bool,
    pub pay_url: String,
}

/// **Every test run this monetize made** — in memory, and in
/// `<data_dir>/`[`RUNS_FILE`] when served.
#[derive(Default)]
pub struct TestRuns {
    path: Option<PathBuf>,
    runs: Mutex<Vec<TestRun>>,
}

impl TestRuns {
    pub fn in_memory() -> TestRuns {
        TestRuns::default()
    }

    /// The runs kept at `path`; none yet (or unreadable, said) is an empty book.
    pub fn open(path: PathBuf) -> TestRuns {
        let runs = match std::fs::read(&path) {
            Ok(b) => serde_json::from_slice(&b).unwrap_or_else(|e| {
                eprintln!(
                    "monetize-server: {} does not parse ({e}); the test runs start empty",
                    path.display()
                );
                Vec::new()
            }),
            Err(_) => Vec::new(),
        };
        TestRuns {
            path: Some(path),
            runs: Mutex::new(runs),
        }
    }

    fn keep(&self, runs: &[TestRun]) {
        let Some(path) = &self.path else { return };
        let tmp = path.with_extension("json.tmp");
        let written = serde_json::to_vec_pretty(runs)
            .map_err(std::io::Error::other)
            .and_then(|b| std::fs::write(&tmp, b))
            .and_then(|()| std::fs::rename(&tmp, path));
        if let Err(e) = written {
            eprintln!(
                "monetize-server: the test runs were not kept at {}: {e}",
                path.display()
            );
        }
    }

    /// Add or replace (by id).
    pub fn put(&self, run: TestRun) {
        let mut runs = self.runs.lock().unwrap_or_else(|p| p.into_inner());
        match runs.iter_mut().find(|r| r.id == run.id) {
            Some(r) => *r = run,
            None => runs.push(run),
        }
        let excess = runs.len().saturating_sub(MAX_RUNS);
        runs.drain(..excess);
        self.keep(&runs);
    }

    pub fn get(&self, id: &str) -> Option<TestRun> {
        self.runs
            .lock()
            .unwrap_or_else(|p| p.into_inner())
            .iter()
            .find(|r| r.id == id)
            .cloned()
    }

    /// Newest first; `limit` 0 = all.
    pub fn list(&self, limit: usize) -> Vec<TestRun> {
        let runs = self.runs.lock().unwrap_or_else(|p| p.into_inner());
        let n = if limit == 0 {
            runs.len()
        } else {
            limit.min(runs.len())
        };
        runs.iter().rev().take(n).cloned().collect()
    }
}

/// Pure: `yymmddhhmmss` of `ms` (UTC) — the stamp a test identity carries.
pub fn stamp(ms: u64) -> String {
    let date: String = estate_order::iso_date(ms)
        .chars()
        .filter(char::is_ascii_digit)
        .collect();
    let secs = (ms / 1000) % 86_400;
    format!(
        "{}{:02}{:02}{:02}",
        &date[2..],
        secs / 3600,
        (secs / 60) % 60,
        secs % 60
    )
}

/// **The test identity of a run**: `(username, company, email)`. The email is
/// the configured mailbox plus-addressed with the username, else an address at
/// [`NO_MAILBOX_DOMAIN`] — never anybody's real address.
pub fn identity(stamp: &str, mailbox: &str) -> (String, String, String) {
    let username = format!("test-{stamp}");
    let company = format!("tb{stamp}");
    let email = match mailbox.trim().split_once('@') {
        Some((local, domain)) if !local.is_empty() && !domain.is_empty() => {
            format!("{local}+{username}@{domain}")
        }
        _ => format!("{username}@{NO_MAILBOX_DOMAIN}"),
    };
    (username, company, email)
}

fn kind_word(k: &TestActionKind) -> &'static str {
    match k {
        TestActionKind::HostedPlan { .. } => "hosted_plan",
        TestActionKind::PrivateInstance { .. } => "private_instance",
    }
}

/// Pure on the config: the empty site when `site` names none.
fn site_opt(site: &str) -> Option<&str> {
    Some(site.trim()).filter(|e| !e.is_empty())
}

/// **The sites `product` is sold as** — `(estate, host)` per declared `[estates.<estate>]`,
/// in name order; one unnamed `("", "")` (its home) when it is sold for no declared site.
pub fn sites(s: &AppState, product: &str) -> Vec<(String, String)> {
    let declared = s.registry.sites_of(product);
    if declared.is_empty() {
        vec![(String::new(), String::new())]
    } else {
        declared
    }
}

/// **The vendor a hosted plan of `product` is paid through on `site`**: the site's route.
fn hosted_vendor<'a>(
    s: &'a AppState,
    product: &str,
    site: &str,
) -> Option<monetize_pay::Vendor<'a>> {
    s.registry
        .find_vendor(&s.registry.route_for(product, site_opt(site)).ok()?)
}

/// **The vendor a private instance sold on `site` is paid through**: that site's own account
/// (ST-5, [`crate::estate_order::terms_on`]), else `[estate_orders] vendor`.
fn instance_vendor<'a>(s: &'a AppState, site: &str) -> Option<monetize_pay::Vendor<'a>> {
    let terms = crate::estate_order::terms_on(s, s.estates.cfg.as_ref()?, site_opt(site)).ok()?;
    s.registry.find_vendor(&terms.vendor)
}

/// Pure on its inputs: `Ok(live)` — the key is LIVE (a real purchase the operator pays
/// himself) or TEST (the vendor's test payment method); `Err` why the button cannot run.
/// The key is read through the ONE check `site test-order` reads it through (A33-4,
/// [`estate_order::site::key_kind`]); `needs_payer`: a TEST key pays only with its test card.
fn key_verdict(
    vendor: Option<monetize_pay::Vendor<'_>>,
    needs_payer: bool,
) -> Result<bool, String> {
    use estate_order::site::KeyKind;
    let Some(v) = vendor else {
        return Err("no vendor is routed for it here".into());
    };
    match estate_order::site::key_kind(v) {
        KeyKind::Live => Ok(true),
        KeyKind::Test(None) if needs_payer => Err(format!(
            "{} has no test payment method: nothing can pay without a person",
            v.name
        )),
        KeyKind::Test(_) => Ok(false),
        KeyKind::Unknown => Err(format!("{}: {NOT_A_TEST_KEY}", v.name)),
    }
}

/// **The test card a private instance's TEST press holds with**: the same one check.
fn instance_test_card(s: &AppState, site: &str) -> Option<String> {
    match estate_order::site::key_kind(instance_vendor(s, site)?) {
        estate_order::site::KeyKind::Test(card) => card,
        _ => None,
    }
}

/// The words a LIVE press carries — on the button, the refusal and the run.
pub const LIVE_WORDS: &str = "LIVE — you will be charged";

/// The account plan of `product`: no price, no room.
fn account_plan(s: &AppState, product: &str) -> Option<String> {
    s.signup_plans
        .snapshot_of(product)
        .into_iter()
        .find(|(_, p)| p.amount_minor == 0 && p.cap_bytes == 0)
        .map(|(id, _)| id)
}

/// **The plan the sign-up page offers NOW for `cap_gib` of room**: a priced
/// room where this deployment prices plans, else the configured table — the
/// shortest period, then the cheapest.
fn hosted_plan(s: &Arc<AppState>, product: &str, cap_gib: u64) -> Option<String> {
    let cap = cap_gib << 30;
    let market = s
        .terms
        .read()
        .unwrap_or_else(|e| e.into_inner())
        .default_market;
    let mut offered: Vec<(String, crate::config::SignupPlan)> =
        match crate::plans::signup_rooms(s, product, market) {
            Some(rooms) => rooms.into_iter().map(|r| (r.id, r.plan)).collect(),
            None => s
                .signup_plans
                .snapshot_of(product)
                .into_iter()
                .filter(|(_, p)| p.amount_minor > 0)
                .collect(),
        };
    offered.retain(|(_, p)| p.cap_bytes == cap);
    offered.sort_by_key(|(id, p)| (p.period_days, p.amount_minor, id.clone()));
    offered.into_iter().next().map(|(id, _)| id)
}

/// **Every product's declared test actions**, one per site it is sold as, each with whether
/// it can run now and whether its key is LIVE.
pub fn actions(s: &AppState, product: &str) -> Vec<pb::TestActionOffer> {
    let mut out = Vec::new();
    for p in s
        .registry
        .products()
        .filter(|p| product.is_empty() || p.id() == product)
    {
        for (site, host) in sites(s, p.id()) {
            for a in p.test_actions() {
                let verdict = match &a.kind {
                    TestActionKind::HostedPlan { .. } => key_verdict(hosted_vendor(s, p.id(), &site), false),
                    TestActionKind::PrivateInstance { .. } if s.estates.cfg.is_none() => Err("this monetize sells no private instances ([estate_orders] is absent)".into()),
                    TestActionKind::PrivateInstance { .. } if account_plan(s, p.id()).is_none() => Err(format!("{} has no account plan (amount 0, no room) to make the buyer's account with", p.id())),
                    TestActionKind::PrivateInstance { .. } => key_verdict(instance_vendor(s, &site), true),
                };
                let (cap_gib, sizes) = match &a.kind {
                    TestActionKind::HostedPlan { cap_gib } => (*cap_gib, None),
                    TestActionKind::PrivateInstance {
                        front_plan,
                        main_plan,
                        main_data_gib,
                    } => (
                        0,
                        Some(pb::EstateSizes {
                            front_plan: front_plan.clone(),
                            main_plan: main_plan.clone(),
                            main_data_gib: *main_data_gib,
                            ..Default::default()
                        }),
                    ),
                };
                let words = match &verdict {
                    Ok(live) => {
                        let price = match &a.kind {
                            TestActionKind::HostedPlan { cap_gib } => s
                                .signup_plans
                                .snapshot_of(p.id())
                                .into_iter()
                                .filter(|(_, pl)| {
                                    pl.amount_minor > 0 && pl.cap_bytes == cap_gib << 30
                                })
                                .min_by_key(|(id, pl)| {
                                    (pl.period_days, pl.amount_minor, id.clone())
                                })
                                .map(|(_, pl)| pl.price().in_words()),
                            TestActionKind::PrivateInstance { .. } => None,
                        };
                        offer_words(*live, &host, price)
                    }
                    Err(_) => String::new(),
                };
                out.push(pb::TestActionOffer {
                    words,
                    product: p.id().to_owned(),
                    id: a.id.clone(),
                    label: a.label.clone(),
                    kind: kind_word(&a.kind).into(),
                    cap_gib,
                    sizes,
                    runnable: verdict.is_ok(),
                    live: verdict.as_ref().is_ok_and(|live| *live),
                    why_not: verdict.err().unwrap_or_default(),
                    site: site.clone(),
                    site_host: host.clone(),
                });
            }
        }
    }
    out
}

/// Pure: ★ A33-11 — **the ONE sentence a button says**, from its key verdict: a LIVE key's press
/// is a real purchase the operator pays at the vendor's page; a TEST key's is paid with the
/// vendor's test card and moves no money. `price`: the plan's, in words, where it is known.
pub fn offer_words(live: bool, site_host: &str, price: Option<String>) -> String {
    let at = if site_host.trim().is_empty() {
        "its home site"
    } else {
        site_host.trim()
    };
    if live {
        format!(
            "Real purchase on {at}: real machines, real money — you pay {} at the vendor's page.",
            price.unwrap_or_else(|| "its price".into())
        )
    } else {
        format!("Test purchase on {at}: real machines, paid with the vendor's test card — no money moves.")
    }
}

fn line(s: &AppState, run: &mut TestRun, source: &str, words: String) {
    eprintln!("monetize-server: test run {}: {source}: {words}", run.id);
    run.lines.push(RunLine {
        at_unix_ms: s.now(),
        source: source.into(),
        line: words,
    });
}

/// **Press one button**, for one site. Refused BEFORE anything is made when the action is
/// not the product's, the site is not one it is sold as, the key cannot say what it is, or
/// the key is LIVE and the operator did not confirm [`LIVE_WORDS`]. Otherwise the run is
/// kept whatever happens next, with its lines. On a TEST key the vendor's test payment
/// method pays; on a LIVE key the SAME real flow mints a real checkout and the run hands the
/// operator its page ([`TestRun::pay_url`]) — no test card, no settle but the vendor's own.
pub fn start(
    s: &Arc<AppState>,
    product: &str,
    action: &str,
    site: &str,
    live_ok: bool,
) -> Result<TestRun, Status> {
    let p = s
        .registry
        .find_product(product)
        .ok_or_else(|| Status::not_found(format!("product {product:?} is not served here")))?;
    let a: TestAction = p
        .test_actions()
        .iter()
        .find(|a| a.id == action)
        .cloned()
        .ok_or_else(|| {
            Status::not_found(format!("{product} declares no test action {action:?}"))
        })?;
    let site = site.trim().to_owned();
    let declared = sites(s, product);
    if site.is_empty() && s.registry.sold_per_site(product) {
        return Err(Status::failed_precondition(format!(
            "REFUSED [test-site-unnamed] {product} is sold for several sites here ({}): a press names the site it sells as, and is never sold as another",
            declared.iter().map(|(e, h)| format!("{e} = {h}")).collect::<Vec<_>>().join(", ")
        )));
    }
    let Some((_, host)) = declared.iter().find(|(e, _)| *e == site).cloned() else {
        return Err(Status::failed_precondition(format!("REFUSED [test-site-undeclared] {site:?} is not a site {product} is sold as here: nothing is bought for it")));
    };
    // Refused before anything is made: the same verdict `Actions` shows.
    let offer = actions(s, product)
        .into_iter()
        .find(|o| o.id == action && o.site == site);
    if let Some(offer) = &offer {
        if !offer.runnable {
            return Err(Status::failed_precondition(offer.why_not.clone()));
        }
        if offer.live && !live_ok {
            return Err(Status::failed_precondition(format!(
                "{}: {LIVE_WORDS}. This site's vendor key is live: a press is a real purchase, paid by you at the vendor's own page. Confirm it on the press.",
                if host.is_empty() { product } else { host.as_str() }
            )));
        }
    }
    let live = offer.as_ref().is_some_and(|o| o.live);
    let now = s.now();
    let st = stamp(now);
    let mailbox = s
        .config
        .read()
        .unwrap_or_else(|e| e.into_inner())
        .test_mailbox
        .clone();
    let (username, company, email) = identity(&st, &mailbox);
    let mut run = TestRun {
        id: format!("tp-{st}"),
        product: product.to_owned(),
        action: a.id.clone(),
        kind: kind_word(&a.kind).into(),
        label: a.label.clone(),
        username: username.clone(),
        started_unix_ms: now,
        mailbox: if mailbox.trim().is_empty() {
            String::new()
        } else {
            monetize_mail::masked(&email)
        },
        site: site.clone(),
        live,
        ..TestRun::default()
    };
    // Its pages on ITS site: the site's own welcome page, else the configured test page.
    let console = s
        .config
        .read()
        .unwrap_or_else(|e| e.into_inner())
        .test_return_url
        .clone();
    let success_url = if !host.is_empty() {
        format!("https://{host}/welcome?session={{session}}")
    } else if !console.is_empty() {
        console
    } else {
        format!("https://{NO_MAILBOX_DOMAIN}/welcome?session={{session}}")
    };
    let cancel_url = if live && !host.is_empty() {
        format!("https://{host}/signup")
    } else {
        String::new()
    };
    let signup = |plan: &str, pay: PayBy| {
        let r = pb::StartSignupRequest {
            username: username.clone(),
            email: email.clone(),
            plan: plan.to_owned(),
            product: product.to_owned(),
            vendor: String::new(),
            success_url: success_url.clone(),
            cancel_url: cancel_url.clone(),
            market: 0,
        };
        // The site the operator named — never another; the home where none is declared.
        let estate = site_opt(&site)
            .map(str::to_owned)
            .or_else(|| s.registry.home_of(product));
        start_signup(s, &r, pay, estate.as_deref())
    };
    let outcome: Result<(), String> = match &a.kind {
        TestActionKind::HostedPlan { cap_gib } => (|| {
            let plan = hosted_plan(s, product, *cap_gib)
                .ok_or_else(|| format!("{product} offers no plan of {cap_gib} GiB now"))?;
            run.vendor = hosted_vendor(s, product, &site)
                .map(|v| v.name.to_owned())
                .unwrap_or_default();
            if live {
                line(s, &mut run, "signup", format!("{username} buys {plan} through the sign-up path: {LIVE_WORDS}, at the vendor's own page"));
                let out = signup(&plan, PayBy::Checkout).map_err(|e| e.message().to_owned())?;
                run.session = out.session.clone();
                run.pay_url = out.url.clone();
                let said = format!(
                    "{} minted a real checkout ({}): pay it yourself at {}",
                    run.vendor, out.session, out.url
                );
                line(s, &mut run, "vendor", said);
            } else {
                line(s, &mut run, "signup", format!("{username} buys {plan} through the sign-up path, paid with the vendor's test payment method"));
                let out = signup(&plan, PayBy::TestPayment).map_err(|e| e.message().to_owned())?;
                run.session = out.session.clone();
                let said = format!("{} took the test payment ({})", run.vendor, out.session);
                line(s, &mut run, "vendor", said);
            }
            Ok(())
        })(),
        TestActionKind::PrivateInstance {
            front_plan,
            main_plan,
            main_data_gib,
        } => (|| {
            // ★ A33-1/A33-3: the press is a VISITOR's order through `EstateOrder.Start{new_account}`'s
            // own path — the name and the address judged as a signup's, the name held, the account
            // made when the order is PAID — never an account made up front. ★ A33-2: on a TEST key
            // the vendor's test card pays the hold (data on the order), on a LIVE key the operator
            // pays at the vendor's page, as a visitor does.
            let test_payer = if live {
                String::new()
            } else {
                instance_test_card(s, &site).ok_or("no test payment method")?
            };
            let sizes = EstateSizes {
                front_plan: front_plan.clone(),
                main_plan: main_plan.clone(),
                main_data_gib: *main_data_gib,
                ..Default::default()
            };
            run.company = company.clone();
            let how = if live {
                format!("{LIVE_WORDS}, at the vendor's own page")
            } else {
                "held with the vendor's test payment method".into()
            };
            line(s, &mut run, "order", format!("{company} ordered for {username} as a visitor's order (its account made when it is paid), {how}"));
            let req = pb::EstateOrderStartRequest {
                company: company.clone(),
                email: email.clone(),
                account: username.clone(),
                new_account: true,
                welcome_url: success_url.clone(),
                cancel_url: cancel_url.clone(),
                ..Default::default()
            };
            let r = crate::services::estate_order::start_visitor_order(
                s,
                site_opt(&site),
                &req,
                &sizes,
                &test_payer,
            )
            .map_err(|e| e.message().to_owned())?;
            run.account_session = r
                .new_account
                .as_ref()
                .map(|na| na.session.clone())
                .unwrap_or_default();
            run.vendor = r
                .handle
                .as_ref()
                .map(|h| h.vendor.clone())
                .unwrap_or_default();
            run.session = r.handle.as_ref().map(|h| h.id.clone()).unwrap_or_default();
            let said = if live {
                run.pay_url = r.checkout_url.clone();
                format!(
                    "{} minted a real checkout of {} for {} ({}): pay it yourself at {}",
                    run.vendor,
                    r.amount.in_words(),
                    r.reference,
                    run.session,
                    r.checkout_url
                )
            } else {
                format!(
                    "{} holds {} for {} with the test payment method ({})",
                    run.vendor,
                    r.amount.in_words(),
                    r.reference,
                    run.session
                )
            };
            line(s, &mut run, "vendor", said);
            Ok(())
        })(),
    };
    if let Err(why) = outcome {
        run.refused = why.clone();
        line(s, &mut run, "run", format!("stopped: {why}"));
    }
    s.tests.put(run.clone());
    Ok(run)
}

/// The signup's state word and the reason a refused one carries.
fn signup_word(s: &AppState, vendor: &str, session: &str) -> (String, String) {
    if session.is_empty() {
        return (String::new(), String::new());
    }
    match s.signups.get(vendor, session) {
        Ok(Some(rec)) => match rec.state {
            crate::signup::SignupState::Pending => ("PENDING".into(), String::new()),
            crate::signup::SignupState::Created { .. } => ("CREATED".into(), String::new()),
            crate::signup::SignupState::Refused { reason, .. } => ("REFUSED".into(), reason),
        },
        Ok(None) => ("UNKNOWN".into(), String::new()),
        Err(e) => ("UNKNOWN".into(), format!("signup store: {e}")),
    }
}

/// **A run as the console reads it**: its own lines, the order's phases and the
/// installer's lines, oldest first, and the one word for where it stands.
pub fn view(s: &AppState, run: &TestRun) -> pb::TestPurchaseRun {
    let mut lines: Vec<pb::TestPurchaseLine> = run
        .lines
        .iter()
        .map(|l| pb::TestPurchaseLine {
            at_unix_ms: l.at_unix_ms,
            source: l.source.clone(),
            line: l.line.clone(),
        })
        .collect();
    let (mut state, mut words, mut phase) = ("running".to_string(), String::new(), String::new());
    let signup_state;
    if run.kind == "hosted_plan" {
        let (w, why) = signup_word(s, &run.vendor, &run.session);
        match w.as_str() {
            "CREATED" if run.live => {
                (state, words) = (
                    "done".into(),
                    format!("{} exists, paid for real ({LIVE_WORDS})", run.username),
                )
            }
            "CREATED" => {
                (state, words) = (
                    "done".into(),
                    format!(
                        "{} exists, paid with {}",
                        run.username,
                        if run.live { "real money" } else { "test money" }
                    ),
                )
            }
            "REFUSED" => (state, words) = ("failed".into(), why),
            _ if run.live => {
                words = format!("{LIVE_WORDS}: waiting for your payment at the vendor's page")
            }
            _ => words = "the account is being made".into(),
        }
        signup_state = w;
    } else {
        let acct_vendor = crate::signup::ACCOUNT_VENDOR;
        signup_state = signup_word(s, acct_vendor, &run.account_session).0;
        if let Some(r) = s.estates.get(&run.company).filter(|r| r.test || run.live) {
            phase = r.phase.clone();
            lines.extend(r.history.iter().map(|t| pb::TestPurchaseLine {
                at_unix_ms: t.at_ms,
                source: "order".into(),
                line: if t.phase.is_empty() {
                    t.note.clone()
                } else {
                    format!("{}: {}", t.phase, t.note)
                },
            }));
            if let Some(dir) = s.estates.run_dir(&run.company) {
                lines.extend(
                    estate_order::setup_log::read_tail(&dir, 400)
                        .into_iter()
                        .map(|l| pb::TestPurchaseLine {
                            at_unix_ms: l.at_unix_ms,
                            source: l.source,
                            line: l.line,
                        }),
                );
            }
            use estate_order::phase as ph;
            match r.phase.as_str() {
                p if ph::is_term(p) => {
                    (state, words) = (
                        "done".into(),
                        format!(
                            "{} is set up and captured, paid with {}",
                            r.company,
                            if run.live { "real money" } else { "test money" }
                        ),
                    )
                }
                ph::OWED | ph::RELEASED | ph::REFUNDED | ph::DELETED => {
                    (state, words) = ("failed".into(), r.why.clone())
                }
                ph::FAILED => words = format!("failed, retried by the driver: {}", r.why),
                p => {
                    words = format!(
                        "{p}{}",
                        if r.queue_why.is_empty() {
                            String::new()
                        } else {
                            format!(" — {}", r.queue_why)
                        }
                    )
                }
            }
        }
    }
    if !run.refused.is_empty() {
        (state, words) = ("failed".into(), run.refused.clone());
    }
    lines.sort_by_key(|l| l.at_unix_ms);
    pb::TestPurchaseRun {
        id: run.id.clone(),
        product: run.product.clone(),
        action: run.action.clone(),
        kind: run.kind.clone(),
        label: run.label.clone(),
        username: run.username.clone(),
        company: run.company.clone(),
        vendor: run.vendor.clone(),
        session: run.session.clone(),
        started_unix_ms: run.started_unix_ms,
        state,
        words,
        signup_state,
        phase,
        lines,
        mailbox: run.mailbox.clone(),
        site: run.site.clone(),
        pay_url: run.pay_url.clone(),
        live: run.live,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A fresh directory, removed when dropped.
    struct TempDir(std::path::PathBuf);
    impl TempDir {
        fn new(name: &str) -> TempDir {
            let d = std::env::temp_dir().join(format!(
                "monetize-{name}-{}-{}",
                std::process::id(),
                crate::bus::now_unix_ms()
            ));
            std::fs::create_dir_all(&d).unwrap();
            TempDir(d)
        }
        fn path(&self) -> &std::path::Path {
            &self.0
        }
    }
    impl Drop for TempDir {
        fn drop(&mut self) {
            let _ = std::fs::remove_dir_all(&self.0);
        }
    }

    /// A vendor double that only says what its key is.
    struct Keyed {
        live: Option<bool>,
        payer: Option<&'static str>,
    }
    impl monetize_pay::payment::Payment for Keyed {
        fn manifest(&self) -> monetize_pay::payment::PayManifest {
            monetize_pay::payment::PayManifest {
                vendor: "keyed".into(),
                markets: Vec::new(),
                settings: Vec::new(),
                holds: true,
                test_payer: self.payer.map(str::to_owned),
            }
        }
        fn page(
            &self,
            _: &monetize_pay::payment::Charge<'_>,
            _: &monetize_pay::payment::Return,
        ) -> Result<monetize_pay::payment::Page, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn status(
            &self,
            _: &monetize_pay::payment::Of<'_>,
        ) -> Result<monetize_pay::payment::Standing, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn capture(
            &self,
            _: &monetize_pay::ChargeHandle,
        ) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn release(
            &self,
            _: &monetize_pay::ChargeHandle,
        ) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn refund(
            &self,
            _: &monetize_pay::ChargeHandle,
            _: monetize_pay::Money,
        ) -> Result<monetize_pay::ChargeStatus, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn callback(
            &self,
            _: monetize_pay::RawCallback<'_>,
        ) -> Result<monetize_pay::CallbackEvent, monetize_pay::PayError> {
            unreachable!("only the key is read")
        }
        fn settlement(&self, _: &monetize_pay::ChargeHandle) -> Option<monetize_pay::Settlement> {
            None
        }
        fn paid_since(
            &self,
            _: i64,
        ) -> Result<Vec<monetize_pay::PaidAtVendor>, monetize_pay::PayError> {
            Ok(Vec::new())
        }
        fn livemode(&self) -> Option<bool> {
            self.live
        }
    }

    /// ★ **A33-4: ONE key check for the test button and the site test-order**: the private
    /// instance's button runs on a TEST key exactly when `site test-order` has a test card to pay
    /// with (a blank test payment method is none), on a LIVE key as a real purchase, and never on
    /// a key that cannot say which it is. The button reads the key through the same check.
    #[test]
    fn the_test_button_and_the_site_test_order_read_the_key_through_one_check() {
        let keys = [
            (Some(true), None),
            (Some(true), Some("pm_card_visa")),
            (Some(false), Some("pm_card_visa")),
            (Some(false), Some("  ")),
            (Some(false), None),
            (None, Some("pm_card_visa")),
            (None, None),
        ];
        for (live, payer) in keys {
            let pay = Keyed { live, payer };
            let v = monetize_pay::Vendor::of(&pay);
            let site_card = crate::estate_order::site::test_card_of(v, "acme", "keyed");
            let button = key_verdict(Some(v), true);
            assert_eq!(button == Ok(false), site_card.is_ok(), "{live:?} {payer:?}: the button on a test key runs exactly when the site test-order has a card: {button:?} / {site_card:?}");
            assert_eq!(
                button == Ok(true),
                live == Some(true),
                "{live:?} {payer:?}: {button:?}"
            );
            assert_eq!(
                key_verdict(Some(v), false).is_ok(),
                live.is_some(),
                "{live:?} {payer:?}: a hosted plan needs no card, only a key that says what it is"
            );
        }
        let code = include_str!("test_purchase.rs");
        let code = &code[..code.find("#[cfg(test)]\nmod tests").unwrap()];
        assert!(
            !code.contains(".livemode()") && !code.contains(".test_payer()"),
            "test_purchase reads the key only through estate_order::site's one check"
        );
    }

    /// ★ A33-11: ONE sentence per button, the server's, from the same key verdict — a LIVE key's
    /// press is a real purchase (real money), a TEST key's moves no money; never both.
    #[test]
    fn each_button_says_one_true_sentence_from_its_key() {
        let live = offer_words(true, "test2.gunnar.rs", Some("99.00 SEK".into()));
        assert_eq!(live, "Real purchase on test2.gunnar.rs: real machines, real money — you pay 99.00 SEK at the vendor's page.");
        let test = offer_words(false, "test2.gunnar.rs", Some("99.00 SEK".into()));
        assert_eq!(test, "Test purchase on test2.gunnar.rs: real machines, paid with the vendor's test card — no money moves.");
        assert!(
            !live.contains("test card") && !live.contains("Test ") && !test.contains("real money"),
            "{live} / {test}"
        );
        assert_eq!(offer_words(true, "", None), "Real purchase on its home site: real machines, real money — you pay its price at the vendor's page.");
    }

    #[test]
    fn a_test_identity_is_stamped_and_never_a_real_address_unless_one_is_configured() {
        // 2026-10-11 15:30:12 UTC
        let ms = 1_791_732_612_000;
        assert_eq!(stamp(ms), "261011153012");
        let (u, c, e) = identity("261011153012", "");
        assert_eq!(
            (u.as_str(), c.as_str(), e.as_str()),
            (
                "test-261011153012",
                "tb261011153012",
                "test-261011153012@test-purchase.invalid"
            )
        );
        assert!(monetize_pay::signup::username_refusal(&u).is_none(), "{u}");
        assert!(
            monetize_cloud::lay::company_name_refusal(&c).is_none(),
            "{c}"
        );
        assert!(monetize_pay::signup::email_refusal(&e).is_none(), "{e}");
        let (_, _, e) = identity("261011153012", "ops@shop.example");
        assert_eq!(
            e, "ops+test-261011153012@shop.example",
            "the configured mailbox, plus-addressed per run"
        );
    }

    #[test]
    fn the_runs_are_kept_newest_first_and_survive_a_restart() {
        let dir = TempDir::new("test-runs");
        let path = dir.path().join(RUNS_FILE);
        let book = TestRuns::open(path.clone());
        book.put(TestRun {
            id: "tp-1".into(),
            ..Default::default()
        });
        book.put(TestRun {
            id: "tp-2".into(),
            ..Default::default()
        });
        book.put(TestRun {
            id: "tp-1".into(),
            refused: "x".into(),
            ..Default::default()
        });
        let again = TestRuns::open(path);
        assert_eq!(
            again
                .list(0)
                .iter()
                .map(|r| r.id.as_str())
                .collect::<Vec<_>>(),
            ["tp-2", "tp-1"]
        );
        assert_eq!(again.get("tp-1").unwrap().refused, "x", "replaced by id");
        assert_eq!(again.list(1).len(), 1);
    }
}