📜 ⬆️ ⬇️

How I made friends with the Quickbooks and PHP site using the Web Connector

Once, the client needed integration of Quickbooks (hereinafter QB) and the site, which we are doing to him. The first question that I had about this was: " What is QB, and can it even be realized? ".

A little googling I found what I was looking for. Quickbooks is an accounting program for small businesses (the main market for US use). This is something like 1C, but only with a normal GUI and some cool buns. QB is an application that the user puts on his computer ( only for Windows ) and with the help of a couple of clicks, develops a company in which he maintains bookkeeping.

Well, now I, at least, know my enemy by sight, one less problem. As for integration, everything is a little more complicated. What can you integrate QB here . What we see there:
')


Hmm, PHP SDK (Coming Soon) - the last hope ... I almost despaired, but it saved me. What is this thing like - Web Connector ? On the off site for this there is a small page on which they offer to download QuickBooks Web Connector Programmer's Guide , and that's it (at least I’m tired of looking for information on the off site).


What is Web Connector?
Web Connector is a kind of intermediary between QB and the web server (it is installed along with QB). By timeout or clicking the mouse, he knocks on a specific url on your site, receives a request from the site that needs to be asked from QB and transmits it; waiting for a response from QB, and when it waits, it knocks on the site and sends you a response from QB.

And so let's get started ...
First we need to tell the Web Connector where to knock, and this is done with the help of the * .QWC file.

clients.QWC
<?xml version="1.0"?> <QBWCXML> <AppName>QuickBooks Integrator (clients)</AppName> <AppID></AppID> <AppURL>http://localhost/quickbooks/clients.php</AppURL> <AppDescription>Export Customers from QB to csv file</AppDescription> <AppSupport>http://localhost/</AppSupport> <UserName>admin</UserName> <OwnerID>{90A44FB7-33D6-4815-AC85-AC86A7E7123B}</OwnerID> <FileID>{57F3B9B6-86F6-4FCC-B1FF-967DE1813123}</FileID> <QBType>QBFS</QBType> <IsReadOnly>false</IsReadOnly> </QBWCXML> 





If you need the service to start automatically every 5 minutes, you need to add the following:

 <Scheduler> <RunEveryNMinutes>5</RunEveryNMinutes> </Scheduler> 


A short supplement to AppURL : if you do not have the ability to configure https (or no money for a real certificate) on the server, then there are 2 loopholes:

1) We set the server's IP and the domain name from localhost to the hosts on which QB is installed, do not forget to ask for this domain in the settings of Apache on the server
2) We put a self-made certificate and add it to the list of trusted servers, otherwise it will not work ( example )

To add qwc you need:
- enable QB and open a company with which the application will work
- open Web Connector
- in the Web Connector, click the Add an application button, specify the qwc file.
- when you click OK, QB will ask you if you want to give access to this application to the QB database (you need to select a user, “admin” in our case)
- when you click “Done” on the last dialog box, return to the Web Connector and enter the password for the user “admin”
- to launch the application, you need to check the box and click the Update Selected button





So, now it's the turn of preparing the site for receiving Web Connector.
As you remember, we indicated localhost/quickbooks/clients.php localhost/quickbooks/clients.php , now localhost/quickbooks/clients.php 's create it. Web Connector uses the SOAP protocol, so the site will have to raise the SOAP server.

