donation item, shop soft open
All checks were successful
Deploy / build-deploy (push) Successful in 4m11s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-08-17 11:04:03 +02:00
commit abbd616b40
23 changed files with 2898 additions and 209 deletions

View file

@ -200,6 +200,13 @@ export struct Product {
// gap, price swing). In both closed states the page stays up, the buy
// form does not, and the checkout POST is refused server-side.
std::string status;
// A donation rather than goods: the buyer names the amount, nothing ships,
// and no VAT is charged (a gift with nothing supplied in return is not a
// taxable supply). This flag is what routes a checkout POST through the
// donation validator instead of the address-and-shipping one, so setting
// it on a priced product would let orders skip shipping — it belongs on
// exactly one kind of catalogue entry.
bool donation = false;
// The EU consumer price, VAT-inclusive, in cents. With variants present
// this is the FROM price (cheapest variant) and is kept in sync by the
// loader; without variants it is simply the price.
@ -226,7 +233,11 @@ export struct Product {
std::string safetyNote;
std::vector<Spec> specs;
bool Buyable() const { return status == "available" && priceInclMinor > 0; }
// A donation has no fixed price by definition, so the price check applies
// only to goods — where a zero price is a content bug that must not sell.
bool Buyable() const {
return status == "available" && (donation || priceInclMinor > 0);
}
bool ComingSoon() const { return status == "coming-soon"; }
// nullptr for a colour we never listed — the checkout rejects rather than
@ -381,6 +392,10 @@ export struct OrderView {
std::int64_t shippingMinor = 0;
std::int64_t totalMinor = 0;
bool vatIncluded = false;
// A donation order: one amount, nothing ships, no VAT, no invoice. The
// renderer folds the money table to a single row and swaps the paid-state
// copy from "your device ships" to a thank-you.
bool donation = false;
// Present only for an awaiting order on a rail without a hosted checkout;
// the page then renders instructions instead of a "resume payment" button.
std::optional<OrderCryptoPay> cryptoPay;