import type { PoolSnapshot } from './budget.js'; import type { CurrencyCode, MinorUnits, OwnerSuppliedText, ShippingStepDown, ShippingTier } from './types.js'; export interface PurchaseFacts { readonly merchantDomain: string; readonly item: OwnerSuppliedText; readonly itemMinorUnits: MinorUnits; readonly taxMinorUnits: MinorUnits; readonly feesMinorUnits: MinorUnits; readonly shippingMinorUnits: MinorUnits; readonly totalMinorUnits: MinorUnits; readonly currency: CurrencyCode; readonly cardLast4: string; readonly shippingTier: ShippingTier; readonly stepDown: ShippingStepDown | null; readonly poolsAfter: PoolSnapshot; /** * Where it is going, rendered from the STORED address. * * A correct total to the wrong address is still a wrong order, and the veto * notice is the last point at which the owner can catch it. Null only when the * checkout asked for no address at all. */ readonly destination?: string | null | undefined; } /** * Render money from integer minor units. * * Throws on a non-integer or negative input rather than rendering something * plausible. A total that did not parse cleanly is not a display problem, it is * a reason to refuse, and the caller must not be able to paper over it by * getting a "0.00" back. */ export declare function formatMinorUnits(amount: MinorUnits, currency: CurrencyCode): string; /** * The approval prompt, ABOVE budget, silence DENIES. * * States the silence rule in the message. The owner should never have to remember * which kind of window they are looking at, and the two say opposite things. */ export declare function renderApprovalMessage(facts: PurchaseFacts, expiresInMinutes: number): string; /** * The veto prompt, WITHIN budget, silence PROCEEDS. * * Also states its silence rule, and states it as the opposite of the approval's, * because the whole design rests on the owner knowing which one they are holding. */ export declare function renderVetoMessage(facts: PurchaseFacts, expiresInMinutes: number): string; /** * The report after an objection. * * One word cancels, and then the owner is told what was stopped and the state * it was left in, never a silent abandonment that leaves them wondering * whether a cart is sitting somewhere half-driven. */ export declare function renderCancellationReport(facts: PurchaseFacts): string; /** * The ONE purchase notice, and the only send site. * * Design rule: showing the purchase to the owner and alerting them when it is * not a major retailer collapse into a single step, since both notify at the * same point. So there is one message, sent once, when the item is chosen and the final total is * known, before payment. The retailer only changes what SILENCE means. * * Both branches carry identical content: what was found, the validated * registrable domain, the item, and the total re-rendered from our own parsed * integers. Never merchant text. * * This exists so the selection is a branch at one call site rather than an * invitation to add a third message type. `renderApprovalMessage` and * `renderVetoMessage` stay separate because their SILENCE RULES are opposite and * must never be unified, see * docs/decisions/2026-07-27-payment-windows-are-deliberately-opposite.md, but * callers should reach for this, not for either of them directly. */ export declare function renderPurchaseNotice(input: { readonly facts: PurchaseFacts; readonly mode: 'approval' | 'veto'; readonly expiresInMinutes: number; /** * Why this merchant qualified, or why it did not, from `classifyMerchant`. * * Always rendered when present, because the verdict alone is not useful to * the owner. "Etsy, buyer protection applies" is something they can weigh at a * glance; "on your approved list" sends them off to go check a list. On the * other side it reads as a checkpoint, not on the list, so I am asking, * and never implies anything is wrong with the seller. * * Sanitised like every other merchant-adjacent string, and prefixed by us so * the framing cannot be supplied by anything a page controls. */ readonly merchantReason?: string | undefined; }): string; /** * The report after the charge goes through. * * ══ Why this is not email-driven ══════════════════════════════════════════ * * The daemon knows it charged the card. It does not need a store to tell it, * and it must not wait for one: a confirmation can take minutes or hours, some * stores send nothing at all, and mail can be broken independently of payments. * A report that depended on any of that would leave the owner with a veto notice, ten * minutes of silence, a charge, and then nothing. * * So this fires at the moment the submit is confirmed, from facts this process * already holds, and every number in it is re-rendered from our own integers. * * The shipping tier is reported as the one ACTUALLY used, with the step-down * spelled out when there was one. The owner approved a purchase; a delivery * option was then chosen inside the budget they set, and they should not learn * which from the parcel arriving later than expected. */ export declare function renderPurchaseReport(input: { readonly facts: PurchaseFacts; /** The merchant's own order reference, when the page showed one. */ readonly merchantOrderId: string | null; /** * Whether a composition read the merchant's own response to the submit. * * False means the click was issued and the card was charged as a * conservative default against a double-spend, but nothing here confirmed * the merchant actually accepted the order. The headline and the closing * line say so plainly rather than reading as a confirmed purchase. */ readonly verified: boolean; }): string; /** * The follow-up once the store's own confirmation turns up. * * ══ The body is never in here ═════════════════════════════════════════════ * * A confirmation email arrives from outside, at the exact moment the owner is * expecting one, which makes it the single most attractive thing for an * attacker to forge. Its body is not rendered, not quoted, and not summarised. * What reaches the owner is a small set of STRUCTURED fields, an order number, a * ship date, a tracking reference, each neutralised, plus our own record of * what was bought, which the email cannot influence at all. * * The email also carries no authority. It cannot confirm that a purchase * happened; our ledger already knows that. All it can do is add a reference * number to a message the owner was going to get anyway. */ export declare function renderConfirmationReport(input: { readonly facts: PurchaseFacts; readonly confirmation: { readonly orderNumber: string | null; readonly shipDate: string | null; readonly trackingReference: string | null; }; /** The registrable domain the mail actually came from, computed by us. */ readonly senderDomain: string; }): string; //# sourceMappingURL=message.d.ts.map