Twilio add incoming call to queue and call to the agent - php

I have run in the situation where i'm handling incoming call using PHP/laravel, so when client calls to the company number the response is this method :
public function respondToUser()
{
$response = new Twiml();
$audio_file_path = trans('ivr_file_paths.welcome');
$response->play($audio_file_path);
$response->redirect('/ivr/call/enqueue', ['method' => 'POST']);
return $response;
}
But what I want to achieve next is to put incoming call in queue and then run the music in background if the operator (one operator /agent only) is busy, if not then connect to him.
this is what it looks like now
public function enqueueCall(Request $request)
{
$please_wait_audio_file = trans('paths.please_wait');
$please_wait_audio_file = trans('ivr_file_paths.please_wait');
$response = new Twiml();
$dial = $response->dial();
$dial->number('+number');
$response->enqueue('support', ['waitUrl' => $please_wait_audio_file]);
Log::info($response);
echo $response;
}
I know there is no queue right now, but this method just ends up the call..
Any suggestions? Thank you very much!

Twilio developer evangelist here.
I recommend you start by looking at the <Enqueue> TwiML verb which queues a caller up, followed by <Queue> which you can use within <Dial> to pop the next user off from the queue and talk to them.
If you need anything more complicated than that, then start reading into TaskRouter.
edit some example code:
Enqueue the caller and dial your agent.
public function enqueueCall(Request $request)
{
// build up the TwiML
$please_wait_audio_file = trans('ivr_file_paths.please_wait');
$response = new Twiml();
$response->enqueue('support', ['waitUrl' => $please_wait_audio_file]);
// make the call to your agent
$client = new Client($yourTwilioAccountSid, $yourTwilioAuthToken);
$call = $client->calls->create(
$yourAgentNumber,
$yourTwilioNumber,
array("url" => "http://example.com/ivr/call/queue")
);
Log::info($response);
echo $response;
}
When the agent connects, dial the queue:
public function dialQueue(Request $request)
{
$response = new Twiml();
$dial = $response->dial();
$dial->queue('support');
echo $response;
}

Related

How to register Soap client to the Laravel Service Provider

I'm building a webapp in Laravel which consumes multiple external REST API's, in which I have to authenticate myself and retrieve an access token before I'm able to do perform requests. I have built that like so:
ExampleAPIServiceProvider.php
class ExampleAPIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(ExampleClient::class, function () {
return new ExampleClient(
accessToken: $this->getAccessToken()
);
});
}
private function getAccessToken(): ?AccessToken
{
return $this->accessToken ?? $this->requestAccessToken($clientId, $clientSecret);
}
}
This enables me to perform requests like so:
ExampleController.php
class ExampleController extends Controller
{
public function __construct(protected ExampleClient $client) { ... }
public function index()
{
$response = $this->client->getExamles();
}
}
I'm trying to consume another service, but this is a SOAP service. I know I can use the SoapClient(). If I understand correctly, with soap I first have to provide the url I'm trying to fetch data from and then do authentication. This is the example that the Soap Server I'm trying to consume provides:
try {
$soap = new SoapClient($webservice_url);
$res = $soap->Authenticate(array('accessKey' => $key));
if (!isset($res->AuthenticateResult)) exit();
$sess_id = $res->AuthenticateResult;
$xmlvar = new SoapVar('<ns1:xmlDoc>'.$xml.'</ns1:xmlDoc>', XSD_ANYXML);
$res = $soap->ProcessJournal(array('sessionID' => $sess_id, 'administrationID' => $admin_id, 'xmlDoc' => $xmlvar));
} catch (SoapFault $e) {
// throw exception...
}
I'd like to build a small wrapper client for this SoapClient and register that to the service provider as well. However I'm not sure how, as it seems that authentication happens after providing the webservice url. With the example from the Soap Server, it would mean that I'd have to provide the $sess_id and $administrationId each time I want to consume the service.
I think the result I'd like to have is to be able to call this from example the controller:
$this->soapClient->url($url)->ProcessJournal(...);
How do I go about doing registering it to the service provider? Or is there any other solution which would allow me to provide the authentication credentials only once?
Thanks in advance.

PHP & MQTT - Publish and subscribe in same function

