Intuit Quickbooks会计API OAuth与开发人员根本不工作

时间:2016-05-06 07:18:29

标签: php curl quickbooks intuit-partner-platform quickbooks-online

我只想使用Quickbooks API获取信息(看起来这应该可以通过他们的API实现)。我在他们的开发站点上设置了一个应用程序,将其链接到我创建的Quickbooks公司,并且我正在尝试运行此代码以从卷曲响应中获取任何内容,但我得到的只是授权失败(401)消息。为什么没有获得授权?一直在研究这个网站12个小时,他们提供的工作都没有提供。我使用此页面作为参考:https://developer.intuit.com/docs/0050_quickbooks_api/0010_your_first_request/rest_essentials_for_the_quickbooks_api和此:https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/0015_calling_data_services#/The_authorization_header

我的index.php文件如下:

    <?php

define('IS_SANDBOX', 1);

require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'oAuth.php');

// GET baseURL/v3/company/companyID/resourceName/entityID
// consumer and consumer_secret
$oAuth = new QuickBooks_IPP_OAuth('qyprdwX21R3klmiskW3AaYLnDRGNLn', 'FDPpxScC6CIgoA07Uc2NYtZJk45CqNDI1Gw4zntn');

$request = array(
    'url' => array(
        'base_request_uri' => IS_SANDBOX == 1 ? 'https://sandbox-quickbooks.api.intuit.com' : 'https://quickbooks.api.intuit.com',
        'version' => 'v3',
        'company' => 'company',
        'companyID' => '123145768959777'
    ),
    'query' => 'SELECT * FROM ESTIMATE',
    'headers' => array(
        'Host' => IS_SANDBOX == 1 ? 'sandbox-quickbooks.api.intuit.com' : 'quickbooks.api.intuit.com',
        'Accept' => 'application/json',
        'User-Agent' => 'APIExplorer'
    )
);

$request_url = implode('/', $request['url']) . '/query?query=' . str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($request['query']))) . '&minorversion=4';

// token, and token_secret
$headers = $oAuth->sign('GET', $request_url, 'qyprdaiy37CxGCuB8ow8XK76FYii3rnRU4AIQrHsZDcVFNnV', 'wWcpmPffdPABp6LNNyYgnraTft7bgdygAmTML0aB');

$request['headers']['Authorization'] = 'OAuth ' . array_pop($headers);

$response = curl($request_url, $request['headers']);

echo '<pre>', var_dump($response), '</pre>';
echo '<pre>', var_dump($request['headers']), '</pre>';


function curl($url, $headers) {
    try {
        $request_headers = array();
        $ch = curl_init();

        if (FALSE === $ch)
            throw new Exception('failed to initialize');

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        if (!empty($headers)) {

            foreach($headers as $key => $value)
            {
                if ($key == 'GET')
                {
                    $request_headers[] = $key . ' ' . $value;
                    continue;
                }

                $request_headers[] = $key . ': ' . $value;
            }

            curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable SSL Verfication, so we can get all info from non-SSL site!
        }

        $data = curl_exec($ch);
        $header = curl_getinfo($ch);

        echo '<h2>Curl Get Info</h2>';
        echo '<pre>', var_dump($header), '</pre>';

        if (FALSE === $data)
            throw new Exception(curl_error($ch), curl_errno($ch));
        else
            return $data;

        curl_close($ch);
    } catch(Exception $e) {
        trigger_error(sprintf(
                'Curl failed with error #%d: %s',
                $e->getCode(), $e->getMessage()), E_USER_ERROR);
    }
}

echo '<pre>', var_dump($request_url), '</pre>';


?>

我的oAuth.php文件如下所示:

<?php

/**
 * QuickBooks PHP DevKit
 * 
 * Copyright (c) 2010 Keith Palmer / ConsoliBYTE, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.opensource.org/licenses/eclipse-1.0.php
 * 
 * @author Keith Palmer <keith@consolibyte.com>
 * @license LICENSE.txt 
 * 
 * @package QuickBooks
 */

class QuickBooks_IPP_OAuth
{
    private $_secrets;

    protected $_oauth_consumer_key;
    protected $_oauth_consumer_secret;

    protected $_oauth_access_token;
    protected $_oauth_access_token_secret;

    protected $_version = null;
    protected $_signature = null;
    protected $_keyfile;

    /**
     * 
     */
    const NONCE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

    const METHOD_POST = 'POST';
    const METHOD_GET = 'GET';
    const METHOD_PUT = 'PUT';
    const METHOD_DELETE = 'DELETE';

