feat(notify): alert the operator over Nostr when a customer is owed cash

cash_owed / partial_pending now sends a NIP-17 gift-wrapped DM (kind 14
rumor → kind 13 seal signed through the operator's signer → kind 1059
wrap from a throwaway key) to the operator's own account pubkey, or to
super_config.alerts_pubkey when set. Best-effort: a failed publish is
logged and the dispense report is still acked; the worklist is the
durable record. No email channel, no NIP-04.
This commit is contained in:
Padreug 2026-10-10 22:26:45 +02:00
commit 8d19da43c2
2 changed files with 212 additions and 0 deletions

View file

@ -80,6 +80,14 @@ def _spawn_distribution(settlement_id: str) -> None:
task.add_done_callback(_inflight.discard)
async def _alert_operator(settlement, machine: Machine, report: DispenseReportIn, status: str) -> None:
"""ADR-005 §6: a human is now owed money — tell the operator over Nostr.
Best-effort; the report is acked regardless (see notify.py)."""
from .notify import notify_cash_outcome
await notify_cash_outcome(settlement, machine, report, status)
async def apply_report_to_settlement(
settlement: DcaSettlement,
report: DispenseReportIn,
@ -118,6 +126,7 @@ async def apply_report_to_settlement(
f"{report.fiat_cents / 100:.2f} {report.currency}) — distributing"
)
elif new_status == "partial_pending":
await _alert_operator(updated or settlement, machine, report, new_status)
logger.warning(
f"spirekeeper: PARTIAL dispense for settlement {settlement.id} "
f"(machine={machine.id}, txid={report.txid}): "
@ -127,6 +136,7 @@ async def apply_report_to_settlement(
f"{report.raw_code or ''}. Held until the operator resolves the shortfall."
)
else:
await _alert_operator(updated or settlement, machine, report, new_status)
logger.error(
f"spirekeeper: CASH OWED — settlement {settlement.id} "
f"(machine={machine.id}, txid={report.txid}): customer paid "