Unable to POST request using guzzle with the Amadeus API - php

Description
I am trying to integrate Amadeus Self-Service API within the Laravel Environment. I am successfully able to get content by GET request, but I am not able to get content by the POST request. I have set the exceptions to display the errors thrown by the guzzle in specific.
Here is the api reference, which has the data and the endpoint which I want to post to.
https://developers.amadeus.com/self-service/category/air/api-doc/flight-offers-search/api-reference
How to reproduce
This is the method which I call from my Client.php and pass the data through by calling the POST method.
public function __construct() {
throw_if(static::$instance, 'There should be only one instance of this class');
static::$instance = $this;
$this->client = new Client([
'base_uri' => 'https://test.api.amadeus.com/',
]);
}
public function get($uri, array $options = []) {
$this->authenticate();
return $this->client->request('GET', $uri, [
$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
After calling the POST method, I pass the 'X-HTTP-Method-Override' as 'GET', and pass the data as body.
$requests_response = $client->post('v2/shopping/flight-offers', [
'headers' => [
'X-HTTP-Method-Override' => 'GET',
],
'body' => [
[
"currencyCode" => "USD",
"originDestinations" => [
[
"id" => "1",
"originLocationCode" => "RIO",
"destinationLocationCode" => "MAD",
"departureDateTimeRange" => [
"date" => "2022-11-01",
"time" => "10:00:00",
],
],
[
"id" => "2",
"originLocationCode" => "MAD",
"destinationLocationCode" => "RIO",
"departureDateTimeRange" => [
"date" => "2022-11-05",
"time" => "17:00:00",
],
],
],
"travelers" => [
["id" => "1", "travelerType" => "ADULT"],
["id" => "2", "travelerType" => "CHILD"],
],
"sources" => ["GDS"],
"searchCriteria" => [
"maxFlightOffers" => 2,
"flightFilters" => [
"cabinRestrictions" => [
[
"cabin" => "BUSINESS",
"coverage" => "MOST_SEGMENTS",
"originDestinationIds" => ["1"],
],
],
"carrierRestrictions" => [
"excludedCarrierCodes" => ["AA", "TP", "AZ"],
],
],
],
],
],
]);
Additional context
Here is the error, which I caught in the log.
local.ERROR: Guzzle error {"response":{"GuzzleHttp\\Psr7\\Stream":"
{
\"errors\": [
{
\"code\": 38189,
\"title\": \"Internal error\",
\"detail\": \"An internal error occurred, please contact your administrator\",
\"status\": 500
}
]
}
"}}
local.ERROR: Server error: POST https://test.api.amadeus.com/v2/shopping/flight-offers resulted in a 500 Internal Server Error response:
{
"errors": [
"code": 38189,
(truncated...)
"exception":"[object] (GuzzleHttp\\Exception\\ServerException(code: 500): Server error: POST https://test.api.amadeus.com/v2/shopping/flight-offers resulted in a 500 Internal Server Error response:
"errors": [
"code": 38189,
(truncated...)
at C:\\xampp\\htdocs\\Application\\vendor\\guzzlehttp\\guzzle\\src\\Exception\\RequestException.php:113)
Please spare some time to have a look, help is really appreciated.

Do the POST calls actually work using a HTTP client such as Postman or Insomnia ?
I am noticing is that you are passing an array of $options and are nesting it inside the Guzzle options. The resulting call will look something like this:
$this->client->request('POST', $uri, [
['headers' => '...', 'body' => ['...']],
'headers' => ['...']
]);
That won't work, you are going to need to unpack them this way:
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
...$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
Notice the dots ... to unpack the options array. Also notice that you are setting the headers key twice (once in your post method definition and once in the options parameter), so only one will actually be used (by the way why exactly are you using the X-HTTP-Method-Override header ?).
Another solution if you want to pass all header and body in the POST function parameters is this:
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
'json' => $options['json'], // I would suggest 'json' because it looks like the API is looking for a JSON body, if that doesn't work go with 'body'
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
...$options['headers']
],
]);
}
Another thing you might try if this doesn't do it is using the Guzzle json option instead of body for the POST request.

when you are exploring any Amadeus Self Service API, I recommend to review the portal, because it will help you with one idea about how to make the http calls.
In your case:
https://developers.amadeus.com/self-service/category/air/api-doc/flight-offers-search/api-reference
Another help could be to review the coding examples:
https://github.com/amadeus4dev/amadeus-code-examples/blob/master/flight_offers_search/v2/post/curl/
https://github.com/amadeus4dev/amadeus-code-examples/tree/master/flight_offers_search/v2/get/curl

Maybe it's a little late but this example work for me:
$options = [
'headers' => [
'Authorization' => sprintf('Bearer %s', $this->getApiToken()),
'content-type' => 'application/vnd.amadeus+json',
'X-HTTP-Method-Override' => 'GET',
],
'body' => '{
"currencyCode": "XPF",
"originDestinations": [
{
"id": 1,
"originLocationCode": "PPT",
"originRadius": null,
"alternativeOriginsCodes": [],
"destinationLocationCode": "CDG",
"alternativeDestinationsCodes": [],
"departureDateTimeRange": {
"date": "2022-12-22",
"dateWindow": "I2D"
},
"includedConnectionPoints": [],
"excludedConnectionPoints": []
}
],
"travelers": [
{
"id": "1",
"travelerType": "ADULT",
"associatedAdultId": null
}
],
"sources": [
"GDS"
]
}'
];
try {
...
$response = $this->httpClient->post(self::AMADEUS_API_URL_FLIGHT_OFFER, $options);
$body = $response->getBody();
...
Note: don't forget the content-type, it's not very obvious at first sight in the documentation but without it doesnt work with Guzzle for me (but with insomnia no problem)
consts of the class:
private const AMADEUS_API_CLIENT_GRANT_TYPE = 'client_credentials';
private const AMADEUS_API_URL_AUTH = '/v1/security/oauth2/token';
private const AMADEUS_API_URL_FLIGHT_OFFER = '/v2/shopping/flight-offers';
Authentication:
/**
*
*/
public function authenticate()
{
if (!is_null($this->getApiToken())) {
return $this->getApiToken();
}
$options = [
'form_params' => [
'client_id' => $this->apiId, //setted in the parent construct
'client_secret' => $this->apiKey, //setted in the parent construct
'grant_type' => self::AMADEUS_API_CLIENT_GRANT_TYPE,
]
];
try {
$response = $this->httpClient->post(self::AMADEUS_API_URL_AUTH, $options);
} catch (ClientExceptionInterface $exception) {
...
}
if ($response->getStatusCode() != Response::HTTP_OK) {
throw new ApiException($errorMessage, [$response->getReasonPhrase()], $response->getStatusCode());
}
$body = $response->getBody();
//custom serializer, AmadeusAuthenticationResponse is a mapping based on Amadeus authentication response
$authenticationResponse = $this->serializer->convertSerializationToData($body->getContents(), AmadeusAuthenticationResponse::class);
$this->setApiToken($authenticationResponse->getAccessToken());
return $this->getApiToken();
}';

Related

How to Post with request to create new data from form using guzzle - Laravel

I have this function in my controller:
public function store(Request $request)
{
$client = new Client();
$headers = [
'Authorization' => $token,
'Content-Type' => 'application/json'
];
$body = '{
"DocNo": 1167722,
"AOQty": 0,
"TL": [
{
"Key": 11678,
"Code": "Screw Hex",
"Detail": true,
"DTL": []
}
]
}';
$request = new Psr7Request('POST', 'http://example.com/api/Order/', $headers, $body);
$res = $client->sendAsync($request)->wait();
echo $res->getBody();
}
that will store the data to the external API
but i want to POST the data from a form
when i work with normal routing (not API) i usually do this:
'Key' => $request->Key,
how can i achieve the above with guzzle?
currently when i submit the form from the view it will submit the above function (store) as static data, how can i submit the data from the form?
UPDATE:
When i use Http as showing below:
$store = Http::withHeaders([
'Content-Type' => 'application/json',
'Authorization' => $token,
])->post('http://example.com/api/Order/', [
'DocNo' => "SO-000284",
'AOQty' => 0.0,
'TL[Key]' => 11678,
'TL[Code]' => "SCREW HEX CAPHEAD 6X30MM",
'TL[Detail]' => true,
'TL[DTL]' => [],
]);
echo $store;
it will store everything before [TL] Array, it won't store anything of:
'TL[Key]' => 11678,
'TL[Code]' => "SCREW HEX CAPHEAD 6X30MM",
'TL[Detail]' => true,
'TL[DTL]' => [],
am i doing it wrong?
You can use HTTP client provided by Laravel.
$response = Http::withToken($token)->post('http://example.com/users', $request->only(['request_param_1', 'request_param_2']));
Or
$data = [];
$data['param_1'] = $request->get('param_1');
$data['param_2'] = $request->get('param_2');
$data['param_3'] = $request->get('param_3');
$response = Http::withToken($token)->post('http://example.com/users', $data);
Edit
$data = [
'DocNo' => "SO-000284",
'AOQty' => 0.0,
'TL' => [
'key' => 11678,
'Code' => "SCREW HEX CAPHEAD 6X30MM",
'Detail' => true,
'DTL' => [],
]
];
SOLUTION:
Using Http client:
'TL' => array ([
'Dtl' => "",
'Code' => "Screw Hex",
'IsDetail' => true,
"DTL" => [],
])
]);