    const DEFAULT_VERSION = '1.0';
    const DEFAULT_SIGNATURE = 'HMAC-SHA1';

    const SIGNATURE_PLAINTEXT = 'PLAINTEXT';
    const SIGNATURE_HMAC = 'HMAC-SHA1';
    const SIGNATURE_RSA = 'RSA-SHA1';

    /** 
     * Create our OAuth instance
     */
    public function __construct($oauth_consumer_key, $oauth_consumer_secret)
    {
        $this->_oauth_consumer_key = $oauth_consumer_key;
        $this->_oauth_consumer_secret = $oauth_consumer_secret;

        $this->_version = QuickBooks_IPP_OAuth::DEFAULT_VERSION;
        $this->_signature = QuickBooks_IPP_OAuth::DEFAULT_SIGNATURE;
    }

    /**
     * Set the signature method
     * 
     * 
     */
    public function signature($method, $keyfile = null)
    {
        $this->_signature = $method;
        $this->_keyfile = $keyfile;
    }

    /**
     * Sign an OAuth request and return the signing data (auth string, URL, etc.)
     *
     * 
     */
    public function sign($method, $url, $oauth_token = null, $oauth_token_secret = null, $params = array()) 
    {
        /*
        print('got in: [' . $method . '], ' . $url);
        print_r($params);
        print('<br /><br /><br />');
        */

        if (!is_array($params))
        {
            $params = array();
        }

        $params = array_merge($params, array(
            'oauth_consumer_key' => $this->_oauth_consumer_key, 
            'oauth_signature_method' => $this->_signature, 
            'oauth_nonce' => $this->_nonce(), 
            'oauth_timestamp' => $this->_timestamp(), 
            'oauth_version' => $this->_version,
            ));

        // Add in the tokens if they were passed in
        if ($oauth_token)
        {
            $params['oauth_token'] = $oauth_token;
        }

        if ($oauth_token_secret)
        {
            $params['oauth_secret'] = $oauth_token_secret;
        }

        // Generate the signature
        $signature_and_basestring = $this->_generateSignature($this->_signature, $method, $url, $params);

        $params['oauth_signature'] = $signature_and_basestring[1];

        /*
        print('<pre>');
        print('BASE STRING IS [' . $signature_and_basestring[0] . ']' . "\n\n");
        print('SIGNATURE IS: [' . $params['oauth_signature'] . ']');
        print('</pre>');
        */

        $normalized = $this->_normalize($params);

        /*
        print('NORMALIZE 1 [' . $normalized . ']' . "\n");
        print('NORMZLIZE 2 [' . $this->_normalize2($params) . ']' . "\n");
        */

        if (false !== ($pos = strpos($url, '?')))
        {
            $url = substr($url, 0, $pos);
        }

        $normalized_url = $url . '?' . $normalized;         // normalized URL

        return array (
            0 => $signature_and_basestring[0],      // signature basestring
            1 => $signature_and_basestring[1],      // signature
            2 => $normalized_url, 
            3 => $this->_generateHeader($params, $normalized),  // header string
            );
    }

    protected function _generateHeader($params, $normalized) 
    {
        // oauth_signature="' . $this->_escape($params['oauth_signature']) . '", 
        $str = '';

        if (isset($params['oauth_token']))
            $str .= rawurlencode('oauth_token') . '="' . rawurlencode($params['oauth_token']) . '", ';

        $nonce = rawurlencode(md5(mt_rand()));

        $nonce_chars = str_split($nonce);

        $formatted_nonce = '';
        foreach($nonce_chars as $n => $chr)
        {
            if (in_array($n, array(8, 12, 16, 20)))
                $formatted_nonce .= '-';

            $formatted_nonce .= $chr;
        }

        $str .= rawurlencode('oauth_nonce') . '="' . $formatted_nonce . '", ' . 
            rawurlencode('oauth_consumer_key') . '="' . rawurlencode($params['oauth_consumer_key']) . '", ' . 
            rawurlencode(oauth_signature_method) . '="' . rawurlencode($params['oauth_signature_method']) . '", ' .
            rawurlencode(oauth_timestamp) . '="' . rawurlencode($params['oauth_timestamp']) . '", ' . 
            rawurlencode(oauth_version) . '="' . rawurlencode($params['oauth_version']) . '", ' . 
            rawurlencode(oauth_signature) . '="' . $this->_escape($params['oauth_signature']) . '"';

        return str_replace(array(' ', '  ', '   '), '', str_replace(array("\r", "\n", "\t"), ' ', $str));
    }

