# Keith Palmer's (Consolibyte Solutions) PHP QuickBooks Integration Framework

## Who Develops It?

• Keith Palmer keith { at } ConsoliBYTE {dot} com

## How can I contribute?

Contributions towards open-source projects are *always* appreciated! You can contribute by:

• Reporting bugs
• Contributing to the documentation on these wiki pages
• Improving the code (contact for subversion access)

## Documentation

### Where should I start?

#### First, make your life a little easier...

Before you do anything, here are a few things that will make your life much easier:

• Enable PHP error logging to a file:
• We recommend you run in E_ALL error reporting mode
• Have a basic understanding of variable scoping. If you can't see why the following example won't work, you need to go read up on scoping:
$my_var = 2; function my_function() { // This *will not* print "2", it *will* result in a PHP notice/warning print($my_var);
}
• Go download the QuickBooks SDK, and make sure that you have an XML Validator application in Start > QuickBooks SDK > Tools > XML Validator.
• Go get familiar with the QuickBooks OSR.

### The Web Connector keeps telling me "No Data Exchange Required". What does that mean?

That message means exactly what it says:

• There's no data to exchange. There's nothing to do.

The Web Connector and this framework work using a 'queue' concept. Once the queue is empty, there's nothing else to do, and you'll get that message. If you add something to the queue, then it will process those items until there's nothing left to do, and then you'll get the “No Data Exchange…” message again.

So, for instance, say you want to build a process whereby every time a customer is created within your store, the customer gets created in QuickBooks. You'd then want to set up a process where when that customer is created within your store, you queue up a request to add the customer to QuickBooks.

You do your queueing using the QuickBooks_Queue class, and the →enqueue() method. The documentation for that is here:

### I'm queueing some things up, and the Web Connector seems to get stuck in an infinite loop!

Make sure you're not doing any queueing in the same scope as the instantiation of $Server = new QuickBooks_Server(); Realize that the Web Connector is a SOAP client/server protocol. For a single successful session of the Web Connector, the Web Connector will make an HTTP request to your server.php file *at least six times*. That means that if you do something like the code shown below,$Queue→enqueue(…) will get called *at least six times*.

...
$Queue->enqeue(QUICKBOOKS_ADD_CUSTOMER,$ID);
$Server = new QuickBooks_Server(...);$Server->handle();
...

So, *don't do that*. You should be calling $Queue→enqueue() in *one* of these *three* scenarios: * Within your actual application, when an action that requires a push to QuickBooks actually occurs (e.g.: the end-user hits the 'Submit' button on your HTML form, you do an INSERT into your database for that new customer, and then you call$Queue→enqueue()).

OR

* Within a login-success hook (QuickBooks_Handlers::HOOK_LOGINSUCCESS) when using a flag to indicate records that need to be sent to QuickBooks. (e.g.: You have a flag in your 'customer' SQL table indicating whether or not it's been sent to QuickBooks, and when the Web Connector connects you do a “SELECT * FROM customer WHERE sent_to_quickbooks = FALSE” and call $Queue→enqueue() for those records). OR * Within an error handler when catching an error indicating something is not present in QuickBooks (e.g.: you do a CustomerQuery to try to find out if a customer already exists, if they don't you catch the 500 error code and then queue up a CustomerAdd with$Queue→enqueue(QUICKBOOKS_ADD_CUSTOMER, $ID);). ### How can I tell the Web Connector to connect to QuickBooks, even if QuickBooks is not open? You can tell the framework to connect to QuickBooks even if QuickBooks is not open by setting the 'qb_company_file' field in the 'quickbooks_user' SQL table to the full path of the company file. That means that for QuickBooks financial editions (i.e. Pro, Premier, Enterprise editions), you should be doing: • UPDATE quickbooks_user SET qb_company_file = 'C:\path\to\your\file.QBW' WHERE qb_username = 'your-web-connector-username' And for QuickBooks Point-of-Sale, use the connection string (you can generate this using the QuickBooks 'POS SDK TEST' application included with the SDK if you don't know it already): • UPDATE quickbooks_user SET qb_company_file = 'Computer Name=QB-POS-SERVER;Company Data=My Company;Version=9' WHERE qb_username = 'your-web-connector-username' You may also need to adjust the integrated application authorization preferences within QuickBooks to tell it to allow connections without prompting/even if QuickBooks is not open. ### What if I want to use my own authentication scheme, and not your built-in authentication? You can write your own custom authentication handlers like this: 1. Write your function function your_function_name_here($username, $password) { if ( the username and password are valid ) { return true; } return false; } 2. Register it as the authentication handler: $handler_options = array(
'authenticate_dsn' => 'function://your_function_name_here',
);

### What do the different status codes in the quickbooks_queue SQL table mean?