Is there a way to make this work..
I'm trying to publish a 'PING' request to a device, and then subscribe to another channel and wait for the 'PONG' response. Here's the code:
public function ping(Request $request)
{
$device = Device::find($request->id);
if ($device) {
//Listen on laravel_device_DEVICEID
$listen_topic = 'laravel_device_' . $device->device_id;
$result = [];
$mqtt = MQTT::connection();
$mqtt->subscribe($listen_topic, function (string $topic, string $message) use ($mqtt, &$result) {
$result['topic'] = $topic;
$result['message'] = $message;
$mqtt->interrupt();
}, 0);
$mqtt->loop(true, true);
//Submit PING message
$topic = $device->device_id;
$message = json_encode([
'type' => 'ping',
'device_id' => $device->device_id
]);
$mqtt = MQTT::connection();
$mqtt->publish($topic, $message);
$device->last_ping_response = 2;
$device->save();
}
}
I'm so free to copy my response from the original question in the php-mqtt/laravel-client repository.
Yes, it is possible and a fairly common thing to do as well. The pattern is called RPC (Remote Procedure Call).
Your code already looks quite good, the only issue is the order of subscribe(), publish() and loop(). Subscribing first is cruicial, since you do not want to miss a response before your subscription goes through. This part is correct already. But you may start the loop only after publishing the request, otherwise the publish() is never reached (because the loop does not exit by itself, only when it receives a message).
Here is the updated piece of code of yours, with some best practices added in. I assumed the response message is what you want to save as $device->last_ping_response:
use PhpMqtt\Client\MqttClient;
use PhpMqtt\Client\Exceptions\MqttClientException;
public function ping(Request $request)
{
$device = Device::find($request->id);
// Exit early if we have no device to ping. Doing it this way round,
// we save one level of identation for the rest of the code.
if ($device === null) {
return;
}
// Prepare all data before connecting to the broker. This way, we do not waste
// time preparing data while already connected.
$subscribeTopic = "laravel_device_{$device->device_id}";
$publishTopic = $device->device_id;
$request = json_encode([
'type' => 'ping',
'device_id' => $device->device_id,
]);
try {
$mqtt = MQTT::connection();
// Ensure we are ready to receive a response to the request
// we are going to send.
$mqtt->subscribe(
$subscribeTopic,
function (string $topic, string $message) use ($mqtt, $device) {
// Update the device based on the response.
$device->last_ping_response = $message;
$device->save();
$mqtt->interrupt();
},
MqttClient::QOS_AT_LEAST_ONCE
);
// Here we register a timeout using a loop event handler.
// The event handler is passed the elapsed time
// the loop runs already (in seconds).
// We do this because in case the receiver of our request is offline,
// we would be stuck in a loop forever.
$mqtt->registerLoopEventHandler(
function function (MqttClient $client, float $elapsedTime) {
// After 10 seconds, we quit the loop.
if ($elapsedTime > 10) {
$client->interrupt();
}
}
);
// Send the request. We use QoS 1 to have guaranteed delivery.
$mqtt->publish($publishTopic, $request, MqttClient::QOS_AT_LEAST_ONCE);
// Wait for a response. This will either return when we receive
// a response on the subscribed topic, or after 10 seconds
// due to our registered loop event handler.
$mqtt->loop();
// Always disconnect gracefully.
$mqtt->disconnect();
} catch (MqttClientException $e) {
\Log::error("Pinging device failed.", [
'device' => $device->id,
'exception' => $e->getMessage(),
]);
}
}

Twilio doesn't call my TwiML instruction url

I'm using Twilio to send a verification code through voice call. I've used this tutorial to write the code:
https://www.twilio.com/blog/implement-account-verification-login-by-phone-laravel-php
So, that's basically the code. My phone rings and I get the "You have a test account, press any key to run your code" message, but when I press a key, it hangs up on me and my code is never called.
public function sendVerificationCodeThroughCall($mobile)
{
$code = $this->createMobileCode($mobile);
$client = new Client(config('services.twilio.sid'), config('services.twilio.auth_token'));
$client->calls->create(
$mobile,
config('services.twilio.phone_number'),
["url" => route('voice-code.build', $code)]
);
}
public function buildVoiceCode($code)
{
Log::debug('Twilio called to say: ' . $code);
$code = $this->splitCode($code);
$response = new VoiceResponse();
$response->say("Salaam. This is your verification code: {$code}. I repeat, {$code}.");
echo $response;
}
protected function splitCode($code)
{
return implode('.', str_split($code));
}

Guzzle is sending requests synchronously