How we can insert header and footer in google docs with google docs api using PHP code

I want to insert header and footer in my google docs with google docs api in PHP code. I am doing it like this-
$requests = new Google_Service_Docs_Request(array(
'createHeader' => [
'type' => 'TITLE',
'sectionBreakLocation' => [
'index' => 0
],
],
)),
$batchUpdateRequest = new Google_Service_Docs_BatchUpdateDocumentRequest(array(
'requests' => $requests
));
$response = $service->documents->batchUpdate($documentId, $batchUpdateRequest);
but, i am getting this error-
PHP Fatal error: Uncaught Google\Service\Exception: {
"error": {
"code": 400,
"message": "Invalid value at 'requests[5].create_header.type' (type.googleapis.com/google.apps.docs.v1.HeaderFooterType), \"TITLE\"",
"errors": [
{
"message": "Invalid value at 'requests[5].create_header.type' (type.googleapis.com/google.apps.docs.v1.HeaderFooterType), \"TITLE\"",
"reason": "invalid"
}
],
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "requests[5].create_header.type",
"description": "Invalid value at 'requests[5].create_header.type' (type.googleapis.com/google.apps.docs.v1.HeaderFooterType), \"TITLE\""
}
]
}
]
}
}
Please help me out with this, That how we can insert texts in header and footer in google docs using PHP.
In your script, how about the following modification?
Create header:
I thought that the reason of the error message of Invalid value at 'requests[5].create_header.type' (type.googleapis.com/google.apps.docs.v1.HeaderFooterType), \"TITLE\"" is due to 'type' => 'TITLE',. But when I saw your script, $requests is required to be an array. So how about the following modification?
From:
$requests = new Google_Service_Docs_Request(array(
'createHeader' => [
'type' => 'TITLE',
'sectionBreakLocation' => [
'index' => 0
],
],
)),
$batchUpdateRequest = new Google_Service_Docs_BatchUpdateDocumentRequest(array(
'requests' => $requests
));
To:
$requests = new Google_Service_Docs_Request(array(
'createHeader' => [
'type' => 'DEFAULT',
'sectionBreakLocation' => [
'index' => 0
],
],
));
$batchUpdateRequest = new Google_Service_Docs_BatchUpdateDocumentRequest(array(
'requests' => array($requests)
));
Create footer:
In this case, please replace createHeader to createFooter in the above $requests.
Note:
As additional information, when you want to use the first page header and footer, you can use the following request.
$requests = new Google_Service_Docs_Request(array(
'updateDocumentStyle' => [
'documentStyle' => [
'useFirstPageHeaderFooter' => true,
],
'fields' => 'useFirstPageHeaderFooter',
],
));
References:
CreateHeaderRequest
CreateFooterRequest