• q ⇒ Queued up and waiting to be processed.
• i ⇒ In progress (If you see this, and the Web Connector is *not* currently processing requests, then something probably went wrong and this request failed because you have a PHP error or a qbXML request that doesn't validate.).
• s ⇒ Successfully processed.
• e ⇒ An error occurred while processing this request.
• h ⇒ An error occurred while processing this request, but an error handler handled the error successfully (note: QuickBooks will return an error code if you search for an object(s) and the object(s) is not found. Thus, if your code does searches, and then catches 500 or 1 error codes, you will see 'h' statuses. Thus, an 'h' status doesn't necessarily indicate a problem per se, it just indicates that QuickBooks threw an error and you caught it.)
• n ⇒ NoOp - this operation was skipped because the request handler returned a QUICKBOOKS_NOOP NoOp value.

## Troubleshooting

### I'm getting the error message: "No registered functions for action: ..."

This could mean a few different things:

• First, check to make sure that the action you queued something up with is in your $map parameter to the server. i.e. This will cause a problem: // Here is our$map
$map = array( QUICKBOOKS_ADD_CUSTOMER => array( 'add_customer_request_function', 'add_customer_response_function' ), ); // Here's what we queued up$Queue->enqueue(QUICKBOOKS_QUERY_VENDOR);

// This will generate an error message "No registered functions for action: CustomerQuery" because you didn't tell the server using $map what functions to call for that type of action, you only told it what to call for QUICKBOOKS_ADD_CUSTOMER. Fix it like this:$map = array(
QUICKBOOKS_QUERY_VENDOR => array( 'query_vendor_request_function', 'query_vendor_response_function' ),
);
• After that, make sure that you're not embedding an incorrect requestID=”…” attribute in your qbXML query. The requestID=”…” attribute is used to keep track of which request goes with which response, so it's important that you get this correct in your qbXML query, or the framework won't know what to do:
// GOOD (specify the requestID attribute using the provided variable):
$xml = "... <CustomerQueryRq requestID="' .$requestID . '"> ... ";

// BETTER (leave the requestID out entirely, the framework will add it for you):
$xml = "... <CustomerQueryRq> ... "; // BAD (an incorrect requestID):$xml = "... <CustomerQueryRq requestID=" anything else other than $requestID here "> ... "; // REALLY BAD (an incorrect requestID again... why are you re-defining$requestID? it's already passed to you as a parameter to the function):
$requestID = "my made up value";$xml = "... <CustomerQueryRq requestID="' . $requestID . '"> ... "; ### I'm having problems using a 32 character or 40 character password. What's wrong? Short explanation: Due to various security concerns, 32 character and 40 character passwords are artificially disabled and will never authenticate. Long explanation: The framework supports multiple hash methods for encrypting the password in the quickbooks_user table. It supports: • Plain-text passwords • MD5-hash passwords • SHA1-hash passwords • SHA1-hash passwords with an internal salt Because passwords can be stored as 32-char MD5 hashes, allowing 32-char plain-text passwords opens up the possibility that someone could maliciously submit 32 random characters and potentially match the MD5 hash, even though the submitted hashed password does not match the stored hashed password. Thus, 32 character and 40 character passwords have an artificial limit in place which always disallows logins when a 32 character or 40 character password is submitted via the Web Connector. ### I'm having problems with my CustomerMod actions and EditSequence elements The EditSequence indicates the last time the record changes within QuickBooks. Thus, whenever you update a record with a *Mod command, you need to have the latest EditSequence to do the update. Usually, the easy way to do things is to issue a query, and use the$extra parameter to store a note to yourself indicating that you really want to do a modify. So, queue up a CustomerQuery with extra data of CustomerMod. In your CustomerQuery response handler, store the latest EditSequence, and then check the extra data. If the extra data is CustomerAdd, queue up a CustomerAdd. If the extra data is CustomerMod, queue up a CustomerMod, etc. If you do it this way, you won't need to muck around with stored procedures. As an added benefit, you can set up an error handler which will catch any 'object not found' errors if the query can't find the record, and handle the error appropriately.

You can use the \$priority parameter to the →enqueue() function to set priorities on things in the queue. Higher priority things will get run first, lower priority things later.

### Something is going wrong, but I'm not sure what. What can I do to find out?

If you're not entirely sure what is going wrong, this is what you need to do/know to find out.

1. Check the PHP error log! First, if things are going really wrong, make sure you turn on PHP error logging to a file. Without having PHP log errors to a file, it's difficult (if not impossible) to find PHP errors or warnings that are causing problems and causing requests to QuickBooks to fail.
1. Watch the MySQL tables! The two tables to watch are:
1. quickbooks_queue
2. quickbooks_log

The quickbooks_queue table shows a record of each action that we performed on QuickBooks, and what the resulting status was. If you see a status of 'i', that is a bad thing, and generally means that some PHP or database error occurred. If you see a status of 'e', it's bad and means that an error occurred while communicating with QuickBooks. Any other status is OK. The 'ident' field is the primary key of your record you passed into the queueing function (i.e.: The quote ID or the customer ID).

The quickbooks_log table shows a log of all incoming and outgoing XML and SOAP requests/responses, plus other general logging. By watching this table you can see the actual outgoing and incoming qbXML requests and responses your application is producing/consuming.

1. Check the Web Connector log file! Often if you view the Web Connector log file you can get a general idea of where the error occurred, or what the error was.