Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Как я подружил Quickbooks и PHP сайт с поддержкой Web Connector’а

Anna | 31.05.2014 | нет комментариев

Как-то некогда потребовалось заказчику интеграция Quickbooks (дальше QB) и сайта, тот, что мы ему и делаем. 1-й вопрос тот, что у меня появился по этому поводу: “Что такое QB, и дозволено ли вообще это реализовать?“.

Немножко погуглив я обнаружил то что искал. Quickbooks — это бухгалтерская программа для малого бизнеса (стержневой рынок применения США). Это что-то типа 1С но только с типичным GUI и некоторыми прикольными плюшками. QB — это приложение, которое пользователь ставит у себя на компе (only for Windows) и с поддержкой пару кликов, разворачивает себе компанию в которой ведет бухгалтерию.

Хорошо, сейчас я, правда бы, знаю своего недруга в лицо, одной загвоздкой поменьше. Что касается интеграции то здесь все немножко труднее. С чем дозволено интегрировать QB вы можете посмотреть тут. Что же мы там видим:

  • .NET SDK
  • Java SDK
  • PHP SDK (Coming Soon)
  • Windows Azure SDK
  • QuickBooks QBXML v12 SDK (но on desktop scenarios only)

Мда, PHP SDK (Coming Soon) — последняя надежда… Я примерно отчаялся, но меня спасло это. Что же эта за штука такая — Web Connector? На офф сайта по этому есть маленькая страничка, на которой предлагают скачать QuickBooks Web Connector Programmer’s Guide, и все (по крайней мере мне наскучило искать информацию на офф сайте).

Что же из себя представляет Web Connector?
Web Connector — это неповторимый посредник между QB и web-сервером (он устанавливается совместно с QB). По timeout’у либо клику мышки он стучится на определенный url на вашем сайте, получает от сайта запрос, тот, что необходимо спросить у QB и передает его; ожидает результата от QB, а когда дождется то постучится на сайт и пришлет вам результат от QB.

И так приступим…
Для начала нам необходимо сказать Web Connector’у куда необходимо стучаться, а делается это с помьщью файла *.QWC.

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>
  • AppName — наименование службы, которое будет отобржатся в списке Web Connector’а
  • AppID — для чего это необходимо я так и не осознал, но без нее не работает
  • AppURL — тут указывается url на тот, что Web Connector будет стучатся. Тут есть оговорка, httpдозволено применять только в отладочных целях, те. если домен содержит слово localhost (test-localhost-serv, localhost-admin… ) то дозволено применять http. А вот если не содержит, то необходимо применять https и здесь без вариантов.
  • AppDescription — изложение службы
  • AppSupport — url по тот, что будет отображаться в списке web connector’a, как линка на справку (здесь дозволено указывать http)
  • UserName — имя пользователя, из-под которого мы будет общатся к базе QB (такой пользователь болжен быть сделан в QB)
  • OwnerID и FileID — это уникальные последовательности, состоящие из шестнадцатеричных символов (для всякой службы свое, я легко менял одну значение и все)
  • QBType — это тип подключения Web Connector’a к QB (допустимые значения QBFS либо QBPOS)
  • IsReadOnly — если ваша служба изменяет, удаляет, добавляет данные в QB, то должна быть true

Если вам необходимо что бы служба запускалать механически всякие 5 минут, то необходимо довавить следующее:

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

Короткое дополнене к AppURL: если у вас нет вероятности настроить https (либо нет денег на подлинный сертификат) на сервере, то здесь есть 2 уловки:

1) В хосты на котором стоит QB прописываем IP сервера и доменное имя с localhost, не позабудьте проситать данный домен в настройках апача на сервере
2) Ставим самопальный сертификат и добавляем его в список доверенных серверов, напротив не заработает (пример)

Для того Дабы добавить qwc необходимо:
— включить QB и открыть компанию с которой будет трудиться приложение
— открыть Web Connector
— в Web Connector’e нажимаете кнопку Add an application, указываете qwc файл.
— когда вы нажмете OK, QB вас спросит, хотите ли вы дать доступ данному приложению в базу QB (необходимо предпочесть пользователя, «admin» в нашем случае)
— когда нажмете «Done» на последнем диалоговом окне, вернитесь в Web Connector и введите пароль для пользователя «admin»
— что бы запустить приложение, необходимо поставить чекбокс и нажать кнопку Update Selected

Так, сейчас настала очередь подготовки сайта к приему Web Connector’a.
Как помните мы указали <AppURL>http://localhost/quickbooks/clients.php</AppURL>, теперь займемся его созданием. Web Connector используюет SOAP протокол, значит на сайте придется поднять SOAP сервер.

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/Y H: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();

Функция requestId() — небходима для того Дабы сберечь id транзакции в файл. В примере, тот, что будет дальше рассмотрен, мы хотим получить список всех заказчиков, а это может быть не одна тысяча компаний. Следственно будем получать долями по 500, так вернее и нагрузка на сервер поменьше. Для чего необходимы‘QB_LOGINQB_PASSWORD и QB_TICKET увидите дальше. Последние 3 строчки — и есть создание SOAP сервера.qbwebconnectorsvc.wsdl данный файл я обнаружил на просторах офф сайта, но где имеено не помню (они не давным-давно сделали редизайн).

Позабыл сказать, Web Connector знает только 8 слов: clientVersionserverVersionauthenticate,sendRequestXMLreceiveResponseXMLconnectionErrorgetLastError и 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;
    }
}

 

  • clientVersion — тут Web Connector стучится к нам с говорит: «Слышь, к тебе ображется Web Connector с версией xxxxx. Что тебе необходимо?». В результат вы можете сказать какую версию хотите, а можете и промолчать, что я и сделал
  • serverVersion — см. выше.
  • authenticate — тут Web Connector говорит нам что такой-то пользователь с таким логином (логин мы указали в qwc файле, а пароль в окошке пароля Web Connector’a), мы сопоставляем с возможными и пропускаем, либо посылаем ошибку. В случае триумфа мы отдаем Web Connector’y тикет QB_TICKET, тот, что будет использоватся в течении нынешней сессии
  • sendRequestXML — тут мы формируем запрос, тот, что Web Connector передаст QB’y.
  • receiveResponseXML — прием данных в результат на наш запрос
  • connectionError — если случилась оплошность при передаче данных, то вызовется данный способ
  • getLastError — если наш мы написали не правильный запрос, то вызовется данный способ
  • closeConnection — если все прошло по плану, и мы удачно приняли запрос

В файле, представленном ниже, вы можете видеть как формируется запрос, и как обрабатывается прием данных.

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;
    }
}

Строка <?qbxml version="12.0"?> говорит о том, что я использую 12′ю версию qbxml. На данный момент это последняя доступная версия (она поддерживается в 13′ом и 14′ом QB). Чем выше версия qbxml, тем огромнее вероятностей работы с QB. Список всех доступных запросов можете обнаружить тут. Перейдя по ссылке вы увидите все допустимые запросы которые дозволено передать в QB (они отображатся в списке Select Message). Вкладки Request и Response — генерируются в зависимости от того, какой запрос вы предпочли.

PS. Есть одно ‘но‘. Если предпочесть к примеру запрос “CustomerAdd“, то можете увидеть что данный запрос поддерживает блок «Contacts», тот, что доступен с 12′ой версии qbxml. Но на самом деле он не реализован а только в процессе внедрения (отчего его включили в доку — загадка, я потратил не один час работы на эту загвоздку, пока нечаянно не зашел на форум где описана это фишка). Следственно если что-то не работает вqbxml v.12, то не факт что должно :)

PSS. Начальный код — здесь

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB