Quickstart

This pages demonstrate how to make API calls to QuickBooks Online through QuickBooks V3 PHP SDK. It is assuming developers have obtained OAuth 1.0 or OAuth 2.0 tokens, and have configured their dataService correctly. If not, please refer to the Authorization part and Configuration part

Create new resources (POST)

QuickBooks Online API has a list API endpoints that support creating new entities. For example, you can create a new Invoice, a new SalesReceipt, or a new Item through QuickBooks Online API. For a list of API endpoints that support create operation, please refer to our API Reference: https://developer.intuit.com/docs/api/accounting

We use Invoice as our example here (Other API entities have very similar data structure compared with Invoice, developers should have no difficulty replacing Invoice with other entities by using our API reference page).

Constructing Entities

Last week, we provide a sewing service to our Customer Alex. We need to charge him $150 dollars. How can we record this in QuickBooks Online?

When creating an Invoice, it will need to have at least a target Customer, a documentation Number that identifies the Invoice, and information for goods/services that we provide.

The first thing we need to figure out is how to tell QuickBooks Online that this Invoice is for our Customer Alex.

In QuickBooks Online, all Name list resource (like Customer, Employee, Vendor, Item, etc, they are grouped under Name list resources in the API Reference page) will be referred by their Id in QuickBooks Online. For our example, “Alex” has Id=1.

Refer back to Invoice documentation page <https://developer.intuit.com/docs/api/accounting/invoice>, we found CustomerRef is exactly what we are looking for:

_images/example.jpg

Document for CustomerRef

we follow the instruction, use the same name CustomerRef from our docs as our key, and passing 1 to its value attribute. Record them in an array format:

"CustomerRef" => [
      "value" => "1",
      "name" => "Alex"
]

and this will be the part specifying a Customer for an Invoice.

Note

  1. The key, CustomerRef we use in our array has the SAME name displayed on our documentation page. This is not by coincidence. For all the keys we use in the QuickBooks V3 SDK, they all have the same name as our docs.
  2. The field name is not required here, only value is.
  3. Refer to the Query and access resources (GET) section for how to find the Id of a Name list resources

After the Customer is provided, QuickBooks Online will need information about the goods/services we provide.

QuickBooks Online identifies the goods/services for an entity by extracting information from what it called Line Item. An Line item represents a transaction. QuickBooks Online has five types of Line Item: SalesItemLine, GroupItemLine , DescriptionOnly, DiscountLine, and SubtotalLine. Each Line type has a specific usage. For our Invoice, we will use SalesItemLine to represent the sewing service we provide to Alex.

To find out what information we need to provide in the SalesItemLine, we refer back to the API reference page again: <https://developer.intuit.com/docs/api/accounting/invoice>

_images/example2.jpg

Document for SalesItemLine

The fields that are labelled as required is what we need to provide. We found that we need to specify the Amount we charge for Alex, which is $150 dollars; the DetailType for the LineItem, which is SalesItemLineDetail. Under the SalesItemLineDetail, we also need to specify the Item we sell as well (The docs said it is optional, however, for Invoice creation with Service, it is required).

In QuickBooks Online, an Item is a thing that your company buys, sells, or re-sells, such as products and services. An item is shown as a line on an invoice or other sales form. Since Item is located under Name list resources, it will be a Reference type. We find that QuickBooks Online has a default Item called Services, which has Id=1. We use this Services item to represent our sewing service. Again, we store these information in an associate array, adding description to make it clear.

"Line" => [
    [
      "Description" => "Sewing Service for Alex",
      "Amount" => 150.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 1,
          "name" => "Services"
        ]
      ]
    ]
]

Searching the API reference page for documentation number, we have:

"DocNumber" => "101",

Putting everything together for our Invoice, we have the following associate array:

[
  "DocNumber" => "101",
  "Line" => [
  [
    "Description" => "Sewing Service for Alex",
    "Amount" => 150.00,
    "DetailType" => "SalesItemLineDetail",
    "SalesItemLineDetail" => [
      "ItemRef" => [
        "value" => 1,
        "name" => "Services"
      ]
    ]
  ]],
  "CustomerRef" => [
     "value" => "1",
     "name" => "Alex"
  ]
]

This associate array contains all the information we want to store in the Invoice. To actually use this array to construct an Invoice Object with this information, we will import our Invoice Facade class:

use QuickBooksOnline\API\Facades\Invoice;

and pass the array to the create() static method:

$invoiceToCreate = Invoice::create([
  "DocNumber" => "101",
  "Line" => [
    [
      "Description" => "Sewing Service for Alex",
      "Amount" => 150.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 1,
          "name" => "Services"
        ]
      ]
    ]
  ],
  "CustomerRef" => [
      "value" => "1",
      "name" => "Alex"
  ]
]);

