Merge branch 'feat/whatsapp-call-incoming-pipeline' into feat/whatsapp-call-meta-bridge

This commit is contained in:
Tanmay Deep Sharma
2026-05-07 20:11:31 +07:00
2 changed files with 23 additions and 2 deletions
@@ -120,13 +120,25 @@ class Whatsapp::IncomingCallService
next if call.terminal?
duration = payload[:duration]&.to_i
status = answered?(call, duration) ? 'completed' : 'no_answer'
reason = payload[:terminate_reason].to_s
status = derive_terminate_status(call, duration, reason)
meta = (call.meta || {}).merge('ended_at' => Time.zone.now.to_i)
update_call!(call, status, duration_seconds: duration, end_reason: payload[:terminate_reason], meta: meta)
update_call!(call, status, duration_seconds: duration, end_reason: reason, meta: meta)
broadcast(call, 'voice_call.ended', status: call.display_status, duration_seconds: call.duration_seconds)
end
end
# Provider-reported failures trump the answered/no_answer heuristic. An
# in_progress call that Meta later terminates with a failure reason would
# otherwise be recorded as 'completed' purely because it had been accepted.
FAILURE_REASONS = %w[failed error rejected busy invalid_offer cancelled].freeze
def derive_terminate_status(call, duration, reason)
return 'failed' if FAILURE_REASONS.any? { |r| reason.include?(r) }
answered?(call, duration) ? 'completed' : 'no_answer'
end
# accepted_by_agent_id is the initiating agent on outbound calls, so it only signals "answered" for inbound.
def answered?(call, duration)
call.in_progress? || duration.to_i.positive? || (call.incoming? && call.accepted_by_agent_id.present?)
@@ -104,6 +104,15 @@ describe Whatsapp::IncomingCallService do
expect(call.reload.status).to eq('no_answer')
end
it 'records the call as failed when Meta reports a failure reason for an in_progress call' do
allow(ActionCable.server).to receive(:broadcast)
params = call_payload(event: 'terminate', duration: 12, terminate_reason: 'failed')
described_class.new(inbox: inbox, params: params).perform
expect(call.reload).to have_attributes(status: 'failed', duration_seconds: 12, end_reason: 'failed')
end
it 'is a no-op when the call is already terminal so retries cannot flip a completed call to no_answer' do
call.update!(status: 'completed', duration_seconds: 5, direction: :outgoing, accepted_by_agent: nil)
allow(ActionCable.server).to receive(:broadcast)