Captain::Hermes::Client (enterprise/app/services/captain/hermes/client.rb): - text_for_hermes: transcreve audio via Whisper antes de enviar pro Hermes (reusa Captain::OpenAiMessageBuilderService) - image_urls_for_hermes: URLs publicas de imagens da message; plugin captain-webhook do Hermes baixa em /tmp/ e popula event.media_urls pra vision multimodal (gpt-4o-mini auxiliary) - contact_history_snapshot: dados eager pro [ctx] (last_reservation_*, total_conversations, ultima_suite, etc) — memoria do contato direto no prompt sem precisar tool call - notify_event + build_event_payload: dispara webhook sintetico pro Hermes pra eventos do sistema (Pix pago etc) — Valentina manda mensagem espontanea sem cliente perguntar Captain::Payments::ConfirmationService: - Hook notify_hermes_proactively! enfileira NotifyPaymentConfirmedJob apos confirmacao de Pix, somente se inbox estiver no fluxo Hermes (Captain interno continua igual sem mudanca) Captain::Hermes::NotifyPaymentConfirmedJob (NOVO): - Monta system_message "[SISTEMA: pagamento_confirmado]\n..." e dispara webhook pro Hermes Valentina - Valentina (via SOUL.md) interpreta como evento do Captain e manda mensagem celebrativa pro cliente Captain::Hermes::DelayedReplyJob (NOVO) — humanizadores: - Liga indicador "digitando..." (composing) via wuzapi - Aguarda delay configuravel via Captain::Assistant.config['response_delay'] (modos: none, fixed, typing_simulation com chars_per_second + min/max) - Posta msg outgoing - Desliga typing - Fallback no HermesCallbackController posta direto se class nao carregada Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
3.5 KiB
Ruby
104 lines
3.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Serviço para confirmar pagamento de uma reserva
|
|
# Atualiza status, labels e cria nota interna
|
|
class Captain::Payments::ConfirmationService
|
|
def initialize(reservation:, source:, payload: nil, actor: nil)
|
|
@reservation = reservation
|
|
@source = source.to_s
|
|
@payload = payload
|
|
@actor = actor
|
|
end
|
|
|
|
def perform
|
|
was_already_paid = reservation.payment_status.to_s == 'paid'
|
|
|
|
ActiveRecord::Base.transaction do
|
|
mark_reservation_paid!
|
|
sync_conversation_labels!
|
|
create_internal_note_once!
|
|
end
|
|
|
|
enqueue_roulette_offer! unless was_already_paid
|
|
notify_hermes_proactively! unless was_already_paid
|
|
|
|
Rails.logger.info "[PaymentConfirmation] Reserva #{@reservation.id} confirmada (#{source_label})"
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :reservation, :source, :payload, :actor
|
|
|
|
def mark_reservation_paid!
|
|
attrs = { payment_status: :paid }
|
|
attrs[:status] = :active if reservation.respond_to?(:active?) && !reservation.active?
|
|
reservation.update!(attrs)
|
|
end
|
|
|
|
def sync_conversation_labels!
|
|
conversation = reservation.conversation
|
|
return if conversation.blank?
|
|
|
|
current = conversation.label_list
|
|
merged = (current + %w[pagamento_confirmado reserva_feita]).uniq
|
|
merged -= %w[aguardando_pagamento comprovante_recebido pagamento_em_revisao]
|
|
conversation.update_labels(merged)
|
|
end
|
|
|
|
def create_internal_note_once!
|
|
conversation = reservation.conversation
|
|
return if conversation.blank?
|
|
return if confirmation_note_already_created?
|
|
|
|
content = [
|
|
"💰 Pagamento confirmado automaticamente (#{source_label}).",
|
|
"📋 Reserva ##{reservation.id}",
|
|
("🔗 Origem: #{source}" if source.present?)
|
|
].compact.join("\n")
|
|
|
|
Messages::MessageBuilder.new(actor, conversation, { content: content, private: true }).perform
|
|
mark_note_created!
|
|
end
|
|
|
|
def source_label
|
|
case source
|
|
when 'webhook_inter_pix' then 'webhook Inter Pix'
|
|
when 'payment_callback' then 'callback de pagamento'
|
|
when 'inter_cob_query_polling' then 'consulta periódica no Inter'
|
|
when 'inter_cob_query' then 'consulta manual no Inter'
|
|
else
|
|
'integração de pagamento'
|
|
end
|
|
end
|
|
|
|
def confirmation_note_already_created?
|
|
reservation.metadata.to_h['payment_confirmed_note_at'].present?
|
|
end
|
|
|
|
def mark_note_created!
|
|
metadata = reservation.metadata.to_h
|
|
metadata['payment_confirmed_note_at'] ||= Time.current.iso8601
|
|
metadata['payment_confirmed_source'] ||= source
|
|
metadata['payment_confirmed_payload'] ||= payload if payload.present?
|
|
reservation.update_column(:metadata, metadata)
|
|
end
|
|
|
|
# Dispara a oferta da Roleta da Sorte após confirmação.
|
|
# Fora da transação — roleta é side effect; se falhar, confirmação continua válida.
|
|
def enqueue_roulette_offer!
|
|
Captain::Payments::OfferRouletteJob.perform_later(reservation.id)
|
|
rescue StandardError => e
|
|
Rails.logger.warn("[PaymentConfirmation] falha ao enfileirar roleta reserva=#{reservation.id}: #{e.class} - #{e.message}")
|
|
end
|
|
|
|
# Notifica o Hermes Agent (se a inbox estiver no fluxo Hermes) pra mandar
|
|
# mensagem espontânea pro cliente. Coexiste com o fluxo Captain interno —
|
|
# se a inbox NÃO estiver no Hermes, o job ignora silenciosamente. Side
|
|
# effect: nunca bloqueia a confirmação.
|
|
def notify_hermes_proactively!
|
|
Captain::Hermes::NotifyPaymentConfirmedJob.perform_later(reservation.id)
|
|
rescue StandardError => e
|
|
Rails.logger.warn("[PaymentConfirmation] falha ao notificar Hermes reserva=#{reservation.id}: #{e.class} - #{e.message}")
|
|
end
|
|
end
|