and the $invoiceToCreate object will our final object sent to QuickBooks Online.

Sending Requests

After the object is constructed, sending it to QuickBooks Online will be very straightforward.

We will use the $dataService->Add() method to complete our task:

$resultObj = $dataService->Add($invoiceToCreate);

Handling Response

If everything works as expected, the $resultObj will be a copy of the Invoice we just created, you can directly access all its public fields. However, if there is any error occurred, we need to use $error = $dataService->getLastError(); to track for the error:

$error = $dataService->getLastError();
if ($error) {
    echo "The Status code is: " . $error->getHttpStatusCode() . "\n";
    echo "The Helper message is: " . $error->getOAuthHelperError() . "\n";
    echo "The Response message is: " . $error->getResponseBody() . "\n";
}else {
    echo "Created Id={$resultObj->Id}. Reconstructed response body:\n\n";
    $xmlBody = XmlObjectSerializer::getPostXmlFromArbitraryEntity($resultingObj, $urlResource);
    echo $xmlBody . "\n";
}

Example and Result

Here is the Complete example and what the result looks like in QuickBooks Online:

use QuickBooksOnline\API\Core\ServiceContext;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Core\Http\Serialization\XmlObjectSerializer;
use QuickBooksOnline\API\Facades\Invoice;
// Prep Data Services
$dataService = DataService::Configure(array(
    'auth_mode' => 'oauth1',
    'consumerKey' => "qyprdUSoVpIHrtBp0eDMTHGz8UXuSz",
    'consumerSecret' => "TKKBfdlU1I1GEqB9P3AZlybdC8YxW5qFSbuShkG7",
    'accessTokenKey' => "qyprdxccscoNl7KRbUJoaJQIhUvyXRzD9tNOlXn4DhRDoj4g",
    'accessTokenSecret' => "JqkHSBKzNHbqjMq0Njbcq8fjgJSpfjMvqHVWnDOW",
    'QBORealmID' => "193514464689044",
    'baseUrl' => "Development"
));
$dataService->setLogLocation("/location/to/newFolderForLog");
$dataService->throwExceptionOnError(true);
//Add a new Invoice
$invoiceToCreate = Invoice::create([
  "DocNumber" => "101",
  "Line" => [
    [
      "Description" => "Sewing Service for Alex",
      "Amount" => 150.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 1,
          "name" => "Services"
        ]
      ]
    ]
  ],
  "CustomerRef" => [
      "value" => "1",
      "name" => "Alex"
  ]
]);

$resultObj = $dataService->Add($invoiceToCreate);
$error = $dataService->getLastError();
if ($error) {
    echo "The Status code is: " . $error->getHttpStatusCode() . "\n";
    echo "The Helper message is: " . $error->getOAuthHelperError() . "\n";
    echo "The Response message is: " . $error->getResponseBody() . "\n";
}else {
    echo "Created Id={$resultObj->Id}. Reconstructed response body:\n\n";
    $xmlBody = XmlObjectSerializer::getPostXmlFromArbitraryEntity($resultingObj, $urlResource);
    echo $xmlBody . "\n";
}
_images/example3.jpg

Result shown on QuickBooks Online

Constructing Entities with Tax

Many QuickBooks Online users need to create entities with Tax. It is achieved on V3 PHP SDK by specifying the value on SalesItemLineDetail.TaxCodeRef when creating entities. Below is an example with creating Invoice with Tax on a Canadian QuickBooks Online Company:

use QuickBooksOnline\API\Core\ServiceContext;
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Core\Http\Serialization\XmlObjectSerializer;
use QuickBooksOnline\API\Facades\Invoice;
// Prep Data Services
$dataService = DataService::Configure(array(
  'auth_mode' => 'oauth2',
  'ClientID' => "Q094uKi203zS1V1ZxH6dyff236cHa2CQhFiXs3JZFjRq1Gmu9f",
  'ClientSecret' => "NacL2Q92jmFEKjycARHEw8qrGGD1fv89OMxbjjbq",
  'accessTokenKey' =>
  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  'refreshTokenKey' => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  'QBORealmID' => "193514708967874",
  'baseUrl' => "Production"
));
$dataService->setLogLocation("/location/to/newFolderForLog");
$dataService->throwExceptionOnError(true);
//Add a new Invoice
$invoiceToCreate = Invoice::create([
  "DocNumber" => "101",
  "Line" => [
    [
      "Description" => "Sewing Service for Jimmy in Canada",
      "Amount" => 150.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 1,
          "name" => "Services"
        ],
        "TaxCodeRef" => [
          "value" => 2
        ]
      ]
    ]
  ],
  "CustomerRef" => [
      "value" => "2",
      "name" => "Jimmy"
  ]
]);