clients.php
 <?php /** * File for integration QB * QB Webconnector send soap request to this file * * @package QB SOAP */ /** * Log function * * @param string $mess */ function _log($mess = '') { $file_name = './log/clients.log'; if(!file_exists(dirname($file_name))) mkdir(dirname($file_name), 0777); $f = fopen($file_name, "ab"); fwrite($f, "==============================================\n"); fwrite($f, "[" . date("m/d/YH:i:s") . "] ".$mess."\n"); fclose($f); } /** * Log function * * @param string $mess */ function requestId($id = '') { $file_name = './log/clients_id.log'; if(!file_exists(dirname($file_name))) mkdir(dirname($file_name), 0777); // save id into file if(trim($id) !== ''){ $f = fopen($file_name, "c+b"); fwrite($f, $id); fclose($f); } $id = trim(file_get_contents($file_name)); return $id; } /** * System variables */ define('QB_LOGIN', 'admin'); define('QB_PASSWORD', ''); define('QB_TICKET', '93f91a390fa604207f40e8a94d0d8fd11005de108ec1664234305e17e'); /** * Main class for SOAP SERVER */ require 'qb_clients.php'; /** * Create SOAP server */ $server = new SoapServer("qbwebconnectorsvc.wsdl", array('cache_wsdl' => WSDL_CACHE_NONE)); $server->setClass("Qb_Clients"); $server->handle(); 


The function requestId () is necessary to save the transaction id to a file. In the example that will be discussed later, we want to get a list of all customers, and this may be more than one thousand companies. Therefore, we will receive portions of 500, so it is safer and the server load is less. What are the 'QB_LOGIN , QB_PASSWORD and QB_TICKET see below. The last 3 lines are the creation of a SOAP server. qbwebconnectorsvc.wsdl I found this file on the offsites of the site, but I don’t remember where (I’ve recently redesigned it).

Forgot to say, Web Connector knows only 8 words: clientVersion , serverVersion , authenticate , sendRequestXML , receiveResponseXML , connectionError , getLastError, and closeConnection .

qb.php
 <?php /** * File contain base QB class and Result class (empty class for Qb reaponse) */ /** * Response class (empty class) * * @package QB SOAP * @version 2013-10-20 */ class Response{ } /** * Base class for QuickBooks integration * * @package QB SOAP * @version 2013-10-20 */ class Qb { /** * Response object * @var string */ var $response = ''; /** * Constructor * * @return void * @access public * @version 2013-10-20 */ public function __construct() { $this->response = new Response(); } /** * Function return client version * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function clientVersion($param = '') { $response->clientVersionResult = ""; return $response; } /** * Function return server version * * @return string * @access public * @version 2013-10-20 */ public function serverVersion() { $this->response->serverVersionResult = ""; return $this->response; } /** * Function try authenticate user by username/password * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function authenticate($param = '') { if(($param->strUserName == QB_LOGIN) && ($param->strPassword == QB_PASSWORD)) $this->response->authenticateResult = array(QB_TICKET, ""); else $this->response->authenticateResult = array("", "nvu"); return $this->response; } /** * Function return last error * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function connectionError($param = '') { $this->response->connectionErrorResult = "connectionError"; return $this->response; } /** * Function return last error * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function getLastError($param = '') { $this->response->getLastErrorResult = "getLastError"; return $this->response; } /** * Function close connection * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function closeConnection($param = '') { $this->response->closeConnectionResult = "Complete"; return $this->response; } } 




In the file below, you can see how the request is formed, and how the data is processed.

qb_clients.php
 <?php /** * File contains class Qb_Clients() extends Qb() */ /** * Include base class for SOAP SERVER */ require 'qb.php'; /** * Class for import all clients from Qb * * @package QB SOAP * @version 2013-10-20 */ class Qb_Clients extends Qb { /** * Function send request for Quickbooks * * @return string * @param object $param * @access public * @version 2013-10-20 */ public function sendRequestXML($param = '') { $id = requestId(); // <!-- ActiveStatus may have one of the following values: ActiveOnly [DEFAULT], InactiveOnly, All --> if($param->ticket == QB_TICKET){ $request = '<?xml version="1.0" encoding="utf-8"?> <?qbxml version="12.0"?> <QBXML> <QBXMLMsgsRq onError="stopOnError"> <CustomerQueryRq requestID="'.time().'" metaData="NoMetaData" iterator="'.(($id != '')?'Continue':'Start').'" '.(($id != '')?'iteratorID="'.$id.'"':'').'> <MaxReturned>500</MaxReturned> <ActiveStatus>ActiveOnly</ActiveStatus> </CustomerQueryRq> </QBXMLMsgsRq> </QBXML>'; $this->response->sendRequestXMLResult = $request; } else $this->response->sendRequestXMLResult = "E: Invalid ticket."; return $this->response; } /** * Function get response from QB * * @return string * @param object $param * @access public * @version 2013-03-15 */ public function receiveResponseXML($param = '') { $response = simplexml_load_string($param->response); $iteratorID = trim($response->QBXMLMsgsRs->CustomerQueryRs->attributes()->iteratorID); // set new iteratorID requestId($iteratorID); if( ($param->ticket == QB_TICKET) && isset($response->QBXMLMsgsRs->CustomerQueryRs->CustomerRet) ){ $rows = $response->QBXMLMsgsRs->CustomerQueryRs; settype($rows, 'array'); // if list contain only one item row if(isset($rows['CustomerRet']->ListID)) $rows = array($rows['CustomerRet']); else $rows = $rows['CustomerRet']; $data = array(); foreach ($rows as $i=>$r) { settype($r, 'array'); $data[] = array( 'qb_id' => trim($r['ListID']), 'qb_es' => trim($r['EditSequence']), 'is_active' => trim($r['IsActive']), 'phone' => trim($r['Phone']), 'notes' => trim($r['Notes']), 'fax' => trim($r['Fax']), 'company_name' => trim($r['Name']), 'b_email' => trim($r['Email']), 'b_email_other' => trim($r['Cc']), 'b_phone' => trim($r['AltPhone']), 'b_salutation' => trim($r['Salutation']), 'b_fname' => trim($r['FirstName']), 'b_lname' => trim($r['LastName']), 'b_address' => trim($r['BillAddress']->Addr1), 'b_address2' => trim($r['BillAddress']->Addr2), 'b_address3' => trim($r['BillAddress']->Addr3), 'b_city' => trim($r['BillAddress']->City), 'b_state' => trim($r['BillAddress']->State), 'b_country' => trim($r['BillAddress']->Country), 'b_zip' => trim($r['BillAddress']->PostalCode), ); } // echo data into log file _log(print_r($data,1)); $this->response->receiveResponseXMLResult = '30'; } else $this->response->receiveResponseXMLResult = '100'; return $this->response; } } 


The string <?qbxml version="12.0"?> Indicates that I am using the 12th version of qbxml. This is currently the latest available version (it is supported in the 13th and 14th QB). The higher the qbxml version, the more QB features. A list of all available requests can be found here . Following the link you will see all possible requests that can be sent to QB (they will be displayed in the Select Message list). The Request and Response tabs are generated depending on which request you have selected.

Ps. There is one ' but '. If you select for example the request " CustomerAdd ", then you can see that this request supports the "Contacts" block, which is available from version 12 of qbxml. But in fact, it is not implemented but only in the implementation process (why it was included in the dock is a mystery, I spent more than one hour of work on this problem, until I accidentally went to the forum where this feature was described). Therefore, if something does not work in qbxml v.12 , then it’s not a fact that it should :)

Pss. The source code is here.

Source: https://habr.com/ru/post/200728/


All Articles