Hotelbeds PHP API - how to get daily rate of available hotel rooms? - php

I am working with Hotelbeds APItude PHP API to find out the information of available hotel rooms.
Well, I am able to get all information of available hotels through the documentation
But, I am facing problem with getting daily rate of all available hotel rooms.
There is an option in documentation to send request attribute for getting daily rate of each room. Here is the request attribute -
availabilityRQ/#dailyRate -- Boolean -- Optional -- Display the rate day-by-day
dailyRate is an boolean value that confirms is the API ll send back daily rate info (I think so far).
So, in implementation, I send following request parameter -
$rqData = new \hotelbeds\hotel_api_sdk\helpers\Availability();
$rqData->stay = new Stay(DateTime::createFromFormat("Y-m-d", "2016-09-01"),
DateTime::createFromFormat("Y-m-d", "2016-09-10"));
$rqData->hotels = ["hotel" => [1067, 1070,]];
// $rqData->destination = new Destination("PMI");
$occupancy = new Occupancy();
$occupancy->adults = 2;
$occupancy->children = 1;
$occupancy->rooms = 1;
$occupancy->paxes = [new Pax(Pax::AD, 30, "Mike", "Doe"), new Pax(Pax::AD, 27, "Jane", "Doe"), new Pax(Pax::CH, 8, "Mack", "Doe")];
$rqData->occupancies = [$occupancy];
$rqData->dailyRate = TRUE;
$availRS = $apiClient->Availability($rqData);
I checked, everything work fine except the $rqData->dailyRate = TRUE; parameter.
I get following error -
Bad Request: The request is not compliant with the specified version of the API. Error at property dailyRate: Can not construct instance of boolean from String value 'Y': only "true" or "false" recognized
I think, I am missing something like creating boolean parameter for the dailyRate attribute.
How can I solve the issue?

This looks like a bug with their public API or their PHP library. File a bug report.
class Availability extends ApiHelper. class ApiHelper extends DataContainer.
In DataContainer, if (is_bool($item)) return $item ? "Y" : "N";.
https://github.com/hotelbeds-sdk/hotel-api-sdk-php/blob/56b4621bbf488a878f1eb1a194f6a63d928dc9db/src/generic/DataContainer.php#L105
Their DataContainer.php in their library casts boolean values to Y or N. As such, when you do your API request - any values which are true/false get converted to Y/N.
But their API requires true/false.

Related

DocuSign - Can't set "sent" on createEnvelope