$resultObj = $dataService->Add($invoiceToCreate);
$error = $dataService->getLastError();
if ($error) {
    echo "The Status code is: " . $error->getHttpStatusCode() . "\n";
    echo "The Helper message is: " . $error->getOAuthHelperError() . "\n";
    echo "The Response message is: " . $error->getResponseBody() . "\n";
}else {
    echo "Created Id={$resultObj->Id}. Reconstructed response body:\n\n";
    $xmlBody = XmlObjectSerializer::getPostXmlFromArbitraryEntity($resultingObj, $urlResource);
    echo $xmlBody . "\n";
}

Note

US Company do not have a numeric value for TaxCode. It is either TAX or NON.

Notice we didn’t provide TxnTaxDetail when creating sales transactions, however, it is returned as part of READ response. In QuickBooks Online, it automatically calculates tax and returns the amount in the TxnTaxDetail.TotalTax attribute of the transaction based on the TaxCode assigned to each line.

For more information, please refer to this docs: https://developer.intuit.com/docs/00_quickbooks_online/2_build/60_tutorials/0060_manage_sales_tax_for_non-us_locales

Query and access resources (GET)

Most API requests sent to QuickBooks Online are either Query or GetById. For example, creating Invoice would require retrieving the Id stored for a specific Customer in QuickBooks Online. QuickBooks V3 PHP SDK provides easier way to perform these operations.

Get by Entity Id

If you already possess the object contains the Id, you can use the FindById() method directly:

//The $invoice object which has an Id
$dataService->FindById($invoice);

or if you know the Id and which API endpoint it belongs, you can use a variation of the FindById() method by passing the string name of the API endpoint and the Id:

//We are looking for Invoice with id=1. You can use
//Bill, Customer, Purchase, etc
$dataService->FindById("Invoice", 1);

Depends if the first parameter is a string or an object, the method will perform differently.

Get Company Info or Preference

For getting Company Info, please use getCompanyInfo() directly from DataService:

$CompanyInfo = $dataService->getCompanyInfo();

Similarly, for getting CompanyPreference, you can use getCompanyPreferences():

$CompanyPrefs = $dataService->getCompanyPreferences();

The getCompanyInfo() and getCompanyPreferences() are also regarded as sample methods to test OAuth 1&2 settings.

Query resources

The query operation is the method for creating a guided query against an entity. The select statement in the request enables you to specify the selection criteria, entity properties, sort order, and pagination. To perform a query in QuickBooks Online, developers will use $dataServices->Query() method. It is similar to a pared down SQL query select statement with constraints in place to ensure the request doesn’t overload server-side resources. For example, to find all invoices from a QuickBooks Online Company, the query method would look like this:

$allInvoices = $dataServices->Query("SELECT * FROM Invoice");

In QuickBooks Online, not all SQL statement is supported. The following are query operation limitations:

  • The response set always returns all properties for each object. That is, projections are not supported.
  • Only those properties with values are returned in the response set.
  • Wild card character support with LIKE clauses is limited to “%” (wildcard that substitutes for 0 or more characters).
  • The OR operation is not supported in WHERE clauses.
  • The GROUP BY clause is not supported.
  • The JOIN clause is not supported.

When using QuickBooks PHP V3 SDK to perform a query, it is suggested to construct the SQL statement OUTSIDE OF the Query() method, as developers may need to use backslash to escape special characters like apostrophe. For query operation, the returned result would always be array.

Maximum number of entities in a response

The maximum number of entities that can be returned in a response is 1000. If the result size is not specified, the default number is 100. If a query returns many entities, fetch the entities in chunks, as described in Pagination. To determine the number of entities that a particular query returns, probe by using the COUNT keyword in the query. See Count for details.

Count

To find out how many object will be returned by a query, specify the COUNT keyword. The number of entities is returned in the totalCount attribute of the <QueryResponse> element. The QuickBooks Online data objects, such as Customer, are not in the response.

The following query returns the total number of customers that belong to the QuickBooks company:

$statement = "SELECT COUNT" . "(" . "*" . ")" . "FROM CUSTOMER";
$numberOfCustomers = $dataServices->Query($statement);

Filters

The filter in the WHERE clause determines which entities are returned by a query. The filter criteria is based on the value of a property. For example, the following query returns all Invoice entities with a TotalAmt property greater than 1000.0:

$invoicesGreaterThenThousand = $dataServices->Query("SELECT * FROM Invoice WHERE TotalAmt > '1000.0'");

