1. GET /api/checkout/payment-status/<id> (the Mini App's "Перевірити
оплату" poll, used before the Monobank webhook arrives) marked an
order paid but skipped everything the webhook and reconciliation
paths do afterward — no fiscal receipt, no staff notification.
Confirmed by cross-checking every other payment_status="paid" call
site in the codebase (api/payments.py, admin/routes.py x3,
utils/reconciliation.py) — all of them call fiscalize_in_background;
this was the only one that didn't. Added the missing staff-chat
notification and fiscalization call, matching the webhook's pattern.
(Skipped the customer Telegram message the webhook also sends — the
customer is already looking at the Mini App screen that just told
them "paid", a duplicate ping there would be noise, not signal.)
2. bober_bbq/utils/supplier_import.py's discard_upload/load_preview/
parse_rows all built a path as `TMP_DIR / token`, where `token` is a
client-submitted hidden form field round-tripped across the
preview/confirm steps — not re-validated as one of save_upload()'s
own uuid4-named files. Path("/some/dir") / "/etc/passwd" evaluates
to "/etc/passwd" (an absolute right-hand side silently discards the
left), so a crafted token let any authenticated suppliers-access
user delete (via discard_upload's error-path cleanup) or, if the
extension happened to match, read the contents of an arbitrary file
on the server. Added _resolve_upload_path() — validates the token
against the exact <uuid4 hex><supported extension> shape and that
the resolved path's parent is still TMP_DIR — used by all three
functions instead of the raw join.
Verified with two smoke tests: 22 checks confirming 6 different attack
payloads (absolute paths both slash styles, relative traversal both
separator styles, a malformed-but-real-extension token, an empty
token) are all rejected without touching a planted victim file, while
a real save_upload()/discard_upload() round trip still works exactly
as before; and 8 checks confirming the checkout endpoint now fiscalizes
and notifies staff on the first paid poll, and does neither again on a
second poll (no double-fiscalization/double-notification). Plus a full
regression rerun of the session's other smoke suites.