Commit graph

8 commits

Author SHA1 Message Date
27f1d5f149 Add pluggable payment-provider layer; implement LiqPay alongside Monobank
Introduces bober_bbq/payments/base.py as the shared provider contract
(ProviderNotConfigured/ProviderError, the atomic claim_order_paid race
guard, and a documented function-shape convention) and
bober_bbq/payments/registry.py for provider selection/dispatch, so
checkout.py, orders.py, and reconciliation.py no longer hardcode
Monobank. monobank.py's public names/behavior are unchanged -- its
exceptions now subclass the generic ones and claim_order_paid is
re-exported from base.py, but every existing import keeps working.

Adds liqpay.py (Checkout/CNB API: base64(JSON)+sha1 signing, form-encoded
webhook, status polling, a local checkout-redirect bridge page since
LiqPay has no server-side "create invoice" call) and
api/payments_liqpay.py for its webhook + redirect routes. Wires a
"payment_provider" setting (admin-configurable, env fallback) and adds
matching LiqPay admin settings fields/test button.

No live LiqPay credentials were available to test against; verification
is a re-derived payment-race regression test (webhook/poll/reconciliation
racing to mark an order paid, for both providers) plus mocked-HTTP
structural checks. Uncertain LiqPay details (verify_token's error-code
heuristic) are flagged in liqpay.py and docs/PAYMENTS.md, which also
documents adding a third provider (Fondy, not implemented).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:38:24 +03:00
759a590022 Alert owner on fiscalization failure; close payment-status race condition
Fiscal errors previously only wrote fiscal_status/fiscal_error and logged,
so an order could sit fiscally unissued indefinitely unless someone opened
its detail page — now routed through the existing error_alerts.notify().

Three independent code paths (Monobank webhook, the Mini App's own
"Перевірити оплату" poll, and the periodic reconciliation job) could all
observe the same "just paid" transition via a read-then-write check and
double up notifications/fiscalization. Added claim_order_paid() as a single
atomic UPDATE ... WHERE payment_status != 'paid', with side effects gated
on whichever caller actually won the transition.
2026-08-27 12:49:17 +03:00
922d9c5172 Make brand identity reusable for redeploying to other clients
Groundwork for reusing this codebase per-client (own VPS/DB/bot per
cafe): the operational config (bot token, DB URL, secrets, Checkbox PRRO
creds) was already .env/Setting-driven, but ~13 spots still hardcoded the
literal string "Bober BBQ" instead of reading the existing cafe_name
Setting, and the webapp's static shell (index.html title, PWA manifest)
had no templating at all since Settings only load after JS boots.

- Route the remaining hardcoded strings through Setting.get("cafe_name",
  "Bober BBQ") — same pattern already used correctly in bot/handlers/
  start.py. Every fallback stays "Bober BBQ", so production is byte-
  identical with no cafe_name override.
- Template webapp/index.html via Vite's native %VITE_CAFE_NAME% HTML
  replacement, backed by a committed webapp/.env (default "Bober BBQ",
  no secrets) with a per-client override via gitignored .env.local.
- Add webapp/scripts/gen-manifest.mjs to generate manifest.json from a
  new manifest.template.json the same way, since Vite doesn't process
  public/ assets — wired into the build script.
- Parameterize deploy.sh's systemd unit names and the admin log-viewer's
  log paths via optional SERVICE_WEB/SERVICE_BOT/LOG_FILE_WEB/
  LOG_FILE_BOT env vars, defaulting to today's literal values.
- Rewrite docs/DEPLOYMENT.md into a repeatable new-client runbook (fixed
  an existing /opt/bober-bbq vs /opt/bober-bbq-bot inconsistency, added
  a rebranding checklist).

Verified via a smoke test that every changed string still renders
"Bober BBQ" with no overrides present (matching prod's actual .env/DB
state), and that webapp builds both with and without a VITE_CAFE_NAME
override produce the expected output — including a no-override rebuild
confirming byte-identical output to before this change.
2026-08-25 17:54:29 +03:00
87b0dbb251 Paginate customer order history, pass product images to Monobank
- Customer detail page loaded every order at once; now paginated
  (20/page) like the main orders list, with stats still computed over
  the full history rather than just the current page.
- Monobank's basketOrder items support an "icon" field for showing the
  product photo on the hosted payment page — we were never sending it.
  Now included as an absolute URL when the product has an image.
2026-08-13 21:31:21 +03:00
64b463d4de Toggle basket visibility on Monobank checkout, clearer payment status in manager chat
- New "Показувати список товарів на сторінці оплати" setting — when off,
  the basketOrder field is omitted from the invoice so Monobank's
  checkout page doesn't list items
- Manager chat order message: card orders now show " Очікує оплати"
  instead of a bare  (which read as an error, not the expected
  pending state); cash orders say "при отриманні" instead of implying
  something's wrong
2026-08-09 05:47:13 +03:00
67a0c022f5 Fix Monobank webhook signature verification + add test payment mode
Critical fix: verify_webhook_signature was calling load_der_public_key
on Monobank's pubkey response, but the key is PEM-wrapped, not raw DER
— every real webhook was being rejected with a signature error and
silently falling back to the 10-minute reconciliation poll instead of
updating instantly. Switched to load_pem_public_key.

Also:
- Add a separate test-mode Monobank token + "Тестовий режим оплати"
  toggle in admin settings, so payments can be verified end-to-end
  with fake (Luhn-valid) card numbers before going live. Monobank has
  no shared test key — each admin gets their own via api.monobank.ua.
- Fix two misaligned buttons (clear-database, Monobank "Перевірити")
  caused by the global input margin-bottom throwing off flex
  align-items in forms that mix a label+input with a button.
2026-08-09 05:29:54 +03:00
5d9c5d42a2 Move Monobank payment settings into the admin panel
- Monobank token and the payment description shown to customers are now
  editable in Адмінка → Налаштування → Оплата, instead of requiring SSH
  access to change .env. Falls back to MONOBANK_TOKEN in .env if the
  admin panel field is empty, so existing deployments keep working.
- Add a "Перевірити" button that pings Monobank's merchant details
  endpoint to validate a token live, without creating a real invoice —
  catches typos/revoked tokens before a real customer hits them.
- Payment destination text is now a configurable template ({number}
  placeholder) instead of a hardcoded string.
2026-08-09 05:17:56 +03:00
0eabebaca3 Initial commit: Bober BBQ Telegram bot + backend + admin + Mini App
Flask API/admin backend, aiogram bot with delivery/pickup FSM flows,
monobank payment integration, and a Vite/React Telegram Mini App for
menu browsing and cart management.
2026-08-08 15:50:50 +03:00