Note that not all properties support filters. Refer back to the API reference page (https://developer.intuit.com/docs/api/accounting/), only fields that contain keywords “filterable” are filterable.

Note

  1. For QuickBooks Online, a SQL comparison value is required to use SINGLE QUOTATION MARKS. SQL statements that use double quotes or only an integer value for comparison WILL NOT WORK:
//You will get a 400 from QBO for error parsing string
$theInvoice = $dataServices->Query("select * from Invoice where docNumber=1038");
  1. For Query ReferenceType (ending with keyword Ref), DO NOT USE TypeRef.value. Just use TypeRef. For example, to query an invoice from customer ID 59, you will use:
$theInvoice = $dataServices->Query("SELECT * FROM Invoice WHERE CustomerRef='58'");

Sorting

To sort the results of a query, include the ORDERBY clause, specifying a property as the sort field. Similar to filters, only fields that contain keywords “sortable” in the API reference page are sortable.

For example, the following query sorts Customer objects by the FamilyName property:

$customers = $dataServices->Query("SELECT * FROM Customer ORDERBY FamilyName");

Please refer to our API Data Query page for more information: https://developer.intuit.com/docs/00_quickbooks_online/2_build/20_explore_the_quickbooks_online_api/50_data_queries

Update resources (PUT)

Update resources

Update resources often require a GET operation first. Similar to Create new resources (POST), we use an associate array to pass our updated data.

For example, we want to change the Invoice #101 we just created (see example created at Create new resources (POST)) for Alex with an additional line item. Besides the sewing service, we also want to give Alex a discount, 10 dollars.

After refer back to the API reference page, we add our discount Line with the sewing service(If you don’t have a Service called Discount, you will need to create it first, refer to Item API reference Page: https://developer.intuit.com/docs/api/accounting/item for instruction):

"Line" => [
    [
      "Description" => "Sewing Service for Alex",
      "Amount" => 150.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 1,
          "name" => "Services"
        ]
      ]
    ],
    [
      "Description" => "Discount for Alex",
      "Amount" => -10.00,
      "DetailType" => "SalesItemLineDetail",
      "SalesItemLineDetail" => [
        "ItemRef" => [
          "value" => 21,
          "name" => "Discount"
        ]
      ]
    ]
  ]

This associate array will be our updated Content.

Next step is finding our target invoice. We already know that the Invoice has DocNumber = 101, so we use query operation to find the Invoice:

$targetInvoiceArray = $dataService->Query("select * from Invoice where DocNumber='101'");

The $targetInvoiceArray should only contain one element, and it will be our target to update:

if(!empty($targetInvoiceArray) && sizeof($targetInvoiceArray) == 1){
    $theInvoice = current($targetInvoiceArray);
}

At this point, we have both the target Invoice, and the updated content, we can use Invoice::update() method to update the Invoice with our new line item, and using DataService->Update() method to send the request:

$updatedInvoice = Invoice::update($theInvoice, [
    "sparse" => true,
    "Line" => [
      [
        "Description" => "Sewing Service for Alex",
        "Amount" => 150.00,
        "DetailType" => "SalesItemLineDetail",
        "SalesItemLineDetail" => [
          "ItemRef" => [
            "value" => 1,
            "name" => "Services"
          ]
        ]
      ],
      [
        "Description" => "Discount for Alex",
        "Amount" => -10.00,
        "DetailType" => "SalesItemLineDetail",
        "SalesItemLineDetail" => [
          "ItemRef" => [
            "value" => 21,
            "name" => "Discount"
          ]
        ]
      ]
     ]
]);
$updatedResult = dataService->Update($updatedInvoice);

and our update operation is complete.

For Line item update, QuickBooks Online will replace current Line with the updated Line, therefore, it requires us to provide the FULL Line details each time when we update Line item.

In QuickBooks Online PHP V3 SDK, you can use helper class Line to create the additional Line item, and merge this Line with existed Line from target object:

$LineObj = Line::create([
    "Description" => "Discount for Alex",
    "Amount" => -10.00,
    "DetailType" => "SalesItemLineDetail",
    "SalesItemLineDetail" => [
      "ItemRef" => [
        "value" => 21,
        "name" => "Discount"
      ]
    ]
]);
//Remove last element of Line Item.
$lineArray = array_pop($theInvoice->Line);
$lineArray[] = $LineObj;
$updatedInvoice = Invoice::update($theInvoice, [
    "sparse" => true,
    "Line" => $lineArray
]);
$updatedResult = dataService->Update($updatedInvoice);

The example remove the last element of Line Item, because the last Line of a read request would always be a SubTotalLineDetail. We can rely on QuickBooks Online to re-calculate this line for us.

Update resources with Tax

