Skip to content

feat(bot): RabbitMQ order events + redirect + expiry (AI-core side)

Administrator requested to merge feat-bot-rabbitmq-events into beta

Order-lifecycle event bot (AI-core side)

Pairs with booking!198 (which publishes the events). When a user pays, RabbitMQ events drive proactive WhatsApp/Telegram messages back into their chat, the message is logged into the AI-core conversation memory, and unpaid carts expire after 15 min.

Flow

  • Payment gateway callbackUrl → GET /pay/return/{channel} 302s the user back to the wa.me/t.me deep link after paying.
  • An aio-pika consumer subscribes to transaction.confirm (transaction.exchange) + order.processing|confirmed|failed (order.exchange), correlates each by cartId → the bookings row → messages the user via the channel bridge, appends the message to orch_chat_history, and marks booking status.
  • A 60s sweeper closes carts still unpaid past CART_TTL_MINUTES (15) and tells the user.

What's inside

  • Notifier as a proper layer: repositories/notify adapters (WhatsApp/Telegram, x-internal-secret, best-effort) behind a NotifierPort, logics/notify NotifyLogic (send + memory log).
  • Redirect endpoint /pay/return/{channel} + callback_url() wired into the payment body; channel threaded onto the booking DTO.
  • Correlation: AnalyticsRepository.get_by_cart_id / mark_status / find_stale over the existing bookings table.
  • Copy messages.py (en/ar/fa, English fallback) for transaction.confirm / order.confirmed / order.failed / cart.expired. order.processing is bound but intentionally silent (transaction.confirm carries the "processing" message).
  • Memory ConversationRepository.log_proactive — audit turn + best-effort orch_chat_history append so the LLM sees the proactive message next turn.
  • EventHandler (idempotent, correlate-by-cartId, swallow-all) + aio-pika consumer + ExpirySweeper, wired via container @atomic dispatch_event / sweep_expired_carts.

Safety / gating

  • Best-effort throughout: nothing raises into the broker or crashes startup.
  • Consumer disabled when RABBITMQ_URI empty; sweeper only when CART_TTL_MINUTES > 0 (both off by default → this MR is inert until those are set on beta).
  • Bridge sends gated on the shared INTERNAL_BOT_SECRET; no secret ever logged.

Notes for reviewers

  • Recipient is booking.user_id (bare wa_id / telegram chat id); thread_id ({channel}:{user_id}) is memory-only.
  • Repos flush-only; both DB entrypoints run inside @async_postgres_sqlalchemy_atomic_decorator so mark_status + log_proactive commit together.
  • Race fixed in review: transaction.confirm now marks the booking processing (out of the sweeper's find_stale set, not final) so the sweeper can't expire a paid, in-flight order and swallow its confirmation.
  • Built TDD; per-file tests under ai/evals/tests/test_* (adapters, notify logic, pay/return, callback_url, booking lookups, event copy, log_proactive, event handler, consumer binding, expiry). PDF vouchers + flight.confirmed deferred (flight still uses TCP notification, not RabbitMQ).

Config added (defaults inert): AI_PUBLIC_BASE_URL, WA_DEEPLINK_URL, TG_DEEPLINK_URL, RABBITMQ_URI, CART_TTL_MINUTES.

Edited by Administrator

Merge request reports

Loading