Convert cURL to Guzzle Post sending json data

I have this cURL command that I need to convert to PHP using Guzzle 7. I've have been researching this for a few (well, more than a few) days and getting nowhere fast. The cURL command uses the Simpli.fi api to create an organization under the parent org.
curl -i -X POST -H "X-App-Key: xxxx-xx-xx-xx-xxxxxx" -H "X-User-Key: xxxx-xx-xx-xx-xxxxxx" \
-H "Content-Type: application/json" \
-d '{
"organization": {
"name": "My New Organization",
"parent_id": "5786",
"custom_id": "<Put your organization identifier here or omit this optional field>"
}
}' \
"https://app.simpli.fi/api/organizations"
I was able to convert it using this website but it doesn't use Guzzle: https://onlinedevtools.in/curl
Here's what it gave me:
include('vendor/rmccue/requests/library/Requests.php');
Requests::register_autoloader();
$headers = array(
'X-App-Key' => 'xxxx-xx-xx-xx-xxxxxx',
'X-User-Key' => 'xxxx-xx-xx-xx-xxxxxx',
'Content-Type' => 'application/json'
);
$data = '{\n "organization": {\n "name": "My New Organization",\n "parent_id": "5786",\n "custom_id": "<Put your organization identifier here or omit this optional field>"\n }\n }';
$response = Requests::post('https://app.simpli.fi/api/organizations', $headers, $data);
Here's what I've tried so far aside from the converted code above:
public static function createOrganization()
{
self::init();
$client = new Client(['debug' => true]);
$request = $client->request('POST',
self::$baseUrl.'/organizations', [
'multipart' => [
[
'name' => 'data',
'contents' => "{'organization':{'name':'Pete's Pet Pagoda','parent_id':'1052385'}}",
],
],
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
]
]);
dd($response = $request->getStatusCode());
}
I'm getting quite a few different errors however this is the latest:
curl_setopt_array(): cannot represent a stream of type Output as a STDIO FILE*
Can anyone tell me what I'm doing wrong? Is there something missing?
UPDATE: After further research into this issue and chatting with a developer on the Laracast Slack channel, I've come to learn that this is a bug with the ['debug' => true] option when running on a Windows system and is described on this GITHUB page: https://github.com/guzzle/guzzle/issues/1413
I'm running on a Windows 10 Pro system. I corrected it on my end by changing the debug option to use fopen() like this:
'debug' => fopen('php://stderr', 'w'),
I use PHPStorm. It suggested using the 'wb' to make it binary safe. After changing it, the post requests worked fine.
You need to use body, not multipart. You can also use json.
$request = $client->request('POST',
self::$baseUrl.'/organizations', [
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'body' => [
'{"organization":
[
{
"name":"Pete`s Pet Pagoda",
"parent_id":"1052385"
}
]
}',
],
]);
method 2
You can pass array to the json request option and it will convert it to json when sending the guzzle request. No need to use header as application/json as it applies internally.
$client->request('POST',
self::$baseUrl.'/organizations', [
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'json' => [
"organization" => [
[
"name" => "Pete`s Pet Pagoda"
"parent_id" => "1052385"
]
]
],
]);
I hope this will help you. For further debugging use Middleware::tap(find more help here middleware+json+request)
try{
$client = new Client();
$response = $client->request('POST', self::$baseUrl.'/organizations',
[
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'json' => [
'organization' =>
[
"name" => "some_name_value",
"parent_id" => "id_here",
"custom_id" => "custom id here"
]
]
]);
$_response = json_decode($response->getBody()->getContents(), true);
}
catch(BadResponseException $e){
$response = $e->getResponse();
$errorArray = json_decode($response->getBody()->getContents(), true);
//echo "<pre>";
//print_r($errorArray);
//return some message from errorarray;
}

