Intuit Quickbooks Accounting API OAuth with Developer根本不起作用


Intuit Quickbooks Accounting API OAuth with Developer not working at all

我只想使用Quickbooks API获取信息(这似乎可以通过他们的API实现)。我在他们的开发网站上安装了一个应用程序,将其链接到我创建的Quickbooks公司,并试图运行此代码从curl响应中获取任何信息,但我得到的只是授权失败(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公司内部获得所有估计,但我得到的只是401授权失败消息。

我是不是忘了在这里做点什么了?或者可能有什么不正确的地方?

是的,当然。详见下文:

$headers=$oAuth->sign(null,…

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

$headers=$oAuth->符号(null,$_SERVER['REQUEST_URI'],

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

$headers=$oAuth->sign(null,$_SERVER['REQUEST_URI'],'qyprday37CxGCuB8ow8XK76FYii3rnR4AIQrHsZDcVFNnV','wWcpmPfdPABp6LNNyYgnraTft7bgdygAmTML0aB');

您不能对OAuth访问令牌和机密进行硬编码。它们每6个月更改一次,因此必须存储在某个数据库/文件中,这样您就可以在不编辑代码的情况下每隔6个月对其进行更改。

$request_url=内爆('/',$request['url'])。'/查询query='。str_replace('+','',str_replace'('%7E','~',rawurlencode($request['query']))。'&微量元素=4';

这是您应该签名的URL。

我应该从ID为123145768959777的Quickbook公司内部获得所有估计,但我得到的只是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");
}

有用的链接:

  • https://github.com/consolibyte/quickbooks-php
  • https://github.com/consolibyte/quickbooks-php/blob/master/docs/partner_platform/example_app_ipp_v3/
  • https://github.com/consolibyte/quickbooks-php/blob/master/docs/partner_platform/example_app_ipp_v3/example_invoice_query.php