feat(bot): RabbitMQ order events + redirect + expiry (AI-core side)
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 bycartId→ thebookingsrow → messages the user via the channel bridge, appends the message toorch_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/notifyadapters (WhatsApp/Telegram,x-internal-secret, best-effort) behind aNotifierPort,logics/notifyNotifyLogic(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_staleover the existingbookingstable. -
Copy
messages.py(en/ar/fa, English fallback) fortransaction.confirm / order.confirmed / order.failed / cart.expired.order.processingis bound but intentionally silent (transaction.confirm carries the "processing" message). -
Memory
ConversationRepository.log_proactive— audit turn + best-effortorch_chat_historyappend so the LLM sees the proactive message next turn. -
EventHandler (idempotent, correlate-by-cartId, swallow-all) + aio-pika consumer + ExpirySweeper, wired via container
@atomicdispatch_event/sweep_expired_carts.
Safety / gating
- Best-effort throughout: nothing raises into the broker or crashes startup.
- Consumer disabled when
RABBITMQ_URIempty; sweeper only whenCART_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_decoratorsomark_status+log_proactivecommit together. -
Race fixed in review:
transaction.confirmnow marks the bookingprocessing(out of the sweeper'sfind_staleset, 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