QuickBooks Online does allow developers to update Tax on an existed entity. In order to update the tax used for an entity, the developer needs to do the following for the company(this rule applied for non-us Company):

  1. Override the TxnTaxDetail.TotalTax with the new total tax amount.
  2. Override the TxnTaxDetail.TaxLine.TaxLineDetail.TaxRateRef. You must send all TaxRateRefs of a TaxCode even if you are modifying just one of them.
  3. Override the TxnTaxDetail.TaxLine.Amount
  4. Override the TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent
  5. Override the TaxCodeRef.value on LineDetail

Pretty much, if a developer wants to update TaxCode used for a LineItem, he/she needs to update everything. QuickBooks Online will not be able to calculate tax anymore, it is the developer’s job to calculate the tax correctly and provide it to QuickBooks Online.

For example, we have a invoice on a Canada Company:

_images/example4.jpg

Document for a Canadian Invoice with Tax

When we read this Invoice from API, here is what we have:

{
  "Deposit": 0,
  "AllowIPNPayment": false,
  "AllowOnlinePayment": false,
  "AllowOnlineCreditCardPayment": false,
  "AllowOnlineACHPayment": false,
  "domain": "QBO",
  "sparse": false,
  "Id": "1",
  "SyncToken": "0",
  "MetaData": {
    "CreateTime": "2018-03-13T16:33:42-07:00",
    "LastUpdatedTime": "2018-03-13T16:33:42-07:00"
  },
  "CustomField": [],
  "DocNumber": "1001",
  "TxnDate": "2018-03-13",
  "CurrencyRef": {
    "value": "CAD",
    "name": "Canadian Dollar"
  },
  "LinkedTxn": [],
  "Line": [
  {
    "Id": "1",
    "LineNum": 1,
    "Description": "Jimmy Service hours",
    "Amount": 200.0,
    "DetailType": "SalesItemLineDetail",
    "SalesItemLineDetail": {
      "ItemRef": {
        "value": "2",
        "name": "Hours"
      },
      "UnitPrice": 200,
      "Qty": 1,
      "TaxCodeRef": {
        "value": "6"
      }
    }
   },
   {
    "Id": "2",
    "LineNum": 2,
    "Description": "Jimmy extra hours",
    "Amount": 100.0,
    "DetailType": "SalesItemLineDetail",
    "SalesItemLineDetail": {
      "ItemRef": {
        "value": "2",
        "name": "Hours"
      },
      "UnitPrice": 100,
      "Qty": 1,
      "TaxCodeRef": {
        "value": "2"
      }
    }
   },
   {
    "Amount": 300.0,
    "DetailType": "SubTotalLineDetail",
    "SubTotalLineDetail": {}
   }
 ],
 "TxnTaxDetail": {
  "TotalTax": 30.0,
  "TaxLine": [
    {
      "Amount": 30.0,
      "DetailType": "TaxLineDetail",
      "TaxLineDetail": {
        "TaxRateRef": {
          "value": "12"
        },
        "PercentBased": true,
        "TaxPercent": 15,
        "NetAmountTaxable": 200.0
      }
    },
    {
      "Amount": 0,
      "DetailType": "TaxLineDetail",
      "TaxLineDetail": {
        "TaxRateRef": {
          "value": "2"
        },
        "PercentBased": true,
        "TaxPercent": 0,
        "NetAmountTaxable": 100.0
      }
    }
   ]
 },
 "CustomerRef": {
  "value": "1",
  "name": "Jimmy"
 },
 "SalesTermRef": {
  "value": "3"
 },
 "DueDate": "2018-04-12",
 "GlobalTaxCalculation": "TaxExcluded",
 "TotalAmt": 330.0,
 "PrintStatus": "NotSet",
 "EmailStatus": "NotSet",
 "Balance": 330.0
}

from the Invoice, we find out we actually provided wrong TaxCode for the second line, Jimmy extra hours, it should use the same TaxCode as first line, 15%, not 0%.

We follow the instruction to update the TaxCode:

  1. TxnTaxDetail.TotalTax should be 45 (the extra hours now have 15 tax on it, 15 + 30 = 45)
  2. TxnTaxDetail.TaxLine.TaxLineDetail.TaxRateRef now should be using 12 now, which is the same as first lineItem.
  3. TxnTaxDetail.TaxLine.Amount should be 15(100 * 15% = 15).
  4. TxnTaxDetail.TaxLine.TaxLineDetail.TaxPercent should be same 15% as well.
  5. The TaxCode used on LineItem now should be 6 instead of 2.

After we changed the value, we found out that there is no longer two TaxLine, we should only have one TaxLine. Therefore, we delete the second TaxLine, only left the first one. Here is what our updating request looks like in JSON:

