title: Quickbooks WebConnector Handlers on Laravel
body: <p>I implemented <a href="https://github.com/consolibyte/quickbooks-php" rel="nofollow noreferrer">consolibyte/quickbooks-php</a> to my laravel project. Everything is fine except hook handler for login success. Most of my code are based on <a href="https://github.com/iresci23/laravel-qbd" rel="nofollow noreferrer">this repo</a> </p>
  
  <p>I want to communicate with Quickbooks on login success hook. Now I'm getting <strong>"QBWC1012: Authentication failed due to following error message.
  Response is not well-formed XML"</strong> when I add this handlers for login success. 
  Everything worked fine when I queue the actions over routes like <a href="https://github.com/iresci23/laravel-qbd/blob/c833077fad2a994fe748a564fc864804dc3bfcc6/src/LaravelQbdController.php#L68" rel="nofollow noreferrer">"addCustomer" on this repo.</a> </p>
  
  <p>I'm using Laravel V5.8. 
  Some files for Quickbooks logic below:</p>
  
  <p><strong>- app/Quickbooks/Quickbooks.php</strong> (custom quickbooks class)</p>
  
  <pre><code>&lt;?php
  
  namespace App\Quickbooks;
  
  use Illuminate\Http\Request;
  use App\Http\Controllers\Controller;
  use Illuminate\Support\Facades\Log;
  
  class Quickbooks
  {
      /**
     * User Configuration File Array
     */
    protected $dsn;
    protected $config;
    protected $map = [];
  
    public function __construct()
    {
      $this-&gt;config = config('quickbooks');
      $this-&gt;dsn    = $this-&gt;config['qb_dsn'];
    }
  
    public function connect()
    {
      // error_reporting(E_ALL | E_STRICT);
      date_default_timezone_set($this-&gt;config['qb_timezone']);
  
      // error_log(\QuickBooks_WebConnector_Handlers::HOOK_LOGINSUCCESS);
      // error_log(QuickBooks_WebConnector_Handlers::HOOK_LOGINSUCCESS);
  
      if(!$this-&gt;config['qb_dsn'])
      {
        $dbconf   = config('database');
        $db   =  $dbconf['connections'][$dbconf['default']];
  
        if($db['driver'] == 'mysql')
        {
          $db['driver'] = 'mysqli';
        }
        $this-&gt;dsn = $db['driver'] . '://' . $db['username'] . ':' .$db['password'] . '@' . $db['host'] . ':' . $db['port'] .'/'. $db['database'];
      }
  
      // Check to make sure our database is set up 
          if (!\QuickBooks_Utilities::initialized($this-&gt;dsn))
          {
              // Initialize creates the neccessary database schema for queueing up requests and logging
              \QuickBooks_Utilities::initialize($this-&gt;dsn);
  
              // This creates a username and password which is used by the Web Connector to authenticate
              \QuickBooks_Utilities::createUser($this-&gt;dsn, $this-&gt;config['qb_username'], $this-&gt;config['qb_password']);
          }
  
          // Set up our queue singleton
      \QuickBooks_WebConnector_Queue_Singleton::initialize($this-&gt;dsn);
  
      // 
      return \QuickBooks_Utilities::initialized($this-&gt;dsn);
    }
  
    public function initServer($return = true, $debug = false) 
    {
      // Create a new server and tell it to handle the requests 
      $Server = new \QuickBooks_WebConnector_Server(
        $this-&gt;dsn, 
        $this-&gt;config['actions'], 
        $this-&gt;config['error_map'], 
        $this-&gt;config['hooks'], 
        $this-&gt;config['qb_log_level'], 
        $this-&gt;config['soap']['server'], 
        QUICKBOOKS_WSDL, 
        $this-&gt;config['soap']['options'], 
        $this-&gt;config['handler_options'], 
        $this-&gt;config['driver_options'], 
        $this-&gt;config['callback_options']
      );
  
      return $Server-&gt;handle($return, $debug); 
      // if $return is turned-on, there is an error "Response is not well-formed XML". Probably because of whitespaces somewhere. I haven't figured out this yet
    } 
  
      // Generate and return a .QWC Web Connector configuration file 
      public function generateQWC()
      {
          $name = 'Metrony Admin';            // A name for your server (make it whatever you want)
          $descrip = 'Schwartz &amp; Company';        // A description of your server 
          $appurl = 'https://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']) . '/qbwc';      // This *must* be httpS:// (path to your QuickBooks SOAP server)
          $appsupport = $appurl;      // This *must* be httpS:// and the domain name must match the domain name above
          $username = $this-&gt;config['qb_username'];       // This is the username you stored in the 'quickbooks_user' table by using QuickBooks_Utilities::createUser()
          $fileid = \QuickBooks_WebConnector_QWC::fileID();       // Just make this up, but make sure it keeps that format
          $ownerid = \QuickBooks_WebConnector_QWC::ownerID();     // Just make this up, but make sure it keeps that format
          $qbtype = QUICKBOOKS_TYPE_QBFS; // You can leave this as-is unless you're using QuickBooks POS
          $readonly = false; // No, we want to write data to QuickBooks
      $run_every_n_seconds = 600; // Run every 600 seconds (10 minutes)
  
          // Generate the XML file
          $QWC = new \QuickBooks_WebConnector_QWC($name, $descrip, $appurl, $appsupport, $username, $fileid, $ownerid, $qbtype, $readonly, $run_every_n_seconds);
      $xml = $QWC-&gt;generate(); 
  
      return $xml;
    }
  
      // Queue up a request for the Web Connector to process
      public function enqueue($action, $ident, $priority = 0, $extra = null, $user = null)
      {
          $Queue = new \QuickBooks_WebConnector_Queue($this-&gt;dsn);
          Log::alert(print_r(array($action, $ident, $priority, $extra, $user), true));
          return $Queue-&gt;enqueue($action, $ident, $priority, $extra, $user);
    }
  
    // 
    // public function 
  
    /**
     * Get the last date/time the QuickBooks sync ran
     * 
     * @param string $user        The web connector username 
     * @return string         A date/time in this format: "yyyy-mm-dd hh:ii:ss"
     */
    function _quickbooks_get_last_run($user, $action)
    {
      $type = null;
      $opts = null;
      return \QuickBooks_Utilities::configRead(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_LAST . '-' . $action, $type, $opts);
    }
  
    /**
     * Set the last date/time the QuickBooks sync ran to NOW
     * 
     * @param string $user
     * @return boolean
     */
    function _quickbooks_set_last_run($user, $action, $force = null)
    {
      $value = date('Y-m-d') . 'T' . date('H:i:s');
  
      if ($force)
      {
        $value = date('Y-m-d', strtotime($force)) . 'T' . date('H:i:s', strtotime($force));
      }
      // error_log($action . ": " . $value);
      return \QuickBooks_Utilities::configWrite(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_LAST . '-' . $action, $value);
    }
  
    /**
     * 
     * 
     */
    function _quickbooks_get_current_run($user, $action)
    {
      $type = null;
      $opts = null;
      return \QuickBooks_Utilities::configRead(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_CURR . '-' . $action, $type, $opts);
    }
  
    /**
     * 
     * 
     */
    function _quickbooks_set_current_run($user, $action, $force = null)
    {
      $value = date('Y-m-d') . 'T' . date('H:i:s');
      if ($force)
      {
        $value = date('Y-m-d', strtotime($force)) . 'T' . date('H:i:s', strtotime($force));
      }
      return \QuickBooks_Utilities::configWrite(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_CURR . '-' . $action, $value);
    }
  
  
  }
  </code></pre>
  
  <p><strong>- app/Http/Controllers/QuickbooksController.php</strong></p>
  
  <pre><code>&lt;?php
  
  namespace App\Http\Controllers;
  
  use Illuminate\Http\Request;
  use Illuminate\Http\Response;
  use App\Quickbooks\Quickbooks;
  use Log;
  
  class QuickbooksController extends Controller
  {
      protected $qbd;
  
      public function __construct() 
      {
          $this-&gt;qbd = new Quickbooks;
          $this-&gt;qbd-&gt;connect();
      }
  
      // 
      public function initQBWC(Request $request) 
      {
  
          $response = $this-&gt;qbd-&gt;initServer(true, true); //$return,$debug
  
          $contentType = 'text/plain';
  
          if($request-&gt;isMethod('post'))
          {
              $contentType = 'text/xml';
          }
          else if($request-&gt;input('wsdl') !== null or $request-&gt;input('WSDL') !== null)
          {
              $contentType = 'text/xml';
          }
  
          if($contentType == 'text/xml')
          { 
              $tidy = new \tidy();
              $response = $tidy-&gt;repairString($response, ['input-xml'=&gt; 1, 'indent' =&gt; 0, 'wrap' =&gt; 0]);
          }
          // Uncomment log if you want to see the request headers submitted from WebConnector
  
          return response($response,200)-&gt;header('Content-Type', $contentType);
      }
  
      public function generateQWC()
      {
          $xml = $this-&gt;qbd-&gt;generateQWC();
          // header('Content-type: text/xml');
          // print($xml);
          // exit;
          return response($xml,200)
              -&gt;header('Content-Type', 'text/xml')
              -&gt;header('Content-Disposition', 'attachment; filename="my-quickbooks-wc-file.qwc"'); // Send as a file download
      }
  
  }
  
  </code></pre>
  
  <p><strong>- config/quickbooks.php</strong></p>
  
  <pre><code>&lt;?php
  
  return [
    'qb_dsn'            =&gt; env('QB_DSN'),
    'qb_username'   =&gt; env('QB_USERNAME'),
    'qb_password'   =&gt; env('QB_PASSWORD'),
    'qb_timezone'   =&gt; env('QB_TIMEZONE'), 
    'qb_log_level'  =&gt; constant(env('QB_LOGLEVEL')), 
    'qb_mem_limit'  =&gt; env('QB_MEMLIMIT'), 
    'error_map'     =&gt; array( 
      '*' =&gt; array(App\Quickbooks\ErrorHandler::class, '_quickbooks_error_catchall') 
    ), 
    'hooks'           =&gt; array( 
      \QuickBooks_WebConnector_Handlers::HOOK_LOGINSUCCESS =&gt; array(App\Quickbooks\HooksHandler::class, '_quickbooks_hook_loginsuccess') 
    ), // An array of callback hooks
    'soap'            =&gt; array( 
      'server'    =&gt; constant(env('QB_SOAPSERVER')), // A pure-PHP SOAP server (no PHP ext/soap extension required, also makes debugging easier)
      'options'   =&gt; [] // See http://www.php.net/soap
    ),
    'handler_options'=&gt; array(
      'deny_concurrent_logins' =&gt; false,
      'deny_reallyfast_logins' =&gt; false
    ), // See the comments in the QuickBooks/Server/Handlers.php file
    'driver_options' =&gt; array(), // See the comments in the QuickBooks/Driver/&lt;YOUR DRIVER HERE&gt;.php file ( i.e. 'Mysql.php', etc. )
    'callback_options'=&gt; array(),
  
    // Map QuickBooks actions to handle functions 
      'actions'   =&gt; [
          QUICKBOOKS_IMPORT_ACCOUNT =&gt; [
        [ App\Quickbooks\AccountImport::class, 'xmlRequest' ],
        [ App\Quickbooks\AccountImport::class, 'xmlResponse' ]
      ]
      // QUICKBOOKS_ADD_EMPLOYEE =&gt; [
      //   [ App\Quickbooks\EmployeeAdd::class, 'xmlRequest' ],
      //   [ App\Quickbooks\EmployeeAdd::class, 'xmlResponse' ]
      // ],
      // QUICKBOOKS_ADD_CUSTOMER =&gt; [
      //   [ App\Quickbooks\CustomerAdd::class, 'xmlRequest' ],
      //   [ App\Quickbooks\CustomerAdd::class, 'xmlResponse' ]
      // ],
      ]
  ];
  ...
  </code></pre>
  
  <p><strong>- Web Connector Log</strong></p>
  
  <pre><code>20190619.18:17:12 UTC   : QBWebConnector.WebServiceManager.DoUpdateSelected() : updateWS() for application = 'Metrony Admin' has STARTED
  20190619.18:17:12 UTC   : QBWebConnector.RegistryManager.getUpdateLock() : HKEY_CURRENT_USER\Software\Intuit\QBWebConnector\UpdateLock = FALSE
  20190619.18:17:12 UTC   : QBWebConnector.RegistryManager.setUpdateLock() : HKEY_CURRENT_USER\Software\Intuit\QBWebConnector\UpdateLock has been set to True
  20190619.18:17:12 UTC   : QBWebConnector.RegistryManager.setUpdateLock() : ********************* Update session locked *********************
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.instantiateWebService() : Initiated connection to the following application.
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.instantiateWebService() : AppName: Metrony Admin
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.instantiateWebService() : AppUniqueName (if available): Metrony Admin
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.instantiateWebService() : AppURL: http://localhost/schwartz/public/qbd-connector/qbwc
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_serverVersion() : *** Calling serverVersion().
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_serverVersion() : Actual error received from web service for serverVersion call: &lt;Response is not well-formed XML.&gt;. For backward compatibility of all webservers, QBWC will catch all errors under app-not-supporting-serverVersion.
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_serverVersion() : This application does not contain support for serverVersion. Allowing update operation for backward compatibility.
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_clientVersion() : *** Calling clientVersion() with following parameter:&lt;productVersion="2.3.0.25"&gt;
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.updateWS() : Actual error received from web service for clientVersion call: &lt;Response is not well-formed XML.&gt;. For backward compatibility of all webservers, QBWC will catch all errors under app-not-supporting-clientVersion.
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_clientVersion() : This application does not contain support for clientVersion. Allowing update operation for backward compatibility.
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_authenticate() : Authenticating to application 'Metrony Admin', username = 'quickbooks'
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_authenticate() : *** Calling authenticate() with following parameters:&lt;userName="quickbooks"&gt;&lt;password=&lt;MaskedForSecurity&gt;
  20190619.18:17:12 UTC   : QBWebConnector.SOAPWebService.do_authenticate() : QBWC1012: Authentication failed due to following error message.
  Response is not well-formed XML.
  More info:
  StackTrace =    at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
     at QBWebConnector.localhost.WCWebServiceDoc.authenticate(String strUserName, String strPassword)
     at QBWebConnector.localhost.WCWebService.authenticate(String strUserName, String strPassword)
     at QBWebConnector.SOAPWebService.authenticate(String UserName, String Password)
     at QBWebConnector.WebService.do_authenticate(String&amp; ticket, String&amp; companyFileName)
  Source = System.Web.Services
  20190619.18:17:12 UTC   : QBWebConnector.RegistryManager.setUpdateLock() : HKEY_CURRENT_USER\Software\Intuit\QBWebConnector\UpdateLock has been set to False
  20190619.18:17:12 UTC   : QBWebConnector.RegistryManager.setUpdateLock() : ********************* Update session unlocked *********************
  20190619.18:17:12 UTC   : QBWebConnector.WebServiceManager.DoUpdateSelected() : Update completed with errors. See log (QWClog.txt) for details.
  </code></pre>
  
  <p><strong>- Laravel Error Log</strong></p>
  
  <pre><code>[2019-06-19 15:05:39] local.ERROR: Array
  (
      [0] =&gt; 
      [1] =&gt; -4
      [2] =&gt; Callback does not exist: [function] App\Quickbooks\HooksHandler(...)
  )
  </code></pre>