testing progress

This commit is contained in:
2025-08-12 07:05:47 -07:00
parent 304b5de40b
commit 51dd3077d2
5 changed files with 2698 additions and 0 deletions

View File

@@ -86,6 +86,34 @@ class TWP_Webhooks {
'permission_callback' => '__return_true'
));
// Browser phone voice webhook
register_rest_route('twilio-webhook/v1', '/browser-voice', array(
'methods' => 'POST',
'callback' => array($this, 'handle_browser_voice'),
'permission_callback' => '__return_true'
));
// Browser phone fallback webhook
register_rest_route('twilio-webhook/v1', '/browser-fallback', array(
'methods' => 'POST',
'callback' => array($this, 'handle_browser_fallback'),
'permission_callback' => '__return_true'
));
// Smart routing webhook (checks user preference)
register_rest_route('twilio-webhook/v1', '/smart-routing', array(
'methods' => 'POST',
'callback' => array($this, 'handle_smart_routing'),
'permission_callback' => '__return_true'
));
// Smart routing fallback webhook
register_rest_route('twilio-webhook/v1', '/smart-routing-fallback', array(
'methods' => 'POST',
'callback' => array($this, 'handle_smart_routing_fallback'),
'permission_callback' => '__return_true'
));
// Agent screening webhook (screen agent before connecting)
register_rest_route('twilio-webhook/v1', '/agent-screen', array(
'methods' => 'POST',
@@ -200,6 +228,341 @@ class TWP_Webhooks {
exit;
}
/**
* Handle browser phone voice webhook
*/
public function handle_browser_voice($request) {
$params = $request->get_params();
$call_data = array(
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
'From' => isset($params['From']) ? $params['From'] : '',
'To' => isset($params['To']) ? $params['To'] : '',
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : ''
);
// Log the browser call
TWP_Call_Logger::log_call(array(
'call_sid' => $call_data['CallSid'],
'from_number' => $call_data['From'],
'to_number' => $call_data['To'],
'status' => 'browser_call_initiated',
'actions_taken' => 'Browser phone call initiated'
));
// For outbound calls from browser, handle caller ID properly
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
$twiml .= '<Response>';
if (isset($params['To']) && !empty($params['To'])) {
$to_number = $params['To'];
$from_number = isset($params['From']) ? $params['From'] : '';
// If it's an outgoing call to a phone number
if (strpos($to_number, 'client:') !== 0) {
$twiml .= '<Dial timeout="30"';
// Add caller ID if provided
if (!empty($from_number) && strpos($from_number, 'client:') !== 0) {
$twiml .= ' callerId="' . htmlspecialchars($from_number) . '"';
}
$twiml .= '>';
$twiml .= '<Number>' . htmlspecialchars($to_number) . '</Number>';
$twiml .= '</Dial>';
} else {
// Incoming call to browser client
$twiml .= '<Dial timeout="30">';
$twiml .= '<Client>' . htmlspecialchars(str_replace('client:', '', $to_number)) . '</Client>';
$twiml .= '</Dial>';
}
} else {
$twiml .= '<Say voice="alice">No destination number provided.</Say>';
}
$twiml .= '</Response>';
return $this->send_twiml_response($twiml);
}
/**
* Handle browser phone fallback when no browser clients answer
*/
public function handle_browser_fallback($request) {
$params = $request->get_params();
$call_data = array(
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
'From' => isset($params['From']) ? $params['From'] : '',
'To' => isset($params['To']) ? $params['To'] : '',
'DialCallStatus' => isset($params['DialCallStatus']) ? $params['DialCallStatus'] : ''
);
error_log('TWP Browser Fallback: No browser clients answered, status: ' . $call_data['DialCallStatus']);
// Log the fallback
TWP_Call_Logger::log_call(array(
'call_sid' => $call_data['CallSid'],
'from_number' => $call_data['From'],
'to_number' => $call_data['To'],
'status' => 'browser_fallback',
'actions_taken' => 'Browser clients did not answer, using fallback'
));
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
$twiml .= '<Response>';
// Fallback options based on call status
if ($call_data['DialCallStatus'] === 'no-answer' || $call_data['DialCallStatus'] === 'busy') {
// Try SMS notification to agents as fallback
$this->send_agent_notification_sms($call_data['From'], $call_data['To']);
$twiml .= '<Say voice="alice">All our agents are currently busy. We have been notified of your call and will get back to you shortly.</Say>';
$twiml .= '<Say voice="alice">Please stay on the line for voicemail, or hang up and we will call you back.</Say>';
// Redirect to voicemail
$voicemail_url = home_url('/wp-json/twilio-webhook/v1/voicemail-callback');
$twiml .= '<Redirect>' . $voicemail_url . '</Redirect>';
} else {
// Other statuses - generic message
$twiml .= '<Say voice="alice">We apologize, but we are unable to connect your call at this time. Please try again later.</Say>';
$twiml .= '<Hangup/>';
}
$twiml .= '</Response>';
return $this->send_twiml_response($twiml);
}
/**
* Send SMS notification to agents about missed browser call
*/
private function send_agent_notification_sms($customer_number, $twilio_number) {
// Get agents with phone numbers
$agents = get_users(array(
'meta_key' => 'twp_phone_number',
'meta_compare' => 'EXISTS'
));
$message = "Missed call from {$customer_number}. Browser phone did not answer. Please call back or check voicemail.";
foreach ($agents as $agent) {
$agent_phone = get_user_meta($agent->ID, 'twp_phone_number', true);
if (!empty($agent_phone)) {
$twilio_api = new TWP_Twilio_API();
$twilio_api->send_sms($agent_phone, $message, $twilio_number);
}
}
}
/**
* Handle smart routing based on user preferences
*/
public function handle_smart_routing($request) {
$params = $request->get_params();
$call_data = array(
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
'From' => isset($params['From']) ? $params['From'] : '',
'To' => isset($params['To']) ? $params['To'] : '',
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : ''
);
// Log the incoming call
TWP_Call_Logger::log_call(array(
'call_sid' => $call_data['CallSid'],
'from_number' => $call_data['From'],
'to_number' => $call_data['To'],
'status' => 'smart_routing',
'actions_taken' => 'Smart routing - checking workflows first, then user preferences'
));
// FIRST: Check if there's a workflow assigned to this phone number
$workflow = TWP_Workflow::get_workflow_by_phone_number($call_data['To']);
if ($workflow) {
error_log('TWP Smart Routing: Found workflow for ' . $call_data['To'] . ', executing workflow ID: ' . $workflow->id);
// Execute the workflow instead of direct routing
$workflow_twiml = TWP_Workflow::execute_workflow($workflow->id, $call_data);
if ($workflow_twiml) {
header('Content-Type: application/xml');
echo $workflow_twiml;
exit;
}
}
// FALLBACK: If no workflow found or workflow failed, use direct agent routing
error_log('TWP Smart Routing: No workflow found for ' . $call_data['To'] . ', falling back to direct agent routing');
// Check for any active agents and their preferences
$agents = get_users(array(
'meta_key' => 'twp_phone_number',
'meta_compare' => 'EXISTS'
));
$browser_agents = [];
$cell_agents = [];
foreach ($agents as $agent) {
$call_mode = get_user_meta($agent->ID, 'twp_call_mode', true);
$agent_phone = get_user_meta($agent->ID, 'twp_phone_number', true);
if ($call_mode === 'browser') {
$client_name = 'agent_' . $agent->ID . '_' . sanitize_title($agent->display_name);
$browser_agents[] = $client_name;
} elseif (!empty($agent_phone)) {
$cell_agents[] = $agent_phone;
}
}
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
$twiml .= '<Response>';
$twiml .= '<Say voice="alice">Please hold while we connect you to an agent.</Say>';
// Try browser agents first, then cell agents
if (!empty($browser_agents)) {
$twiml .= '<Dial timeout="20" action="' . home_url('/wp-json/twilio-webhook/v1/smart-routing-fallback') . '" method="POST">';
foreach ($browser_agents as $client_name) {
$twiml .= '<Client>' . htmlspecialchars($client_name) . '</Client>';
}
$twiml .= '</Dial>';
} elseif (!empty($cell_agents)) {
// No browser agents, try cell phones
$twiml .= '<Dial timeout="20" action="' . home_url('/wp-json/twilio-webhook/v1/smart-routing-fallback') . '" method="POST">';
foreach ($cell_agents as $cell_phone) {
$twiml .= '<Number>' . htmlspecialchars($cell_phone) . '</Number>';
}
$twiml .= '</Dial>';
} else {
// No agents available
$twiml .= '<Say voice="alice">All agents are currently unavailable. Please leave a voicemail.</Say>';
$twiml .= '<Redirect>' . home_url('/wp-json/twilio-webhook/v1/voicemail-callback') . '</Redirect>';
}
$twiml .= '</Response>';
return $this->send_twiml_response($twiml);
}
/**
* Handle smart routing fallback when initial routing fails
*/
public function handle_smart_routing_fallback($request) {
$params = $request->get_params();
$call_data = array(
'CallSid' => isset($params['CallSid']) ? $params['CallSid'] : '',
'From' => isset($params['From']) ? $params['From'] : '',
'To' => isset($params['To']) ? $params['To'] : '',
'DialCallStatus' => isset($params['DialCallStatus']) ? $params['DialCallStatus'] : ''
);
error_log('TWP Smart Routing Fallback: Initial routing failed, status: ' . $call_data['DialCallStatus']);
// Log the fallback
TWP_Call_Logger::log_call(array(
'call_sid' => $call_data['CallSid'],
'from_number' => $call_data['From'],
'to_number' => $call_data['To'],
'status' => 'routing_fallback',
'actions_taken' => 'Smart routing failed, trying alternative methods'
));
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
$twiml .= '<Response>';
// Get agents and their preferences for fallback routing
$agents = get_users(array(
'meta_key' => 'twp_phone_number',
'meta_compare' => 'EXISTS'
));
$browser_agents = [];
$cell_agents = [];
foreach ($agents as $agent) {
$call_mode = get_user_meta($agent->ID, 'twp_call_mode', true);
$agent_phone = get_user_meta($agent->ID, 'twp_phone_number', true);
if ($call_mode === 'browser') {
$client_name = 'agent_' . $agent->ID . '_' . sanitize_title($agent->display_name);
$browser_agents[] = $client_name;
} elseif (!empty($agent_phone)) {
$cell_agents[] = $agent_phone;
}
}
// Fallback strategy based on initial failure
if ($call_data['DialCallStatus'] === 'no-answer' || $call_data['DialCallStatus'] === 'busy') {
// If browsers didn't answer, try cell phones; if cells didn't answer, try queue or voicemail
if (!empty($cell_agents) && !empty($browser_agents)) {
// We tried browsers first, now try cell phones
$twiml .= '<Say voice="alice">Trying to connect you to another agent.</Say>';
$twiml .= '<Dial timeout="20">';
foreach ($cell_agents as $cell_phone) {
$twiml .= '<Number>' . htmlspecialchars($cell_phone) . '</Number>';
}
$twiml .= '</Dial>';
// If this also fails, fall through to final fallback below
$twiml .= '<Say voice="alice">All agents are currently busy.</Say>';
} else {
// No alternative agents available - go to final fallback
$twiml .= '<Say voice="alice">All agents are currently busy.</Say>';
}
// Send SMS notification to agents about missed call
$this->send_missed_call_notification($call_data['From'], $call_data['To']);
// Offer callback or voicemail options
$twiml .= '<Gather timeout="10" numDigits="1" action="' . home_url('/wp-json/twilio-webhook/v1/callback-choice') . '" method="POST">';
$twiml .= '<Say voice="alice">Press 1 to request a callback, or press 2 to leave a voicemail.</Say>';
$twiml .= '</Gather>';
// Default to voicemail if no input
$twiml .= '<Say voice="alice">No response received. Transferring you to voicemail.</Say>';
$twiml .= '<Redirect>' . home_url('/wp-json/twilio-webhook/v1/voicemail-callback') . '</Redirect>';
} elseif ($call_data['DialCallStatus'] === 'failed') {
// Technical failure - provide different message
$twiml .= '<Say voice="alice">We are experiencing technical difficulties. Please try again later or leave a voicemail.</Say>';
$twiml .= '<Redirect>' . home_url('/wp-json/twilio-webhook/v1/voicemail-callback') . '</Redirect>';
} else {
// Other statuses or unknown - generic fallback
$twiml .= '<Say voice="alice">We apologize, but we are unable to connect your call at this time.</Say>';
$twiml .= '<Gather timeout="10" numDigits="1" action="' . home_url('/wp-json/twilio-webhook/v1/callback-choice') . '" method="POST">';
$twiml .= '<Say voice="alice">Press 1 to request a callback, or press 2 to leave a voicemail.</Say>';
$twiml .= '</Gather>';
$twiml .= '<Redirect>' . home_url('/wp-json/twilio-webhook/v1/voicemail-callback') . '</Redirect>';
}
$twiml .= '</Response>';
return $this->send_twiml_response($twiml);
}
/**
* Send SMS notification to agents about missed call
*/
private function send_missed_call_notification($customer_number, $twilio_number) {
// Get agents with phone numbers
$agents = get_users(array(
'meta_key' => 'twp_phone_number',
'meta_compare' => 'EXISTS'
));
$message = "Missed call from {$customer_number}. All agents were unavailable. Customer offered callback/voicemail options.";
foreach ($agents as $agent) {
$agent_phone = get_user_meta($agent->ID, 'twp_phone_number', true);
if (!empty($agent_phone)) {
$twilio_api = new TWP_Twilio_API();
$twilio_api->send_sms($agent_phone, $message, $twilio_number);
}
}
}
/**
* Verify Twilio signature
*/