    /**
     * 
     * 
     */
    protected function _escape($str) 
    {
        if ($str === false)
        {
            return $str;
        }
        else
        {
            return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode($str)));
        }
    }

    protected function _timestamp()
    {
        //return 1326976195;

        //return 1318622958;
        return time();
    }

    protected function _nonce($len = 5) 
    {
        //return '1234';

        $tmp = str_split(QuickBooks_IPP_OAuth::NONCE);
        shuffle($tmp);

        //return 'kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg';
        return substr(implode('', $tmp), 0, $len);
    }

    protected function _normalize($params)
    {   
        $normalized = array();

        ksort($params);
        foreach ($params as $key => $value)
        {
            // all names and values are already urlencoded, exclude the oauth signature
            if ($key != 'oauth_secret')
            {
                if (is_array($value))
                {
                    $sort = $value;
                    sort($sort);
                    foreach ($sort as $subkey => $subvalue)
                    {
                        $normalized[] = $this->_escape($key) . '=' . $this->_escape($subvalue);
                    }
                }
                else
                {
                    $normalized[] = $this->_escape($key) . '=' . $this->_escape($value);
                }
            }
        }

        return implode('&', $normalized);
    }

    protected function _generateSignature($signature, $method, $url, $params = array()) 
    {
        /*
        print('<pre>params for signing');
        print_r($params);
        print('</pre>');
        */

        //if (false !== strpos($url, 'get_access'))
        /*if (true)
        {
            print($url . '<br />' . "\r\n\r\n");
            die('NORMALIZE MINE [' . $this->_normalize($params) . ']');
        }*/

        /*
        print('<pre>');
        print('NORMALIZING [' . "\n");
        print($this->_normalize($params) . "]\n\n\n");
        print('SECRET KEY FOR SIGNING [' . $secret . ']' . "\n");
        print('</pre>');
        */

        if (false !== ($pos = strpos($url, '?')))
        {
            $tmp = array();
            parse_str(substr($url, $pos + 1), $tmp);

            // Bad hack for magic quotes... *sigh* stupid PHP
            if (get_magic_quotes_gpc())
            {
                foreach ($tmp as $key => $value)
                {
                    if (!is_array($value))
                    {
                        $tmp[$key] = stripslashes($value);
                    }
                }
            }

            $params = array_merge($tmp, $params);

            $url = substr($url, 0, $pos);
        }

        //print('url [' . $url . ']' . "\n");
        //print_r($params);

        $sbs = $this->_escape($method) . '&' . $this->_escape($url) . '&' . $this->_escape($this->_normalize($params));

        //print('sbs [' . $sbs . ']' . "\n");

        // Which signature method? 
        switch ($signature)
        {
            case QuickBooks_IPP_OAuth::SIGNATURE_HMAC:
                return $this->_generateSignature_HMAC($sbs, $method, $url, $params);    
            case QuickBooks_IPP_OAuth::SIGNATURE_RSA:
                return $this->_generateSignature_RSA($sbs, $method, $url, $params);
        }

        return false;
    }


    /*
        // Pull the private key ID from the certificate
        $privatekeyid = openssl_get_privatekey($cert);

        // Sign using the key
        $sig = false;
        $ok  = openssl_sign($base_string, $sig, $privatekeyid);   

        // Release the key resource
        openssl_free_key($privatekeyid);

        base64_encode($sig)
    */


    protected function _generateSignature_RSA($sbs, $method, $url, $params = array())
    {
        // $res = ... 
        $res = openssl_pkey_get_private('file://' . $this->_keyfile);

        /*
        print('key id is: [');
        print_r($res);
        print(']');
        print("\n\n\n");
        */

        $signature = null;
        $retr = openssl_sign($sbs, $signature, $res);

        openssl_free_key($res);

        return array(
            0 => $sbs, 
            1 => base64_encode($signature), 
            );
    }



    /*
    $key = $request->urlencode($consumer_secret).'&'.$request->urlencode($token_secret);
    $signature = base64_encode(hash_hmac("sha1", $base_string, $key, true));
    */  

    protected function _generateSignature_HMAC($sbs, $method, $url, $params = array())
    {
        $secret = $this->_escape($this->_oauth_consumer_secret);

        $secret .= '&';

        if (!empty($params['oauth_secret']))
        {
            $secret .= $this->_escape($params['oauth_secret']);
        }

        //print('generating signature from [' . $secret . ']' . "\n\n");

        return array(
            0 => $sbs, 
            1 => base64_encode(hash_hmac('sha1', $sbs, $secret, true)), 
            );
    }
}
?>