{
  "Deposit": 0,
  "AllowIPNPayment": false,
  "AllowOnlinePayment": false,
  "AllowOnlineCreditCardPayment": false,
  "AllowOnlineACHPayment": false,
  "domain": "QBO",
  "sparse": false,
  "Id": "1",
  "SyncToken": "4",
  "MetaData": {
    "CreateTime": "2018-03-13T16:33:42-07:00",
    "LastUpdatedTime": "2018-03-13T17:05:39-07:00"
  },
  "CustomField": [],
  "DocNumber": "1001",
  "TxnDate": "2018-03-13",
  "CurrencyRef": {
    "value": "CAD",
    "name": "Canadian Dollar"
  },
  "LinkedTxn": [],
  "Line": [
    {
      "Id": "1",
      "LineNum": 1,
      "Description": "Jimmy Service hours",
      "Amount": 200.0,
      "DetailType": "SalesItemLineDetail",
      "SalesItemLineDetail": {
        "ItemRef": {
          "value": "2",
          "name": "Hours"
        },
        "UnitPrice": 200,
        "Qty": 1,
        "TaxCodeRef": {
          "value": "6"
        }
      }
    },
    {
      "Id": "2",
      "LineNum": 2,
      "Description": "Jimmy extra hours",
      "Amount": 100.0,
      "DetailType": "SalesItemLineDetail",
      "SalesItemLineDetail": {
        "ItemRef": {
          "value": "2",
          "name": "Hours"
        },
        "UnitPrice": 100,
        "Qty": 1,
        "TaxCodeRef": {
          "value": "6"
        }
      }
    },
    {
      "Amount": 300.0,
      "DetailType": "SubTotalLineDetail",
      "SubTotalLineDetail": {}
    }
  ],
  "TxnTaxDetail": {
    "TotalTax": 45.0,
    "TaxLine": [
      {
        "Amount": 45.0,
        "DetailType": "TaxLineDetail",
        "TaxLineDetail": {
          "TaxRateRef": {
            "value": "12"
          },
          "PercentBased": true,
          "TaxPercent": 15,
          "NetAmountTaxable": 200.0
        }
      }
    ]
  },
  "CustomerRef": {
    "value": "1",
    "name": "Jimmy"
  },
  "SalesTermRef": {
    "value": "3"
  },
  "DueDate": "2018-04-12",
  "GlobalTaxCalculation": "TaxExcluded",
  "TotalAmt": 330.0,
  "PrintStatus": "NotSet",
  "EmailStatus": "NotSet",
  "Balance": 330.0
}

We put this body in our code, and make an update call. Our Invoice has been updated successfully.

_images/example5.jpg

Document for a Canadian Invoice after updating the Tax

Due to the complexity of updating TaxCode, Some developers prefer not updating the TaxCode used, just override the Tax amount used. In this case, they only need to override the TxnTaxDetail.TotalTax and TxnTaxDetail.TaxLine.Amount.

For example, we just want to update the TaxAmount to 45 instead of updating the TaxCode we use, we can provide this JSON body:

{
  "sparse" : "true",
  "Id": "1",
  "SyncToken": "9",
  "TxnTaxDetail": {
    "TotalTax": 45.0,
    "TaxLine": [
    {
      "Amount": 30.0,
      "DetailType": "TaxLineDetail",
      "TaxLineDetail": {
        "TaxRateRef": {
          "value": "12"
        },
        "PercentBased": true,
        "TaxPercent": 15,
        "NetAmountTaxable": 200.0
      }
    },
    {
      "Amount": 15,
      "DetailType": "TaxLineDetail",
      "TaxLineDetail": {
        "TaxRateRef": {
          "value": "2"
        },
        "PercentBased": true,
        "TaxPercent": 0,
        "NetAmountTaxable": 100.0
        }
     }
    ]
  }
}

and the UI will have the correct TotalTaxAmount, with wrong taxCode.

_images/example6.jpg

Document for a Canadian Invoice after overriding the Tax

Sparse Update & Full Update

Be careful when you use the update method. The example we present all use sparse => true when update the invoice. QuickBooks Online provides two updates: full update and sparse update. The sparse update operation provides the ability to update a subset of attributes for a given object; only those specified in the request are updated. Missing attributes are left untouched. This is in contrast to the full update operation, where elements missing from the request are cleared.

Considerations for using sparse updates include:

  • Prevent unintended overwrites: A client application often does not use all the fields of an entity, so when it sends a full update request with only fields they use, it results in an erroneous blanking out of fields that were not sent.
  • Reduce request payload: Always desired, but is more relevant when the client application is mobile because of lower speeds, spotty connections, and the fact that mobile users are sensitive to amount of the data usage in each billing cycle.
  • Facilitate future field additions: New fields can be added to an entity without past versions of production applications clearing all other existing fields inadvertently, as would happen with a full update operation.

Delete resources(DELETE)

