crates/monetize-pay/tests/redirect.rs
//! **A charge's page URL (`Payment::status` of `Of::Page`, read through `Vendor::page_url`), across the vendors that need no socket.**
//!
//! `invoice` answering `None` is the assertion that is worth nothing on its own:
//! `None` is also what a method nobody wrote would return, and it is what a
//! `redirect` that ignored its argument would return for everything. So the two
//! vendors are asserted TOGETHER, in the same test, over the same handles — one
//! that has a checkout page and one that cannot have one. A single
//! implementation cannot satisfy both halves by accident.
//!
//! Both are feature-gated vendors, so this file only exists when both are
//! compiled in (`--all-features`, which the suite runs).
#![cfg(all(feature = "invoice", feature = "sandbox"))]
use monetize_pay::invoice::{InvoiceConfig, InvoiceProvider};
use monetize_pay::sandbox::{SandboxConfig, SandboxProvider};
use monetize_pay::{ChargeHandle, ChargeRequest, Currency, Market, Money, Payment, Vendor};
/// The page URL as the host reads it: through the ten ops.
fn page_url(p: &dyn Payment, h: &ChargeHandle) -> Option<String> {
Vendor::new("t", &[], p).page_url(h)
}
fn request(reference: &str) -> ChargeRequest {
ChargeRequest {
reference: reference.into(),
amount: Money {
minor: 12_500,
currency: Currency::Sek,
},
market: Market::Se,
description: "gunnar pro 2026-09".into(),
payer: None,
}
}
/// (r) A vendor WITH a checkout page and a vendor WITHOUT one, side by side.
///
/// The sandbox mints a URL per order, the way Klarna and Stripe do; the invoice
/// has none, because a bankgiro reference is paid from the customer's own bank
/// and there is no page to send anyone to. Before this method existed monetize
/// built both from the same `checkout_url` template, which meant every invoice
/// sale put a dead link in front of an operator.
#[test]
fn a_vendor_with_a_checkout_page_answers_and_one_without_says_so() {
let sandbox = SandboxProvider::new(SandboxConfig {
callback_secret: "s3cret".into(),
state_path: None,
});
let invoice = InvoiceProvider::new(InvoiceConfig {
import_token: "import-secret".into(),
bankgiro: Some("5050-1055".into()),
});
let sandbox_handle = sandbox.reserve(&request("gunnar/acme/2026-09")).unwrap();
let invoice_handle = invoice.reserve(&request("gunnar/acme/2026-09")).unwrap();
let url = page_url(&sandbox, &sandbox_handle)
.expect("the sandbox rehearses the checkout step, so it has a link");
assert!(
url.ends_with(&sandbox_handle.id),
"the link names THIS order, not a constant: {url}"
);
assert!(
url.starts_with("https://checkout.sandbox.invalid/"),
"and it is under the RFC 2606 `.invalid` TLD, which can never be delegated — a rehearsal must not be able to send a real \
customer anywhere real: {url}"
);
assert_eq!(
page_url(&invoice, &invoice_handle),
None,
"a bankgiro invoice has no checkout page; the customer pays the reference from their own bank"
);
// …and the two are not simply agreeing by accident: the SAME reference gave
// one a link and the other none, and neither answers for a handle it did not
// mint or for another vendor's.
assert_ne!(
page_url(&sandbox, &sandbox_handle),
page_url(&invoice, &invoice_handle)
);
assert_eq!(
page_url(
&sandbox,
&ChargeHandle {
vendor: "sandbox".into(),
id: "sbx_never".into()
}
),
None,
"an order it never minted"
);
assert_eq!(
page_url(
&sandbox,
&ChargeHandle {
vendor: "klarna".into(),
id: sandbox_handle.id.clone()
}
),
None,
"not our vendor"
);
assert_eq!(
page_url(
&invoice,
&ChargeHandle {
vendor: "invoice".into(),
id: "nonsense".into()
}
),
None
);
}
/// (r) The sandbox's link keeps working across the whole money path — reserve,
/// capture, release — because a console re-renders an outcome whenever an
/// operator opens the page, and a link that vanished after capture would look
/// like a bug in the sale.
#[test]
fn the_sandbox_link_is_stable_for_the_life_of_the_charge() {
let sandbox = SandboxProvider::new(SandboxConfig {
callback_secret: "s3cret".into(),
state_path: None,
});
let a = sandbox.reserve(&request("gunnar/one/2026-09")).unwrap();
let b = sandbox.reserve(&request("gunnar/two/2026-09")).unwrap();
assert_ne!(a.id, b.id, "two references, two charges");
let before = page_url(&sandbox, &a).unwrap();
assert_ne!(
Some(before.clone()),
page_url(&sandbox, &b),
"two charges, two links"
);
sandbox.capture(&a).unwrap();
assert_eq!(
page_url(&sandbox, &a),
Some(before.clone()),
"still the same link after capture"
);
sandbox.release(&b).unwrap();
assert!(
page_url(&sandbox, &b).is_some(),
"and a released charge still says where it had been"
);
}