I am using Docusign PHP Client and trying to create and envelope and send it as email. With the current SDK, I was getting an error:
INVALID_REQUEST_BODY The request body is missing or improperly formatted. Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'API_REST.Models.v2.document[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\n ◀
To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive t ▶
Path 'documents.documentBase64', line 1, position 31.
So I had to edit EnvelopeApi.php (line 2876) json_encode($httpBody) to make it work.
Now that it's working, I receive a response like this, however I can't change status created to sent is my problem.
EnvelopeSummary {#460 ▼
#container: array:4 [▼
"envelope_id" => "6b9ef863-2ee0-4ea6-9f7e-20b7d4f59b22"
"status" => "created"
"status_date_time" => "2018-10-03T12:30:22.8600000Z"
"uri" => "/envelopes/6b9ef863-2ee0-4ea6-9f7e-20b7d4f59b22"
]
}
My full code:
First, I authenticated and fetched my $accountId
And then creating Envelope:
$path = public_path('test.pdf');
$b64Doc = base64_encode(file_get_contents($path));
$document = new Document();
$document->setName("TEST.pdf");
$document->setFileExtension("pdf");
$document->setDocumentId(1);
$document->setDocumentBase64($b64Doc);
$sign_here = new SignHere();
$sign_here->setXPosition(25);
$sign_here->setYPosition(50);
$sign_here->setDocumentId(1);
$sign_here->setPageNumber(1);
$sign_here->setRecipientId(1);
$tabs = new Tabs();
$tabs->setSignHereTabs($sign_here);
$signers = new Signer();
$signers->setName('Test User');
$signers->setEmail('test#mailinator.com');
$signers->setRoleName('Signer');
$signers->setRecipientId(1);
$signers->setRoutingOrder(1);
$signers->setTabs($tabs);
$recipients = new Recipients();
$recipients->setSigners($signers);
$envelope_definition = new EnvelopeDefinition();
$envelope_definition->setEmailSubject('Signature Request');
$envelope_definition->setStatus("sent"); // ***
$envelope_definition->setDocuments($document);
$envelope_definition->setRecipients($recipients);
$options = new CreateEnvelopeOptions();
$options->setCdseMode(null);
$options->setMergeRolesOnDraft(null);
try {
$envelopeSummary = $envelopeApi->createEnvelope($accountId, $envelope_definition, $options);
dd($envelopeSummary);
// Also tried this:
// $envelopeApi->update($accountId, $envelopeSummary->getEnvelopeId(), json_encode(['status' => 'sent']);
} catch (ApiException $e){
dd($e->getResponseBody()->errorCode . " " . $e->getResponseBody()->message);
}
$envelope_definition->setStatus("sent"); this should trigger the email, right? But it doesn't for some reason. Also I can't see this created envelope in my Sandbox either.
You are not setting signers correctly. It must be an array of signer objects.
Here is some untested code:
# This code creates a signer, not signers
$signer = new Signer();
$signer->setName('Test User');
$signer->setEmail('test#mailinator.com');
$signer->setRoleName('Signer');
$signer->setRecipientId(1);
$signer->setRoutingOrder(1);
$signer->setTabs($tabs);
$recipients = new Recipients();
# setSigners wants an array of signer objects.
# in this case, we make an array with one element
$recipients->setSigners(array($signer));
Also, you are not creating the tabs right either. Again, it needs to be an array of the tab type.
See this example for additional ideas.
Yes, setting status to sent should make DocuSign send the envelope upon creation. The fact that the response contains "status" => "created" seems to indicate that your setting of the property ($envelope_definition->setStatus("sent");) is not actually being included as part of the request that's being issued to DocuSign.
I've compared your code with the code examples provided in GitHub for the PHP SDK, specifically, with the signatureRequestOnDocument function on that page. The only obvious difference I can see between your code and that example code is in the syntax for creating objects. For example, creating the envelope:
Your code: $envelope_definition = new EnvelopeDefinition();
PHP SDK example code: $envelop_definition = new DocuSign\eSign\Model\EnvelopeDefinition();
I don't know much about PHP, let alone about the DocuSign PHP SDK, but I'd suggest that you try to closely mimic the code examples that are part of the SDK repo on GitHub, to see if you get a different result that way.
My code work like this :
$signersArray = array();
$signer = new Signer();
$signer->set...
$signersArray[] = $signer;
$recipients->setSigners($signersArray);
If it's not working try to dump the data send from the SDK to the API and double check that the status is correct :
Go to Docusign/esign-client/src/ApiClient.php and var_dump($postData) at line 159

Is there a limit to wp_remote_get and/or simplexml_load_string from Vimeo?

I know *wp_remote_get* is a WordPress function, and that I should be posting this in wordpress.stackexchange, however, I am almost certain my issue lies more on the general PHP side.
The problem: I need to retrieve all Vimeo videos found within an album, yet I am only getting 20.
The approach:
$vmg_feed_url = 'http://vimeo.com/api/v2/';
$vmg_user = '2212323';
if($vmg_type == 'user'){ /** just an input variable checking whether the function should search for user or for album. in our case, it searches for album **/
$vmg_type_url = '';
} else {
$vmg_type_url = $vmg_type . '/';
}
$vmg_videos_url = $vmg_feed_url . $vmg_type_url . $vmg_user . '/videos.xml';
$videos_result = wp_remote_get($vmg_videos_url);
$vmg_videos = simplexml_load_string($videos_result['body']);
The resulting XML is http://vimeo.com/api/v2/album/2212323/videos.xml - and as you can see, I am only retrieving 20 videos.
The question: Am I missing something? Is there a function/variable that sets a limit to the amount of videos I can retrieve? I know that wp_remote_get gives me these attributes (from the WordPress Codex):
$url
(string) (required) Universal Resource Locator (URL).
- Default: None
$args
(array) (optional)
- Default: method: GET, timeout: 5, redirection: 5, httpversion: 1.0, blocking: true, headers: array(), body: null, cookies: array()
Any help is truly appreciated. Please let me know if I forgot about any details!
Yor problem is not in wordpress or PHP. It is API limits:
http://developer.vimeo.com/apis/simple
Simple API responses include up to 20 items per page.
You can get more by adding ?page parameter in next requests.