PHP / Docusign - Verify HMAC signature on completed event

I'm trying to secure my callback url when completed event is triggered.
My Controller:
public function callbackSubscriptionCompleted(
int $subscriptionId,
DocusignService $docusignService,
Request $request
) {
$signature = $request->headers->get("X-DocuSign-Signature-1");
$payload = file_get_contents('php://input');
$isValid = $docusignService->isValidHash($signature, $payload);
if (!$isValid) {
throw new ApiException(
Response::HTTP_BAD_REQUEST,
'invalid_subscription',
'Signature not OK'
);
}
return new Response("Signature OK", Response::HTTP_OK);
}
My DocusignService functions:
private function createEnvelope(Company $company, Subscription $subscription, LegalRepresentative $legalRepresentative, Correspondent $correspondent, $correspondents) : array
{
// ...
$data = [
'disableResponsiveDocument' => 'false',
'emailSubject' => 'Your Subscription',
'emailBlurb' => 'Subscription pending',
'status' => 'sent',
'notification' => [
'useAccountDefaults' => 'false',
'reminders' => [
'reminderEnabled' => 'true',
'reminderDelay' => '1',
'reminderFrequency' => '1'
],
'expirations' => [
'expireEnabled' => 'True',
'expireAfter' => '250',
'expireWarn' => '2'
]
],
'compositeTemplates' => [
[
'serverTemplates' => [
[
'sequence' => '1',
'templateId' => $this->templateId
]
],
'inlineTemplates' => [
[
'sequence' => '2',
'recipients' => [
'signers' => [
[
'email' => $legalRepresentative->getEmail(),
'name' => $legalRepresentative->getLastname(),
'recipientId' => '1',
'recipientSignatureProviders' => [
[
'signatureProviderName' => 'universalsignaturepen_opentrust_hash_tsp',
'signatureProviderOptions' => [
'sms' => substr($legalRepresentative->getCellphone(), 0, 3) == '+33' ? $legalRepresentative->getCellphone() : '+33' . substr($legalRepresentative->getCellphone(), 1),
]
]
],
'roleName' => 'Client',
'clientUserId' => $legalRepresentative->getId(),
'tabs' => [
'textTabs' => $textTabs,
'radioGroupTabs' => $radioTabs,
'checkboxTabs' => $checkboxTabs
]
]
]
]
]
]
]
],
'eventNotification' => [
"url" => $this->router->generate("api_post_subscription_completed_callback", [
"subscriptionId" => $subscription->getId()
], UrlGeneratorInterface::ABSOLUTE_URL),
"includeCertificateOfCompletion" => "false",
"includeDocuments" => "true",
"includeDocumentFields" => "true",
"includeHMAC" => "true",
"requireAcknowledgment" => "true",
"envelopeEvents" => [
[
"envelopeEventStatusCode" => "completed"
]
]
]
];
$response = $this->sendRequest(
'POST',
$this->getBaseUri() . '/envelopes',
[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->getCacheToken()
],
json_encode($data)
);
}
public function isValidHash(string $signature, string $payload): bool
{
$hexHash = hash_hmac('sha256',utf8_encode($payload),utf8_encode($this->hmacKey));
$base64Hash = base64_encode(hex2bin($hexHash));
return $signature === $base64Hash;
}
I've created my hmac key in my Docusign Connect and i'm receiving the signature in the header and the payload but the verification always failed.
I've followed the Docusign documentation here
What's wrong ?
PS: Sorry for my bad english
Your code looks good to me. Make sure that you are only sending one HMAC signature. That way your hmacKey is the correct one.
As a check, I'd print out the utf8_encode($payload) and check that it looks right (it should be the incoming XML, no headers). Also, I don't think it should have a CR/NL in it at the beginning. That's the separator between the HTTP header and body.
Update
I have verified that the PHP code from the DocuSign web site works correctly.
The payload value must not contain either a leading or trailing newline. It should start with <?xml and end with >
I suspect that your software is adding a leading or trailing newline.
The secret (from DocuSign) ends with an =. It is a Base64 encoded value. Do not decode it. Just use it as a string.
Another update
The payload (the body of the request) contains zero new lines.
If you're printing the payload, you'll need to wrap it in <pre> since it includes < characters. Or look at the page source.
It contains UTF-8 XML such as
<?xml version="1.0" encoding="utf-8"?><DocuSignEnvelopeInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.docusign.net/API/3.0"><EnvelopeStatus><RecipientStatuses><RecipientStatus><Type>Signer</Type><Email>larry#worldwidecorp.us</Email><UserName>Larry Kluger</UserName><RoutingOrder>1</RoutingOrder><Sent>2020-08-05T03:11:13.057</Sent><Delivered>2020-08-05T03:11:27.657</Delivered><DeclineReason xsi:nil="true" /><Status>Delivered</Status><RecipientIPAddress>5.102.239.40</RecipientIPAddress><CustomFields /><TabStatuses><TabStatus><TabType>Custom</TabType><Status>Active</Status><XPosition>223</XPosition><YPosition>744....
We've done some more testing, and the line
$payload = file_get_contents('php://input');
should be very early in your script. The problem is that a framework can munge the php://input stream so it won't work properly thereafter.
Note this page from the Symfony site -- it indicates that the right way to get the request body is:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;
$app->before(function (Request $request) {
$payload = $request->getContent();
hmac_verify($payload, $secret);
});
I would try to use the Symfony code instead of file_get_contents('php://input');