I am using Guzzle to consume a SOAP API. I have to make 6 requests, but in the future this might be even an indeterminate amount of requests.
Problem is that the requests are being send sync, instead of async. Every request on it's own takes +-2.5s. When I send all 6 requests paralell (at least thats what I am trying) it takes +- 15s..
I tried all the examples on Guzzle, the one with a fixed array with $promises, and even the pool (which I need eventually). When I put everything in 1 file (functional) I manage to get the total timing back to 5-6s (which is OK right?). But when I put everything in Objects and functions somehow I do something that makes Guzzle decide to do them sync again.
Checks.php:
public function request()
{
$promises = [];
$promises['requestOne'] = $this->requestOne();
$promises['requestTwo'] = $this->requestTwo();
$promises['requestThree'] = $this->requestThree();
// etc
// wait for all requests to complete
$results = \GuzzleHttp\Promise\settle($promises)->wait();
// Return results
return $results;
}
public function requestOne()
{
$promise = (new API\GetProposition())
->requestAsync();
return $promise;
}
// requestTwo, requestThree, etc
API\GetProposition.php
public function requestAsync()
{
$webservice = new Webservice();
$xmlBody = '<some-xml></some-xml>';
return $webservice->requestAsync($xmlBody, 'GetProposition');
}
Webservice.php
public function requestAsync($xmlBody, $soapAction)
{
$client = new Client([
'base_uri' => 'some_url',
'timeout' => 5.0
]);
$xml = '<soapenv:Envelope>
<soapenv:Body>
'.$xmlBody.'
</soapenv:Body>
</soapenv:Envelope>';
$promise = $client->requestAsync('POST', 'NameOfService', [
'body' => $xml,
'headers' => [
'Content-Type' => 'text/xml',
'SOAPAction' => $soapAction, // SOAP Method to post to
],
]);
return $promise;
}
I changed the XML and some of the parameters for abbreviation. The structure is like this, because I eventually have to talk against multiple API's, to thats why I have a webservice class in between that does all the preparation needed for that API. Most API's have multiple methods/actions that you can call, so that why I have something like. API\GetProposition.
Before the ->wait() statement I can see all $promises pending. So it looks like there are being send async. After ->wait() they have all been fulfilled.
It's all working, minus the performance. All 6 requests don't take more then 2.5 to max 3s.
Hope someone can help.
Nick
The problem was that the $client object was created with every request. Causing the curl multi curl not to be able to know which handler to use.
Found the answer via https://stackoverflow.com/a/46019201/7924519.

Created Request using die() also dies the Request caller

I don't know if it's the right terms to employ...
I made an API, in which the answer is sent by the die() function, to avoid some more useless calculations and/or functions calls.
example :
if (isset($authorize->refusalReason)) {
die ($this->api_return(true, [
'resultCode' => $authorize->resultCode,
'reason' => $authorize->refusalReason
]
));
}
// api_return method:
protected function api_return($error, $params = []) {
$time = (new DateTime())->format('Y-m-d H:i:s');
$params = (array) $params;
$params = ['error' => $error, 'date_time' => $time] + $params;
return (Response::json($params)->sendHeaders()->getContent());
}
But my website is based on this API, so I made a function to create a Request and return the contents of it, based on its URI, method, params, and headers:
protected function get_route_contents($uri, $type, $params = [], $headers = []) {
$request = Request::create($uri, $type, $params);
if (Auth::user()->check()) {
$request->headers->set('S-token', Auth::user()->get()->Key);
}
foreach ($headers as $key => $header) {
$request->headers->set($key, $header);
}
// things to merge the Inputs into the new request.
$originalInput = Request::input();
Request::replace($request->input());
$response = Route::dispatch($request);
Request::replace($originalInput);
$response = json_decode($response->getContent());
// This header cancels the one there is in api_return. sendHeaders() makes Content-Type: application/json
header('Content-Type: text/html');
return $response;
}
But now when I'm trying to call an API function, The request in the API dies but dies also my current Request.
public function postCard($token) {
$auth = $this->get_route_contents("/api/v2/booking/payment/card/authorize/$token", 'POST', Input::all());
// the code below is not executed since the API request uses die()
if ($auth->error === false) {
return Redirect::route('appts')->with(['success' => trans('messages.booked_ok')]);
}
return Redirect::back()->with(['error' => $auth->reason]);
}
Do you know if I can handle it better than this ? Any suggestion of how I should turn my code into ?
I know I could just use returns, but I was always wondering if there were any other solutions. I mean, I want to be better, so I wouldn't ask this question if I knew for sure that the only way of doing what I want is using returns.
So it seems that you are calling an API endpoint through your code as if it is coming from the browser(client) and I am assuming that your Route:dispatch is not making any external request(like curl etc)
Now There can be various approaches to handle this:
If you function get_route_contents is going to handle all the requests, then you need to remove the die from your endpoints and simply make them return the data(instead of echoing). Your this "handler" will take care of response.
Make your Endpoint function to have an optional parameter(or some property set in the $request variable), which will tell the function that this is an internal request and data should be returned, when the request comes directly from a browser(client) you can do echo
Make an external call your code using curl etc(only do this if there is no other option)

Categories