There are three ways to “delete” an entity in QuickBooks Online: DELETE, VOID, and Inactivate. Please refer to our API reference docs: https://developer.intuit.com/docs/api/ for more details.

To Delete an entity, you need to provide the Id and SyncToken of that entity. If we want to delete the Invoice that has Id=1, here is how we do it:

$invoice = Invoice::create([
    "Id" => "1",
    "SyncToken" => "0"
]);
$currentResultObj = $dataService->Delete($invoice);

Developers can also GET the entity through a READ or QUERY operation, then passing the whole object to Delete method. (For void operation, simply replace $dataService->Delete() with $dataService->Void())

For those entities only support Inactivate, Delete or Void will not work. In QuickBooks Online, Inactivate is not considered as Delete operation, it is considered as Update. It is achieved by setting the Active attribute to false in an object update request. Please refer to Update resources part for more details.

Accessing Entity Fields Directly

So far, all the REST examples we present show how to change an object through an associate array. It is the suggested way to create/update an entity resource.

However, developers are welcomed to use objects fields directly. You can also create an invoice in this way:

// Add an invoice
$invoice = new IPPInvoice();
$invoice->Deposit       = 0;
$invoice->domain        =  "QBO";
$invoice->AutoDocNumber = true;
$invoice->TxnDate = date('Y-m-d', time());
$invoice->CustomerRef   = "66";
$invoice->PrivateNote   = "SomeNote";
$invoice->TxnStatus     = "Payable";

$line = new IPPLine();
$line->Id = "0";
$line->LineNum          = "1";
$line->Description      = "test";
$line->Amount           = 1000;
$line->DetailType = "DescriptionOnly";

$sub_line = new IPPLine();
$sub_line->Id           = "0";
$sub_line->LineNum      = "2";
$sub_line->Description  = "Sub Total";
$sub_line->Amount       = 2000;
$sub_line->DetailType   = "SubtotalLineDetail";


$invoice->Line          = array($line, $sub_line);
$invoice->RemitToRef    = "66";
$invoice->TotalAmt      = 1000;
$invoice->FinanceCharge = 'false';

// Add a invoice
$resultingInvoiceObj = $dataService->Add($invoice);

The type of value will need to match type displayed on our Entity Reference page: https://developer-static.intuit.com/SDKDocs/QBV3Doc/IPPPHPDevKitV3/entities/index.html.

For example, if the Id type is String, then $sub_line->Id= "0"; will work but $sub_line->Id= 0; will not.

Below is a list of entities that support create/update operation through static methods in the SDK:

  • Account
  • Bill
  • BillPayment
  • CompanyCurrency
  • CreditMemo
  • Customer
  • Class
  • Department
  • Deposit
  • Employee
  • Estimate
  • Line — For excessive usage of Line objects, we create easy way of contructing Line items as well. However, Line is not an API endpoint entity
  • Invoice
  • Item
  • JournalEntry
  • Payment
  • Purchase
  • PurchaseOrder
  • RefundReceipt
  • SalesReceipt
  • TimeActivity
  • Transfer
  • VendorCredit
  • Vendor
  • TaxService and TaxRate (for Creation Only)

Other Supported Operations

Below is a list of operations that currently are supported by the SDK.

Sending E-mail

For those entities that supports send operation, you can use dataService->SendEmail() method to send an E-mail to user/users:

$invoice = $dataService->FindById("invoice", 1);
$result = $dataService->SendEmail($invoice, "abc@xyz.com");

Batch Request

The SDK also support batch requests.

Developers will need to create a $batch object first, then add the operation in the batch request:

$batch = $dataService->CreateNewBatch();
$batch->AddQuery("select * from Customer startPosition 0 maxResults 20", "queryCustomer", "uniqueQuery");
$batch->AddEntity($invoice, "uniqueIdentifier", "create");
$batch->Execute();

To add a query to the batch request, use AddQuery() method.

To add an operation to the batch request, AddEntity() method will be used.

Both method will have its second parameter as a string to uniquely identify the operation.

The AddEntity() method will have its third parameter as Operation Name, it is either create, update, or delete.

The result of batch request will be inside its intuitBatchItemResponses, use uniqueIdentifier to find the specific batch response:

$batchItemResponse = $batch->intuitBatchItemResponses["uniqueIdentifier"];

Change Data Capture(CDC)

The change data capture (cdc) operation returns a list of objects that have changed since a specified time. This operation is for an app that periodically polls data services in order to refresh its local copy of object data. The V3 PHP SDK supports this function as well. For example, if I want to capture all the change for Invoice, Item and Bill from 2017803-07T12:28:32, I will use below code:

<?php
require 'vendor/autoload.php';

use QuickBooksOnline\API\Core\OAuth\OAuth2\OAuth2LoginHelper;
use QuickBooksOnline\API\DataService\DataService;