Microsoft graph Unable to read JSON request payload

I'm trying to create subscriptions with microsoft graph in php, however I am unable to see what is going wrong at this point.
The code is breaking at the following:
protected $http_subscribe = "https://graph.microsoft.com/v1.0/subscriptions";
public function getSubscriptions()
{
if(empty($this->token)) {
dd('no token supplied'); //some debugging
}
$date = $this->endTimeDate->format('Y-m-d'); //This is Carbon date
$time = $this->endTimeDate->format('H:i:s.u');
$response = $this->client->request('POST', $this->http_subscribe, [
'headers' => [
'Authorization' => 'Bearer ' . $this->token,
'Content-Type' => "application/json"
], 'form_params' => [
"changeType" => "created,updated",
"notificationUrl" => "https://website.test/notify",
"resource" => "me/mailFolders('Inbox')/messages",
"expirationDateTime" => $date.'T'.$time,
"clientState" => "secretClientValue"
]
]);
dd($response);
}
The full error I'm getting is:
"""
{\r\n
"error": {\r\n
"code": "BadRequest",\r\n
"message": "Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.",\r\n
"innerError": {\r\n
"request-id": "063d9947-80fd-461d-a70e-q0bd8eee9d56",\r\n
"date": "2018-08-07T08:20:54"\r\n
}\r\n
}\r\n
}
"""
Now I get what the error says, my json would be invalid, however this array is in correct json format, I'm using Guzzle as my Client.
You should use json option. Try this:
$response = $this->client->request('POST', $this->http_subscribe, [
'headers' => [
'Authorization' => 'Bearer ' . $this->token,
],
'json' => [
"changeType" => "created,updated",
"notificationUrl" => "https://website.test/notify",
"resource" => "me/mailFolders('Inbox')/messages",
"expirationDateTime" => $date.'T'.$time,
"clientState" => "secretClientValue",
],
]);

Categories