Salesforce: Creating OpportunityLineItems as part of the Opportunity in PHP

I am trying to bulk upload 'Opportunities' into Salesforce using PHP Toolkit 20.0 and the Enterprise SOAP API.
The way I have found to do it is to create an Opportunity object and then create it in Salesforce via the SOAP API, then on the response I take the Id and use that for each 1..n OpportunityLineItems that exists for that Opportunity.
This isn't very efficient as it use 2 SOAP API calls and when done in bulk uses a lot of resources and is liable to timeouts. (I do not want to reduce the amount sent in one go as the API calls are limited)
Therefore is there a way to create both the Opportunity and it's OpportunityLineItems in a single API call?
I tried the following:
$opp = new stdClass();
$opp->Name = 'Opp1';
$opp->StageName = 'Closed Won';
$opp->Account = new stdClass();
$opp->Account->Custom_ID__c = '1234';
$opp->Pricebook2Id = '...';
$opp->OpportunityLineItems = array();
$opp->OpportunityLineItems[0] = new stdClass();
$opp->OpportunityLineItems[0]->Description = 'Product name';
$opp->OpportunityLineItems[0]->Quantity = 1;
$opp->OpportunityLineItems[0]->UnitPrice = 10.00;
...
$opp->OpportunityLineItems[n] = new stdClass();
$opp->OpportunityLineItems[n]->Description = 'Product name';
$opp->OpportunityLineItems[n]->Quantity = 1;
$opp->OpportunityLineItems[n]->UnitPrice = 10.00;
But it resulted in:
INVALID_FIELD: No such column 'OpportunityLineItems' on entity 'Opportunity'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names.
Which was to be expected as the WSDL file states that OpportunityLineItems is of type tns:QueryResult rather than an ens:
EDIT: Complete overhaul to show how multiple opps can be added at same time. Should be useful if you can stagger their creation somehow (store them in your local database and upload only when you got a couple/sufficient time has passed/user pressed "flush the queue" button).
Warning, the code actually looks more scary now, you might be better of checking previous version in edit history first.
It won't be too hard to create an Apex class that would accept incoming request with 2 parameters and attempt to insert them. Go to Setup->Develop->Classes->New and try this:
global with sharing class OpportunityLinkedInsert{
static webservice Opportunity insertSingle(Opportunity opp, OpportunityLineItem[] lines){
if(opp == null || lines == null){
throw new IntegrationException('Invalid data');
}
Opportunity[] result = insertMultiple(new List<Opportunity>{opp}, new List<List<OpportunityLineItem>>{lines});
return result[0]; // I imagine you want the Id back :)
}
/* I think SOAP doesn't handle method overloading well so this method has different name.
'lines' are list of lists (jagged array if you like) so opps[i] will be inserted and then lines[i] will be linked to it etc.
You can insert up to 10,000 rows in one go with this function (remember to count items in both arrays).
*/
static webservice List<Opportunity> insertMultiple(List<Opportunity> opps, List<List<OpportunityLineItem>> lines){
if(opps == null || lines == null || opps.size() == 0 || opps.size() != lines.size()){
throw new IntegrationException('Invalid data');
}
insert opps;
// I need to flatten the structure before I insert it.
List<OpportunityLineItem> linesToInsert = new List<OpportunityLineItem>();
for(Integer i = 0; i < opps.size(); ++i){
List<OpportunityLineItem> linesForOne = lines[i];
if(linesForOne != null && !linesForOne.isEmpty()){
for(Integer j = 0; j < linesForOne.size(); ++j){
linesForOne[j].OpportunityId = opps[i].Id;
}
linesToInsert.addAll(linesForOne);
}
}
insert linesToInsert;
return opps;
}
// helper class to throw custom errors
public class IntegrationException extends Exception{}
}
You'll also need an unit test class before this can go to your production organisation. Something like that should do (needs to be filled with couple more things before being 100% usable, see this question for more info).
#isTest
public class OpportunityLinkedInsertTest{
private static List<Opportunity> opps;
private static List<List<OpportunityLineItem>> items;
#isTest
public static void checSingleOppkErrorFlow(){
try{
OpportunityLinkedInsert.insertSingle(null, null);
System.assert(false, 'It should have failed on null values');
} catch(Exception e){
System.assertEquals('Invalid data',e.getMessage());
}
}
#isTest
public static void checkMultiOppErrorFlow(){
prepareTestData();
opps.remove(1);
try{
OpportunityLinkedInsert.insertMultiple(opps, items);
System.assert(false, 'It should have failed on list size mismatch');
} catch(Exception e){
System.assertEquals('Invalid data',e.getMessage());
}
}
#isTest
public static void checkSuccessFlow(){
prepareTestData();
List<Opportunity> insertResults = OpportunityLinkedInsert.insertMultiple(opps, items);
List<Opportunity> check = [SELECT Id, Name,
(SELECT Id FROM OpportunityLineItems)
FROM Opportunity
WHERE Id IN :insertResults
ORDER BY Name];
System.assertEquals(items[0].size(), check[0].OpportunityLineItems.size(), 'Opp 1 should have 1 product added to it');
System.assertEquals(items[1].size(), check[0].OpportunityLineItems.size(), 'Opp 3 should have 1 products');
}
// Helper method we can reuse in several tests. Creates 2 Opportunities with different number of line items.
private static void prepareTestData(){
opps = new List<Opportunity>{
new Opportunity(Name = 'Opp 1', StageName = 'Prospecting', CloseDate = System.today() + 10),
new Opportunity(Name = 'Opp 2', StageName = 'Closed Won', CloseDate = System.today())
};
// You might have to fill in more fields here!
// Products are quite painful to insert with all their standard/custom pricebook dependencies etc...
items = new List<List<OpportunityLineItem>>{
new List<OpportunityLineItem>{
new OpportunityLineItem(Description = 'Opp 1, Product 1', Quantity = 1, UnitPrice = 10)
},
new List<OpportunityLineItem>{
new OpportunityLineItem(Description = 'Opp 2, Product 1', Quantity = 1, UnitPrice = 10),
new OpportunityLineItem(Description = 'Opp 2, Product 2', Quantity = 1, UnitPrice = 10),
new OpportunityLineItem(Description = 'Opp 2, Product 3', Quantity = 1, UnitPrice = 10)
}
};
}
}
That's pretty much in terms of Apex code.
If either of inserts will fail you'll get a SOAP Exception back. This is also a bit better in terms of transactions, ACID etc - if insert of your line items will fail, are you prepared to clean it up from PHP side? What if some automated email notifications etc. were set up in Salesforce and already sent? Having it in one call to Apex will make sure whole request will be rolled back, pretty much like stored procedures work in the databases.
Try to create these classes in sandbox, then locate first one on the list of classes. It will have a link to generate a WSDL file which you can use to generate your PHP classes.
Going to the second one you'll see a "Run Tests" button. You'll have to make sure the test passes before pushing it to your production org - but that's whole new world of programming on the platform for you :)