// Prep Data Services
$dataService = DataService::Configure(array(
  'auth_mode' => 'oauth2',
  'ClientID' => "XXXXXXXXXXXXXXXXXXXXXXX",
  'ClientSecret' => "XXXXXXXXXXXXXXXXXXXXXXX",
  'accessTokenKey' =>
  'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  'refreshTokenKey' => "XXXXXXXXXXXXXXXXXXXXXXX",
  'QBORealmID' => "123456789123456",
  'baseUrl' => "Production"
));


// Use CDC APIs
$entityList = array('invoice','item', 'bill');

$cdcResponse = $dataService->CDC($entityList, "2017-03-07T12:28:32-08:00");
$error = $dataService->getLastError();
if ($error) {
  echo "The Status code is: " . $error->getHttpStatusCode() . "\n";
  echo "The Helper message is: " . $error->getOAuthHelperError() . "\n";
  echo "The Response message is: " . $error->getResponseBody() . "\n";
  exit();
}

if ($cdcResponse->entities) {
  foreach ($cdcResponse->entities as $entityName => $entityArray) {
      echo "CDC Says " . count($entityArray) . " Updated Entities of Type = {$entityName}\n";
  }
}
?>

A few points to remember when using the CDC function on V3 SDK:

  1. If an entity has no change, the SDK will return NULL for that entity. For example, if no invoice has been changed, then $cdcResponse->entities['invoice'] will be NULL
  2. The response does not distinguish Create vs Delete. Developers must query the correspodning entity again with the Id field returned from response.

Generating Reports

In V3 PHP SDK, the report service is separate from data service. To use the Report Service, developers will need to create the ReportService object first, then set the parameters on the report service:

<?php
require 'vendor/autoload.php';

use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\ReportService\ReportService;
use QuickBooksOnline\API\ReportService\ReportName;
// Prep Data Services
$dataService = DataService::Configure(array(
    'auth_mode' => 'oauth2',
    'ClientID' => "Q094uKi203zS1V1ZxH6dyff236cHa2CQhFiXs3JZFjRq1Gmu9f",
    'ClientSecret' => "NacL2Q92jmFEKjycARHEw8qrGGD1fv89OMxbjjbq",
    'accessTokenKey' =>
    'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    'refreshTokenKey' => "XXXXXXXXXXXXXXXXXXXXXXX",
    'QBORealmID' => "123456789123456",
    'baseUrl' => "Production"
  ));

  $serviceContext = $dataService->getServiceContext();

  // Prep Data Services
  $reportService = new ReportService($serviceContext);
  if (!$reportService) {
    exit("Problem while initializing ReportService.\n");
  }


  $reportService->setStartDate("2015-01-01");
  $reportService->setAccountingMethod("Accrual");
  $profitAndLossReport = $reportService->executeReport(ReportName::PROFITANDLOSS);
  if (!$profitAndLossReport) {
    exit("ProfitAndLossReport Is Null.\n");
  } else {
    $reportName = strtolower($profitAndLossReport->Header->ReportName);
    echo("ReportName: " . $reportName . "\n");
    echo("Profit And Loss Report Execution Successful!" . "\n");
  }

The ReportService constructor requires a ServiceContext Object, developers can get it from DataService. For a list of accepted query parameters, please refer to our documentation here: https://developer.intuit.com/docs/api/accounting/profit%20and%20loss

Download As PDF

For a few endpoint entities, developers can get the entity as PDF. Invoice is one good example. To get an invoice from QuickBooks Online as PDF, use the DownloadPDF function:

Note

To specify where the PDF is downloaded, developers can manually pass the directory to the function, DownloadPDF($invoice, $dir), or specify it at sdk.config file on the sdk, as <contentWriter strategy="export" exportDirectory="/path/to/target/folder">

<?php
require 'vendor/autoload.php';


use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Facades\Invoice;

// Prep Data Services
$dataService = DataService::Configure(array(
    'auth_mode' => 'oauth2',
    'ClientID' => "XXXXXXXXXXXXXXXXXXXXX",
    'ClientSecret' => "XXXXXXXXXXXXXXXXXXXXX",
    'accessTokenKey' =>
    'XXXXXXXXXXXXXXXXXXXXX',
    'refreshTokenKey' => "XXXXXXXXXXXXXXXXXXXXX",
    'QBORealmID' => "123456789123456",
    'baseUrl' => "Production"
));

$dataService->throwExceptionOnError(true);
$invoice = Invoice::create([
    "Id" => 2
]);

$directoryForThePDF = $dataService->DownloadPDF($invoice, "/location/to/newFolderForLog");
echo "PDF is download at: " .$directoryForThePDF;