fix: record subscription sale as a signed payment (close off-book hole)
Creating a priced subscription wrote only the mutable `subscriptions`
master row and appended NOTHING to the signed ledger — so the cash an
operator collected showed in the live feed, drawer, and shift Z-report
nowhere, leaving no signed trace. A booth operator could sell
subscriptions and pocket the money untraceably — the exact
operator-as-adversary path the append-only signed ledger exists to close.
Found live: 3 priced subscriptions (27,000 ALL) had zero payment events.
Selling a priced subscription now appends a signed `payment` event at
create time: amount = priceMinor x months (full multi-month prepay),
operator-chosen tender (cash->drawer / card->bank), payload
{ subscriptionSale: true, permitId, operator, months }. Folds into the
shift Z-report/drawer with no new summing logic; the feed badges it
"subscription sale" and resolves the holder name. The create response
returns the recorded { sale }; subscriptionRoutes now takes the EventLog
and ShiftService.
Not hard-gated on an open shift (a sale can happen outside the booth money
path) — it warns instead. The 3 historical off-book sales are not
back-fillable (append-only forbids forging dated events) — reconcile via
cash_movement or a Z-report note.
Verified against a copy of the live DB with the real signing modules:
signed payment appended, hash-chain still verifies, lands in shift cash
totals. Build + lint 12/12.
Wiki: subscription "Collecting the fee" deferred -> BUILT (+ the off-book
hole and why); shift sale-folds-in; threat-model worked example
("store the price != account for the sale").
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
+7
-1
@@ -525,15 +525,21 @@ export type SubscriptionInput = {
|
||||
/** Months paid for: when set (with validFrom), validTo = validFrom + months. */
|
||||
months?: number | null;
|
||||
status?: Subscription["status"];
|
||||
/** How the sale fee was tendered (cash → drawer, card → bank). Used at CREATE when a
|
||||
* price is set (the sale appends a signed payment); ignored on update. */
|
||||
tender?: "cash" | "card";
|
||||
credentials: SubscriptionCredentialInput[];
|
||||
plates: string[];
|
||||
};
|
||||
|
||||
/** The create response = the saved subscription + the auto-print outcome. */
|
||||
/** The create response = the saved subscription + the auto-print outcome, plus the
|
||||
* recorded SALE (the signed payment) when a price was collected. */
|
||||
export type SubscriptionCreated = Subscription & {
|
||||
printed: boolean;
|
||||
printedBy?: string;
|
||||
printError?: string;
|
||||
/** Present when a priced subscription was sold: the signed payment just appended. */
|
||||
sale?: { amountMinor: number; currency: string | null; tender: "cash" | "card"; inShift: boolean };
|
||||
};
|
||||
|
||||
export function fetchSubscriptions(): Promise<{ subscriptions: Subscription[] }> {
|
||||
|
||||
Reference in New Issue
Block a user