How do I pull the subscriber count from a MailChimp list with the API?

I'm trying to display the subscriber count from a MailChimp mailing list using their API, and I've got it partially working, except the code below is currently spitting out the subscriber count for all lists, rather than for one specific list. I've specified the list id in the line $listId ='XXX'; but that doesn't seem to be working. Because I have five lists in total, the output from the PHP below shows this:
10 0 0 1 9
What do I need to do in my code below to get the subscriber count from a specific list id?
<?php
/**
This Example shows how to pull the Members of a List using the MCAPI.php
class and do some basic error checking.
**/
require_once 'inc/MCAPI.class.php';
$apikey = 'XXX';
$listId = 'XXX';
$api = new MCAPI($apikey);
$retval = $api->lists();
if ($api->errorCode){
echo "Unable to load lists()!";
echo "\n\tCode=".$api->errorCode;
echo "\n\tMsg=".$api->errorMessage."\n";
} else {
foreach ($retval['data'] as $list){
echo "\t ".$list['stats']['member_count'];
}
}
?>
I just came across this function (see below) that let's me return a single list using a known list_id. The problem is, I'm not sure how to add the list_id in the function.
I'm assuming I need to define it in this line? $params["filters"] = $filters;
The MailChimp lists() method documentation can be referred to here: http://apidocs.mailchimp.com/rtfm/lists.func.php
function lists($filters=array (
), $start=0, $limit=25) {
$params = array();
$params["filters"] = $filters;
$params["start"] = $start;
$params["limit"] = $limit;
return $this->callServer("lists", $params);
}
I'd strongly recommend not mucking with the internals of the wrapper as it's not going to be nearly as helpful as the online documentation and the examples included with the wrapper. Using the wrapper means the line you tracked down will effectively be filled when make the proper call.
Anywho, this is what you want:
$filters = array('list_id'=>'XXXX');
$lists = $api->lists($filters);
Mailchimp provides a pre-built php wrapper around their api at http://apidocs.mailchimp.com/downloads/#php. This api includes a function lists() which, according to its documentation, returns among other things:
int member_count The number of active members in the given list.
It looks like this is the function which you are referring to above. All you should have to do is iterate through the lists that are returned to find the one with the proper id. From there you should be able to query the subscriber count along with a number of other statistics about the list.

