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);
}
}