$request['headers']看起来像这样:

array(4) {
  ["Host"]=>
  string(33) "sandbox-quickbooks.api.intuit.com"
  ["Accept"]=>
  string(16) "application/json"
  ["User-Agent"]=>
  string(11) "APIExplorer"
  ["Authorization"]=>
  string(306) "OAuth oauth_token="qyprdaiy37CxGCuB8ow8XK76FYii3rnRU4AIQrHsZDcVFNnV",oauth_nonce="189f7f21-6dd9-c136-e208-0f33141feea5",oauth_consumer_key="qyprdwX21R3klmiskW3AaYLnDRGNLn",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1462545676",oauth_version="1.0",oauth_signature="BIpYveqCxlfVT4Ps4qJypS%2BXHh8%3D""
}

响应如下:

message=ApplicationAuthenticationFailed; errorCode=003200; statusCode=401
            SignatureBaseString: GET&https%3A%2F%2Fsandbox-quickbooks.api.intuit.com%2Fv3%2Fcompany%2F123145768959777%2Fquery&minorversion%3D4%26oauth_consumer_key%3DqyprdwX21R3klmiskW3AaYLnDRGNLn%26oauth_nonce%3D189f7f21-6dd9-c136-e208-0f33141feea5%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1462545676%26oauth_token%3Dqyprdaiy37CxGCuB8ow8XK76FYii3rnRU4AIQrHsZDcVFNnV%26oauth_version%3D1.0%26query%3DSELECT%2520%252A%2520FROM%2520ESTIMATE

$request_url看起来像这样:

https://sandbox-quickbooks.api.intuit.com/v3/company/123145768959777/query?query=SELECT%20%2A%20FROM%20ESTIMATE&minorversion=4

我忘了在这里做点什么吗?或者某些事情可能不正确?我应该从ID为123145768959777的Quickbook Company中获取所有估算值,但我得到的只是401授权失败消息。

1 个答案:

答案 0 :(得分:1)

  

我忘了在这里做点什么吗?或者某些事情可能不正确?

是的,当然。请参阅下面的具体信息:

  

$ headers = $ oAuth-&gt; sign(null,...

null不是有效的HTTP请求方法。有效的HTTP请求方法类似于GETPOST等。请参阅HTTP规范和OAuth规范。

  

$ headers = $ oAuth-&gt; sign(null,$ _SERVER [&#39; REQUEST_URI&#39;],

您为什么要签署服务器请求URI?您应该将发送curl请求的URL签名到,而不是用户在您自己的网站上访问的URL。

  

$ headers = $ oAuth-&gt; sign(null,$ _SERVER [&#39; REQUEST_URI&#39;],&#39; qyprdaiy37CxGCuB8ow8XK76FYii3rnRU4AIQrHsZDcVFNnV&#39;,&#39; wWcpmPffdPABp6LNNyYgnraTft7bgdygAmTML0aB&#39;);

您无法对OAuth访问令牌和密码进行硬编码。它们每6个月更换一次,因此必须存储在某个地方的数据库/文件中,这样您就可以每隔6个月更改一次代码就可以更改它们。

  

$ request_url = implode(&#39; /&#39;,$ request [&#39; url&#39;])。 &#39; /查询的查询=&#39?; 。 str_replace(&#39; +&#39;,&#39;&#39;,str_replace(&#39;%7E&#39;,&#39;〜&#39;,rawurlencode($ request [&## 39;查询&#39;])))。 &#39;&安培; minorversion = 4&#39 ;;

这是您应该签名的网址。

  

我应该从Quickbook公司获得ID为123145768959777的All Estimates,但我得到的只是401授权失败消息。

如果您需要进一步的帮助,发布您的实际HTTP请求和响应会很有意义。如果没有真正看到发送的请求以及收到的回复,任何人都无法告诉您。

另外......你意识到所有这些艰苦的工作已经为你完成了,使用你从中获取代码的库,对吧?例如你不需要做你正在做的任何事情 - 它只是重新发明轮子。只是做:

require_once dirname(__FILE__) . '/config.php';

$EstimateService = new QuickBooks_IPP_Service_Estimate();
$estimates = $EstimateService->query($Context, $realm, "SELECT * FROM Estimate STARTPOSITION 1 MAXRESULTS 10");

foreach ($estimates as $Estimate)
{
    print('Estimate # ' . $Estimate->getDocNumber() . "\n");
}

有用的链接: