xero API : Struggling to add a basic invoice - php

Updated to show new code / new error # 10/12/2020 11:30
I need to copy invoices from my website to xero using their API. I managed to get this working using the OAuth 1 but now need to update to OAuth2.0.
I've installed xero-php-oauth2-starter which connects and works fine with the examples. e.g. if I click the example "Create one Contact" link, the demo contact is created in xero.
As such, I know the link is working.
I've been looking around trying to find an example of how to create an invoice using the API but am finding it incredibly hard to work out.
Below is what I currently have in my xero api folder:
<?php
ini_set('display_errors', 'On');
require __DIR__ . '/vendor/autoload.php';
require_once('storage.php');
// Use this class to deserialize error caught
use XeroAPI\XeroPHP\AccountingObjectSerializer;
// Storage Classe uses sessions for storing token > extend to your DB of choice
$storage = new StorageClass();
$xeroTenantId = (string)$storage->getSession()['tenant_id'];
if ($storage->getHasExpired()) {
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'REMOVED',
'clientSecret' => 'REMOVED',
'redirectUri' => 'https://REMOVED/callback.php',
'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize',
'urlAccessToken' => 'https://identity.xero.com/connect/token',
'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'
]);
$newAccessToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $storage->getRefreshToken()
]);
// Save my token, expiration and refresh token
$storage->setToken(
$newAccessToken->getToken(),
$newAccessToken->getExpires(),
$xeroTenantId,
$newAccessToken->getRefreshToken(),
$newAccessToken->getValues()["id_token"] );
}
$config = XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$storage->getSession()['token'] );
$apiInstance = new XeroAPI\XeroPHP\Api\AccountingApi(
new GuzzleHttp\Client(),
$config
);
$invoices = '{"invoices":[{
"type":"Invoice.TypeEnum.ACCREC",
"contact":{"contactID":"97af3783-0f32-42be-a06d-8c586c8aa8ec"},
"lineItems":[{
"description":"Acme Tires",
"quantity":2,
"unitAmount":20,
"accountCode":"000",
"taxType":"NONE",
"lineAmount":40
}],
"date":"2019-03-11",
"dueDate":"2018-12-10",
"reference":"Website Design",
"status":"Invoice.StatusEnum.DRAFT"
}]}';
foreach ($import as $invoice) {
//create contact
$xcontact = new XeroAPI\XeroPHP\Models\Accounting\Contact();
$xcontact->setName($contactname);
//create line item
$lineItems = array();
foreach ($invoice['invoiceLineItems'] as $line) {
$newLine = new XeroAPI\XeroPHP\Models\Accounting\LineItem();
$newLine->setDescription($invoice['message']."\n".$line['description']);
$newLine->setQuantity($line['quantity']);
$newLine->setUnitAmount($line['amount']);
$newLine->setAccountCode('200');
$lineItems[] = $newLine;
}
$xinvoice = new XeroAPI\XeroPHP\Models\Accounting\Invoice();
$xinvoice->setType("ACCREC");
$xinvoice->setStatus("AUTHORISED");
$xinvoice->setDate($invoice['invoiceDate']);
$xinvoice->setDueDate($invoice['dueDate']);
$xinvoice->setLineAmountTypes("NoTax");
$xinvoice->setContact($xcontact);
$xinvoice->setLineItems($lineItems);
$xinvoice->setInvoiceNumber("INV-0".$invoice['invoiceNumber']);
$xinvoices['Invoices'][] = $xinvoice;
}
$apiResponse = $apiInstance->createInvoices($xeroTenantId,$xinvoices);
$summarize_errors = false; // bool | If false return 200 OK and mix of successfully created objects and any with validation errors
$unitdp = 4; // int | e.g. unitdp=4 – (Unit Decimal Places) You can opt in to use four decimal places for unit amounts
?>
gives
Fatal error: Uncaught InvalidArgumentException: Missing the required parameter $invoices when calling createInvoices
which I can't seem to find any details on.
I've created the product "Acme Tires" in xero just incase that was causing the issue (I remember in auth 1 that it wouldn't work if the product wasn't listed first).
Any help would be greatly appreciated.