Netsuite: How to attach custom fields to sales orders

The documentation for Netsuite is quite lacking, they cover the basics and then let you loose to explore. Anyone without a vast knowledge of PHP trying to use their php toolkit would be on their knees begging for mercy.
At any point throughout this whole project it's been trail and error and trying to make sense out of everything until stuff started to work.
I'm stumped on assigning custom fields to sales orders, I know it has to be an object of an object of an object in order for it to tier down the xml for the soap to take over but what with what with what?
I have some code I worked that is getting somewhere but it is complaining it's not the right RecordRef type. If anyone worked with Netsuite and feels my pain please lend me your knowledge before I pull out all my hair.
Thanks in advance.
Code:
$customFields = array('internalId' => 'custbody_new_die_yn','value' => array('name' => 'custbody_new_die_yn','internalId' => 'NO'));
$customObject = new nsComplexObject("SelectCustomFieldRef");
$customObject->setFields($customFields);
$salesOrderFields = array(
'entity' => new nsRecordRef(array('internalId' => $userId)),
'paymentMethod' => array('internalId' => 8),
'ccNumber' => 4111111111111111,
'ccExpireDate' => date("c", mktime(0,0,0,11,1,2011)),
'ccName' => 'Test Testerson',
'itemList' => array(
'item' => array(
'item' => array('internalId' => 5963),
'quantity' => 5
)
),
'department' => new nsRecordRef(array('internalId' => 1)),
'class' => new nsRecordRef(array('internalId' => 47)),
'customFieldList' => $customObject
);
I am not familiar using PHP with Netsuite but I have done a good amount of c#/.net Netsuite work. As Craig mentioned I find it much easier using a language such c#/.net with a Visual Studio generated interface to figure out what is available in the Netsuite SuiteTalk web service API.
There is a fair amount of documentation around this stuff in the NetSuite Help Center - by no means everythign you will need but a good start. Netsuite Help Center
Check out the SuiteFlex/SuiteTalk (Web Services) section specifically this page on Ids & References.
Using Internal Ids, External Ids, and References
With that said I will try to help with a .net example & explanation of adding a custom field to a Sales Order.
Here are a few examples of adding different CustomFieldRefs:
//A list object to store all the customFieldRefs
List<CustomFieldRef> oCustomFieldRefList = new List<CustomFieldRef>();
//List or Record Type reference
SelectCustomFieldRef custbody_XXX_freight_terms = new SelectCustomFieldRef();
custbody_XXX_freight_terms.internalId = "custbody_XXX_freight_terms";
ListOrRecordRef oFreightTermsRecordRef = new ListOrRecordRef();
oFreightTermsRecordRef.internalId = <internalId of specific record in Netsuite>;
//See the References link above for more info on this - trying to figure out typeId caused me a lot of pain.
oFreightTermsRecordRef.typeId = <internalId of the List Record Type in Netsuite>;
custbody_XXX_freight_terms.value = oFreightTermsRecordRef;
oCustomFieldRefList.Add(custbody_XXX_freight_terms);
//Freeform text sorta field
StringCustomFieldRef objStringCustomFieldRef = new StringCustomFieldRef();
objStringCustomFieldRef.internalId = "custbody_XXX_tracking_link";
objStringCustomFieldRef.value = "StringValue";
oCustomFieldRefList.Add(objStringCustomFieldRef);
//Checkbox field type
BooleanCustomFieldRef custbody_XXX_if_fulfilled = new BooleanCustomFieldRef();
custbody_XXX_if_fulfilled.internalId = "custbody_XXX_if_fulfilled";
custbody_XXX_if_fulfilled.value = true;
oCustomFieldRefList.Add(custbody_XXX_if_fulfilled);
//By far the most complicated example a multi-select list referencing other records in Netsuite
MultiSelectCustomFieldRef custrecord_XXX_transaction_link = new MultiSelectCustomFieldRef();
//internal id of field you are updating
custrecord_XXX_transaction_link.internalId = "custrecord_XXX_transaction_link";
List<ListOrRecordRef> oListOrRecordRefList = new List<ListOrRecordRef>();
ListOrRecordRef oListOrRecordRefItemFulfillment = new ListOrRecordRef();
oListOrRecordRefItemFulfillment.name = "Item Fulfillment";
oListOrRecordRefItemFulfillment.internalId = <ItemFulfillmentInternalId>;
//Item Fulfillment is record type (Transaction -30) - this is from the above Reference links
oListOrRecordRefItemFulfillment.typeId = "-30";
oListOrRecordRefList.Add(oListOrRecordRefItemFulfillment);
ListOrRecordRef oListOrRecordRefSalesOrder = new ListOrRecordRef();
oListOrRecordRefSalesOrder.name = "Sales Order";
oListOrRecordRefSalesOrder.internalId = <SalesOrderInternalId>;
//Sales Order is record type (Transaction -30) - this is from the above Reference links
oListOrRecordRefSalesOrder.typeId = "-30";
oListOrRecordRefList.Add(oListOrRecordRefSalesOrder);
//Add array of all the ListOrRecordRefs to the MultiSelectCustomFieldRef
custrecord_XXX_transaction_link.value = oListOrRecordRefList.ToArray();
oCustomFieldRefList.Add(custrecord_XXX_transaction_link);
//And then add all these to the Custom Record List (Array) on the Sales Order Record
objSalesOrder.customFieldList = oCustomFieldRefList.ToArray();
From what I can tell in your above example I think your issue is with the ListOrRecordRef typeId. Its hard to tell from your example what typeId you are referencing but if you can figure that out and set the TypeId on your SelectCustomFieldRef I think that should fix your issue.
The Custom Field Ref Internal ID is the reference ID on the record you are trying to update. This can be found in the Transaction Body fields for that record within Netsuite.
The Internal ID for the ListOrRecordRef is the internal ID for the actual list item or record that you want to attach to the previously mentioned record
The typeID for the ListOrRecordRef is the internal ID for the custom list/record. This is the parent ID for the previous internal ID, and is not inherently tied to the original record.

Categories