With the new PHP SDK you need to pass an array of Invoice objects.
The following creates Invoices based on the contents of a pre-existing array ($import).
You'll see that you need to place the line items into an array, and then insert that into the invoice object. The invoice itself is then inserted into an array which is passed to the API.
<?php
foreach ($import as $invoice) {
//create contact
$xcontact = new XeroAPI\XeroPHP\Models\Accounting\Contact();
$xcontact->setName($contactname);
//create line item
$lineItems = array();
foreach ($invoice['invoiceLineItems'] as $line) {
$newLine = new XeroAPI\XeroPHP\Models\Accounting\LineItem();
$newLine->setDescription($invoice['message']."\n".$line['description']);
$newLine->setQuantity($line['quantity']);
$newLine->setUnitAmount($line['amount']);
$newLine->setAccountCode('200');
$lineItems[] = $newLine;
}
$xinvoice = new XeroAPI\XeroPHP\Models\Accounting\Invoice();
$xinvoice->setType("ACCREC");
$xinvoice->setStatus("AUTHORISED");
$xinvoice->setDate($invoice['invoiceDate']);
$xinvoice->setDueDate($invoice['dueDate']);
$xinvoice->setLineAmountTypes("NoTax");
$xinvoice->setContact($xcontact);
$xinvoice->setLineItems($lineItems);
$xinvoice->setInvoiceNumber("INV-0".$invoice['invoiceNumber']);
$xinvoices['Invoices'][] = $xinvoice;
}
$apiResponse = $apiInstance->createInvoices($xeroTenantId,$xinvoices);
?>
Update:
$apiInstance will have been created earlier eg.
$apiInstance = new XeroAPI\XeroPHP\Api\AccountingApi(
new GuzzleHttp\Client(),
$config
);
$import was an array in the original script that contained the raw data I wanted to import. You would need to replace this with your own data.
Update 2:
To use your original data, you'll need to remove the
foreach ($import as $invoice) {
loop.
and replace the references to $invoice with your own data.
For example:
//create contact
$xcontact = new XeroAPI\XeroPHP\Models\Accounting\Contact();
$xcontact->setName($contactname);
Would become:
//create contact
$xcontact = new XeroAPI\XeroPHP\Models\Accounting\Contact();
$xcontact->setContactId("97af3783-0f32-42be-a06d-8c586c8aa8ec");

Related

Create a contact in Xero and get its contact Id using php api (oauth2)

Just learning Xero API (php) but I'm unsure how to proceed. The docs are pretty good in general. I've successfully created the oauth2 integration and this connects no problem (even for multiple organisations/tenants), I'm able to get the existing contacts in Xero but I now need to create a new contact (I have the name of this conteact - call her Jane Doe) i then wish to update my database record with this new Contacts contactId.
So the docs are a bit confusing but looking at the php api i think i can use:
$response = $accountingApi->setContacts( $xeroTenantId, '{"Name": "Jane Doe"}' );
would this be the right kind of approach (where $accountingApi is defined in a call earlier in the cycle and is connected)? does anyone have an example on how to add a new contact to Xero and return this new contacts contactId?
The docs don't say what (if any) response is returned after adding a new contact.
Lastly somewhat related to this, Some of my contacts are in more than one linked organisations, would these have the same clientID or will i need to somehow define one for each connected organisation?
thanks in advance
ADDITONAL
the api docs on github have this snippet:
try {
$person = new XeroAPI\XeroPHP\Models\Accounting\ContactPerson;
$person->setFirstName("John")
->setLastName("Smith")
->setEmailAddress("john.smith#24locks.com")
->setIncludeInEmails(true);
$arr_persons = [];
array_push($arr_persons, $person);
$contact = new XeroAPI\XeroPHP\Models\Accounting\Contact;
$contact->setName('FooBar')
->setFirstName("Foo")
->setLastName("Bar")
->setEmailAddress("ben.bowden#24locks.com")
->setContactPersons($arr_persons);
$arr_contacts = [];
array_push($arr_contacts, $contact);
$contacts = new XeroAPI\XeroPHP\Models\Accounting\Contacts;
$contacts->setContacts($arr_contacts);
$apiResponse = $accountingApi->createContacts($xeroTenantId,$contacts);
$message = 'New Contact Name: ' . $apiResponse->getContacts()[0]->getName();
} catch (\XeroAPI\XeroPHP\ApiException $e) {
$error = AccountingObjectSerializer::deserialize(
$e->getResponseBody(),
'\XeroAPI\XeroPHP\Models\Accounting\Error',
[]
);
$message = "ApiException - " . $error->getElements()[0]["validation_errors"][0]["message"];
}
I require only the name on Xero (all other details are in my linked App) and to obtain the contactId.
Ok so having reviewed the docs I am going with:
$contact = new XeroAPI\XeroPHP\Models\Accounting\Contact;
$contact->setName('Jane Doe');
$arr_contacts = [];
array_push($arr_contacts, $contact);
$contacts = new XeroAPI\XeroPHP\Models\Accounting\Contacts;
$contacts->setContacts($arr_contacts);
$apiResponse = $accountingApi->createContacts($xeroTenantId,$contacts);
//$message = 'New Contact Name: ' . $apiResponse->getContacts()[0]->getName();
$contactId = $apiResponse->getContacts()[0]->getContactId();

GoCardless API - List Subscriptions

I am using the GoCardless Documentation here to try list all subscriptions for a customer.
I have followed the instructions as you can see below, however nothing at all is displaying when I run this script - does anyone know what I may have done wrong?
require 'vendor/autoload.php';
$client = new \GoCardlessPro\Client(array(
'access_token' => 'XXXXXx',
'environment' => \GoCardlessPro\Environment::LIVE
));
$client->subscriptions()->list([
"params" => ["customer" => "CU000R3B8512345"]
]);
Calling a method on its own doesn’t do anything. It’ll execute the given method, but it’s not going to print anything to your browser screen on its own.
As RiggsFolly says (and is documented in GoCardless’s API documentation), calling $client->subscriptions()->list() will return a cursor-paginated response object. So you need to do something with this result. What that is, I don’t know as it’s your application’s business logic and only you know that.
<?php
use GoCardlessPro\Client;
use GoCardlessPro\Environment;
require '../vendor/autoload.php';
$client = new Client(array(
'access_token' => 'your-access-token-here',
'environment' => Environment::SANDBOX,
));
// Assign results to a $results variable
$results = $client->subscriptions()->list([
'params' => ['customer' => 'CU000R3B8512345'],
]);
foreach ($results->records as $record) {
// $record is a variable holding an individual subscription record
}
Pagination with Gocardless:
function AllCustomers($client)
{
$list = $client->customers()->list(['params'=>['limit'=>100]]);
$after = $list->after;
// DO THINGS
print_r($customers);
while ($after!="")
{
$customers = $list->records;
// DO THINGS
print_r($customers);
// NEXT
$list = $client->customers()->list(['params'=>['after'=>$after,'limit'=>100]]);
$after = $list->after;
}
}

Google Cloud Vision Client Library (PHP) Image Labels - Number of results?

I am using the upper mentioned library (Google Cloud Vision Client Library v1) in PHP to assign labels to images... so far so good. It all works, except it returns fewer results than on the google test page... as far as I understand it has to do with a "max_results" parameter which defaults to 10, but I am not able to find where/how to set it manually...
There was a similar question here on Python and there it was as simple as passing it as a parameter - I have tried many options to do this in PHP, but apparently I am doing something wrong...
Here is a link to the documentation : https://googleapis.github.io/google-cloud-php/#/docs/cloud-vision/v0.19.3/vision/v1/imageannotatorclient?method=labelDetection
I am guessing I have to pass it to the "optionalArgs" parameter... but not exactly sure how to do this...
Here is more or less what my code is:
require __DIR__ . '/vendor/autoload.php';
use Google\Cloud\Vision\V1\ImageAnnotatorClient;
$this->client = new ImageAnnotatorClient();
$response = $this->client->labelDetection(...THE IMAGE...);
$labels = $response->getLabelAnnotations();
if ($labels) {
foreach ($labels as $label) {
// do something with $label->getDescription()
}
}
Anyone got an idea how to get more results in the $labels array?
New Method
Since the other answer I provided seems to be deprecated, I am going to provide a sample that uses the setMaxResults method on the Feature object.
$imageAnnotatorClient = new ImageAnnotatorClient();
$gcsImageUri = 'some/image.jpg';
$source = new ImageSource();
$source->setGcsImageUri($gcsImageUri);
$image = new Image();
$image->setSource($source);
$type = Feature_Type::FACE_DETECTION;
$featuresElement = new Feature();
$featuresElement->setType($type);
$featuresElement->setMaxResults(100); // SET MAX RESULTS HERE
$features = [$featuresElement];
$requestsElement = new AnnotateImageRequest();
$requestsElement->setImage($image);
$requestsElement->setFeatures($features);
$requests = [$requestsElement];
$imageAnnotatorClient->batchAnnotateImages($requests);
Deprecated Method
The maxResults value gets specified in the Image constructor
An example of this code can be found in the source code for the Image object.
$imageResource = fopen(__DIR__ . '/assets/family-photo.jpg', 'r');
$image = new Image($imageResource, [
'FACE_DETECTION',
'LOGO_DETECTION'
], [
'maxResults' => [
'FACE_DETECTION' => 1
],
'imageContext' => [
....
]
]
]);
OK, so for anybody who still may need this here is a working example
use Google\Cloud\Vision\Image;
use Google\Cloud\Vision\VisionClient;
$imageResource = fopen(__DIR__ .'/'. $fileIMG, 'r');
$thePic = new Image($imageResource, [
'LABEL_DETECTION',
'LOGO_DETECTION',
'TEXT_DETECTION'
], [
'maxResults' => [
'LABEL_DETECTION' => 20,
'LOGO_DETECTION' => 20,
'TEXT_DETECTION' => 20
],
'imageContext' => []
]);
$vision = new VisionClient();
$result = $vision->annotate($thePic);
$finalLabels = array();
// do the same for $results->text(), $results->logos()
if($result->labels()){
foreach ($result->labels() as $key => $annonObj) {
$tmp = $annonObj->info();
$finalLabels[] = $tmp['description'];
}
}
But... as it says in the official documentation
Please note this client will be deprecated in our next release. In order
to receive the latest features and updates, please take
the time to familiarize yourself with {#see Google\Cloud\Vision\V1\ImageAnnotatorClient}.
So I still need a way to do this using the ImageAnnotatorClient class... any ideas anyone?

google analytics API implementation for tracking a specific user activities in php

I'm developing an e-commerce student project using php (laravel framework), I have found lots of tutorials about using google analytics, but very rarely about how to retrieve data to my web site using APIs. I have the following questions:
how to retrieve all data from google analytics to my web site using API.
how to retrieve all data of UserID view? because I want to get all data about a specific user to know what is he/she (browser, language, country, Os .... etc) to use it to personalize my web site.
please any idea or help will be appreciated, I'm run out of time, people help
below is my API code that I'm using :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class HomeController extends Controller
{
public function getAnalyticsSummary(Request $request){
$from_date = date("Y-m-d", strtotime($request->get('from_date',"7 days ago")));
$to_date = date("Y-m-d",strtotime($request->get('to_date',$request->get('from_date','today')))) ;
$gAData = $this->gASummary($from_date,$to_date) ;
return $gAData;
}
//to get the summary of google analytics.
private function gASummary($date_from,$date_to) {
$service_account_email = 'get-data-analytics#analytics-api-project-148820.iam.gserviceaccount.com';
// Create and configure a new client object.
$client = new \Google_Client();
$client->setApplicationName("any name");
$analytics = new \Google_Service_Analytics($client);
$cred = new \Google_Auth_AssertionCredentials(
$service_account_email,
array(\Google_Service_Analytics::ANALYTICS_READONLY),
"-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrzw1R6ca16zYQ\n3ozuTlNVbvNWDPODW6NVrnny08V4ycd/ugvTQeU3EKno6mJ8iXNJ/3GXOz52iaRd\nGRKbfrPomK7gt6+F7EHVdpTfbc/u7TIJi5NbYzXS4jIXhIJhB4bGjzsnTGTY/6pF\nFmW/wgX2Y6n31EWyz2q5MiZDC5lEPrmNk/gOgWOyRHCVNHtBcyFdA3+w2or6ix5l\nrKlCwUkkzKAPb4OSvfDMz8o+h6r433E4+6MMHE/mf53CkX1DbDZIlZbUntYLoh19\n0oxKCufjfFEKqzxgTZxIbG5rK7jdrOFLuQwnaoKkUT0HAQTGnGoYrABo9HGjlgwg\n7rHzh+OzAgMBAAECggEBAKcRq8E41Ft4w1V6JI9jqRON1aCa7X2R8e3SwZFJL2C6\nzn28+9zN2khGswLkUSsLOgn+FYZbPO1mAWfqhragafBH8N5ioJNZX9dk/XWbQjTz\ngNHZYUzf16oe/VjzKRmTiRKym3ImjnaZfwi5s+3ZjZS/67ssNy6fFgfK5XwS3lKG\nFzAZYgCMyJIz8Cz9mHAHKmQELH29xiPNDSSCuAbScptOdNJvBB9Vvpu5b+/LtdWI\naBYLuZgMOSNgJiQFBjj/+RY5yBi9pL5aHYGHJJZnWp1CbxRQIN+xe5jBbpHZ6PWH\nUjoCfMkM+IWBNjlKOWCC/APFpGuDqYMRuHfakPvWIXECgYEA4otETO0hR00SceKt\nGXYtIX6ZfdK6K87EPTFofMXAvcH7CWU3Tr2+3pI/DWUD1pPEEfsOZpaS5Ry37IrQ\nVmhkS2j5QxYJ+NfEvjc17rdJuuwJeCeELDFNxsTvhk7yK6y75F7BFQH/dIcc7MYg\nTL11B340TpPlJtulnxLTW7G3nFkCgYEAwiXfGnpmuPE1yyGdux4mY1Eyp6ZmxC0o\nb+DIBqRRlwY23m7pv3g7a0GAqLIFaW7c/1iVlyoDg24eZ8YtSehtR0B5LEiExBaY\n7UpkqHosvCqgCH94O/Mas+DUv9Sfqy82geEagB65Cf+uLr/ixNbKiK12G29O/V7M\nCar/=========================================================================================================================================================================================================================================x/NCNpGaW7zKvAoGAffG7PqgXYNtqQ3MavgLF\nKtQFMzT65kI5AfXPpyzgBDKr84lhvdUddvK/FZg/mIuoLRLSgnYPnAv3s5yhleZ5\n7LGyo5fXXH7XUm2nNt+XZoV1rt6y+WgZi103M+fuv3GXYBdbOonPHopRzw3uzLIA\n9ovyAV95jOu9ybk4YgQXm5I=\n-----END PRIVATE KEY-----\n"
);
$client->setAssertionCredentials($cred);
if($client->getAuth()->isAccessTokenExpired()) {
$client->getAuth()->refreshTokenWithAssertion($cred);
}
$optParams = [
'dimensions' => 'ga:date',
'sort'=>'-ga:date'
] ;
$results = $analytics->data_ga->get(
'ga:133119102',
$date_from,
$date_to,
/*'ga:sessions,ga:users,ga:pageviews,ga:bounceRate,ga:hits,ga:avgSessionDuration',*/
'ga:bounceRate',
$optParams
);
$rows = $results->getRows();
$rows_re_align = [] ;
foreach($rows as $key=>$row) {
foreach($row as $k=>$d) {
$rows_re_align[$k][$key] = $d ;
}
}
$optParams = array(
'dimensions' => 'rt:medium'
);
try {
$results1 = $analytics->data_realtime->get(
'ga:132964552',
'rt:activeUsers',
$optParams);
// Success.
} catch (apiServiceException $e) {
// Handle API service exceptions.
$error = $e->getMessage();
}
$active_users = $results1->totalsForAllResults ;
return view('myGoogle.getGoogle', [
'data'=> $rows_re_align ,
/* 'summary'=>$results->getTotalsForAllResults(),*/
/* 'active_users'=>$active_users['rt:activeUsers']*/
]) ;
}
}
User id is used for internal processing of users across multiple seasons. Example you have a user who uses your website and your moblie application this would technically be two sessions. However if you pass the User id you have for this user when they are logged into there account on your system. You are telling Google analytics this is the same person.
User Id is not something however that you can extract out of the API its used mainly for internal processing. Assuming that your user id is a non user specific value you can also set it as a custom dimension which you can then extract out using the API.

Batch request Google Calendar php API

I'm working on a google Calendar sync with my application.
I'm using the latest google-api-php-client
Now I want to update all my event, so i want to use the batch operation.
The example code of the php client api is:
$client = new Google_Client();
$plus = new Google_PlusService($client);
$client->setUseBatch(true);
$batch = new Google_BatchRequest();
$batch->add($plus->people->get(''), 'key1');
$batch->add($plus->people->get('me'), 'key2');
$result = $batch->execute();
So when I "translate" it to the calendar API, I become the following code:
$client = new Google_Client();
$this->service = new Google_CalendarService($client);
$client->setUseBatch(true);
// Make new batch and fill it with 2 events
$batch = new Google_BatchRequest();
$gEvent1 = new Google_event();
$gEvent1->setSummary("Event 1");
$gEvent2 = new Google_event();
$gEvent2->setSummary("Event 2");
$batch->add( $this->service->events->insert('primary', $gEvent1));
$batch->add( $this->service->events->insert('primary', $gEvent2));
$result = $batch->execute();
But when I run this code, I get this error:
Catchable fatal error: Argument 1 passed to Google_BatchRequest::add()
must be an instance of Google_HttpRequest, instance of Google_Event given
And I do not think that "$plus->people->get('')" is a HttpRequest.
Does anybody know what I do wrong, or what method / object I should use to add in the batch?
Or what the correct use of the batch operation for the calendar is?
Thanks in advance!
I had the same problem while working with inserts to the MirrorService api, specifically with timeline items. What is happening is that the the Google_ServiceRequest object is seeing that you've set the useBatch flag on the client and is actually returning returning Google_HttpRequest object before executing the call to Google but the insert statement in the calendar service doesn't properly handle it as such and ends up returning the calendar event object instead.
It also looks like your params to batch->add are backwards. Should be:
$batch->add( $this->service->events->insert($gEvent1, 'primary'));
Here is my modification to the insert method (you'll need to do this in the calendar service with the proper object input to the method). Just a few lines to make it check what class is coming back from the ServiceRequest class:
public function insert(google_TimelineItem $postBody, $optParams = array()) {
$params = array('postBody' => $postBody);
$params = array_merge($params, $optParams);
$data = $this->__call('insert', array($params));
if ($this->useObjects()) {
if(get_class($data) == 'Google_HttpRequest'){
return $data;
}else{
return new google_TimelineItem($data);
}
} else {
return $data;
}
}
you can use this code to insert events in batch:
public function addEventInBatch($accessToken, $calendarId, array $events)
{
$client = new Google_Client();
$client->setAccessToken($accessToken);
$client->setUseBatch(true);
$service = new Google_Service_Calendar($client);
$batch = $service->createBatch();
collect($events)->each(fn ($event) => $batch->add($service->events->insert($calendarId, $event)));
return $batch->execute();
}

Categories