consilience/xero-api-client

适用于Xero的PSR-18 API客户端

1.1.0 2019-09-08 12:07 UTC

This package is auto-updated.

Last update: 2024-08-29 06:12:48 UTC


README

目录

Xero认证访问的API包。利用PSR-7、PSR-17和PSR-18。

处理合作伙伴应用的OAuth 1.0a请求和令牌续订。它通过提供一个装饰过的PSR-18客户端来实现这一点。

提供支持以捕获OAuth 1.0a令牌的认证流程。

特性包括

  • 目前仅支持合作伙伴应用。(注意:正在添加私有应用的支持,并将很快进行整理。
  • 根据本地令牌年龄或远程Xero API标记的过期标志自动续订令牌。
  • 在令牌续订时,提供一个应用程序的钩子以持久化OAuth1令牌凭据。这样可以减轻应用程序的续订负担。

简单用法 - 合作伙伴应用

令牌持久性

Xero合作伙伴令牌每30分钟过期,需要续订。续订的令牌然后必须保存以用于下一个API调用。以下示例中的持久化将调用假想的类TokenStorage。它将具有以下方法

TokenStorage::get($tokenKey): string;
TokenStorage::save($tokenKey, string $tokenDetails);

令牌详细信息将被编码为JSON字符串的数组。

$token键仅用于标识存储中的令牌。我们将仅使用123作为我们的键。

授权应用

此阶段是为了允许用户授权应用程序访问其Xero组织。结果将是一组令牌凭据(访问令牌),可用于访问API。

您可以使用其他包来获取授权,例如Guzzle和Invoiced/oauth1-xero。或者使用此包来减少依赖关系,以最适合您需求的任何方式。

在OAuth 1.0a流程中有几个步骤来获取令牌

  1. 获取临时令牌。
  2. 将用户发送到Xero进行授权。
  3. 用户返回并带有验证密钥(CSRF令牌)。
  4. 使用临时令牌和验证密钥交换为长期访问令牌。

以下是详细信息。对于每个阶段,您都需要授权客户端

use Consilience\XeroApi\Client\Oauth1\Authorise;

// The public certificate for your RSA-SHA1 public/private key pair will
// be registered with the Partner application on the gateway.
// See: https://developer.xero.com/documentation/auth-and-limits/partner-applications

$authoriseClient = new Authorise([
    'consumer_key'      => 'S8IVZHU6...HUABRRK',
    'consumer_secret'   => 'PLWK9PBG...VHXAQOH',
    'callback_uri'      => 'your callback URL where the user will return to',
    'redirect_on_error' => true,
    'signature_method'  => Authorise::SIGNATURE_METHOD_RSA,
    'private_key_file'  => 'certs/privatekey.pem',
    'private_key_passphrase' => '',
]);

首先从Xero获取临时令牌

$temporaryToken = $authoriseClient->getTemporaryToken();

if ($temporaryToken->isError()) {
    throw new Exception(sprintf(
        'Failed to get temporary token; error %s (%s)',
        $temporaryToken->getErrorCode(),
        $temporaryToken->getErrorReason()
    ));
}

// Store the token object in the session for later.
// JSON serialise it; we will rebuild it.

Session::set('temporary_token', json_encode($temporaryToken));

然后使用临时令牌将用户重定向到Xero

// Your framework will probably have its own way to do a redirect
// that allows it to exit cleanly.

$authoriseUrl = $authoriseClient->authoriseUrl($temporaryToken);
header('Location: ' . (string)$authoriseUrl);
exit;

用户将带有验证器(验证令牌)返回到回调URL

// The verifier will be supplied as a GET parameter.

$oauthVerifier = $_GET['oauth_verifier'];

// Retrieve (and rebuild) the temporary token object we saved earlier.

$temporaryToken = new Token(json_decode(Session::get('temporary_token'), true));

// Use these details to get the final Access Token.

$accessToken = $authoriseClient->getAccessToken(
    $temporaryToken,
    $oauthVerifier
);

现在可以存储访问令牌以用于访问Xero API。我们将将其与令牌键123关联,以便以后可以获取它。

$tokenKey = 123;
TokenStorage::save($tokenKey, json_encode($accessToken));

工厂

Authorise客户端需要一些额外的对象才能运行

  • PSR-17 HTTP工厂(用于生成请求和URI)。
  • 它装饰的PSR-18客户端。

这些可以从Guzzle安装

composer require http-interop/http-factory-guzzle
$authoriseClient->withUriFactory(new \Http\Factory\Guzzle\UriFactory);
$authoriseClient->withRequestFactory(new \Http\Factory\Guzzle\RequestFactory);
$authoriseClient->withClient(new \Http\Adapter\Guzzle6\Client);

或使用diactoros

composer require http-interop/http-factory-diactoros
$authoriseClient->withUriFactory(new \Http\Factory\Diactoros\UriFactory);
$authoriseClient->withRequestFactory(new \Http\Factory\Diactoros\RequestFactory);

可以使用任何其他PSR-17 HTTP URI和请求工厂以及PSR-18客户端。

或者,您可以使用自动发现并让Authorise客户端发现已安装的工厂并为自身创建客户端。

composer require http-interop/http-factory-discovery
composer require php-http/guzzle6-adapter

访问Xero API

创建OAuth1令牌对象

要访问API,首先创建一个OAuth 1.0a令牌对象。

对于合作伙伴应用,这将成为一个可更新的可续订令牌,每次续订时都会在存储中更新。

use Consilience\XeroApi\Client\Oauth1\Token;
use Consilience\XeroApi\Client\OauthTokenInterface;

// Get the current token details from storage.

$tokenKey = 123;

$accessTokenData = TokenStorage::get($tokenKey);

$accessToken = new Token(json_decode($accessTokenData, true));

// Add a callback to persist any refresh to the token.

$onPersist = function (OauthTokenInterface $accessToken) use ($tokenKey) {
    TokenStorage::save($tokenKey, json_encode($accessToken));
};

// Tell the client how to persist token refreshes.

$oauth1Token = $oauth1Token->withOnPersist($onPersist);

// We will add a guard time of five minutes.
// The token will be renewed five minutes early each cycle just to
// cut down on the round-trip API accesses.

$oauth1Token = $oauth1Token->withGuardTimeSeconds(60 * 5);

对于私有应用,oauth令牌要简单得多。将令牌设置为设置私有应用时给出的消费者密钥。

// Example Private Application consumer key.

$oauth1Token = new Token ([
    'oauth_token' => 'PQ4351VSH4FHXTJTPN3JBBBNYSAYXM',
]);

配置HTTP客户端

现在我们设置一个合作伙伴或私有应用客户端。

use Consilience\XeroApi\Client\AbstractClient;

use Consilience\XeroApi\Client\App\Partner;
use Consilience\XeroApi\Client\App\AppPrivate;

// This will create a PSR-18 client decorator.
// Just use it like a PSR-18 client.

// $client can be a Psr\Http\Client\ClientInterface PSR-18 client
// of your choice, or `null` for auto-discovery.

$app = new AppPrivate($client, $oauth1Token, [
// or
$app = new Partner($client, $oauth1Token, [
    // The key and secret are needed for signing.
    'consumer_key'    => 'PQ4351VSH4FHXTJTPN3JBBBNYSAYXM',
    'consumer_secret' => '1FWE9NCU8SYB8S9ROFDTCUDCC3UXMF',
    // RSA is required for Xero.
    'signature_method' => AbstractClient::SIGNATURE_METHOD_RSA,
    // Key file.
    // Xero will already have the public part of your key.
    'private_key_file' => 'certs/privatekey.pem',
    'private_key_passphrase' => '',
]);

应用应提供,用作用户代理。这有助于Xero在查看日志时。

$app = $app->withApplicationName('My Ace Application');

为了支持作为底层PSR-18客户端的Guzzle,以及通过自动发现底层PSR-17消息工厂,您需要通过Composer安装适配器

  • guzzlehttp/psr7
  • guzzlehttp/guzzle
  • php-http/guzzle6-adapter
  • http-interop/http-factory-guzzle [这是为了php-http/guzzle6-adapter适配器能正常工作,不知道为什么]
  • http-interop/http-factory-discovery [稍后]

否则,在实例化时可以传递消息工厂和客户端。

现在我们可以对API进行请求。任何请求都可以 - GET、POST、PUT以及到任何您的应用程序支持的所有Xero端点。

use Http\Discovery\MessageFactoryDiscovery;

// The request factory is just an example.
// If you have a concrete request class, then just use that.
// We are fetching the organisation from the 2.0 Accounting
// API and requesting a JSON response.

$messageFactory = MessageFactoryDiscovery::find();

// This is a very simple request, with no parameters and no payload.
// Builing more complex requests is a job for another package, and
// that will be auto-generated from the Xero OpenAPI specs.

$request = $messageFactory->createRequest(
    'GET',
    'https://api.xero.com/api.xro/2.0/organisation'
)->withHeader('Accept', 'application/json');

$response = $app->sendRequest($request);

$payloadData = json_decode((string)$response->getBody(), true);
var_dump($payloadData);
array(5) {
  ["Id"]=>
  string(36) "2f7b676d-2b01-4699-9148-f660b8331671"
  ["Status"]=>
  string(2) "OK"
  ["ProviderName"]=>
  string(25) "Acme Payments"
  ["DateTimeUTC"]=>
  string(21) "/Date(1553978254788)/"
  ["Organisations"]=>
  array(1) {
    [0]=>
    array(30) {
      ["APIKey"]=>
      string(30) "STVXGL8FXHG6VIYX1BVKBRGRICMF08"
      ["Name"]=>
      string(24) "Acme Payments Company"
      ["LegalName"]=>
      string(24) "Acme Payments Company"
      ["PaysTax"]=>
      bool(true)
      ["Version"]=>
      string(2) "UK"
      ["OrganisationType"]=>
      string(7) "COMPANY"
      ["BaseCurrency"]=>
      string(3) "GBP"
      ["CountryCode"]=>
      string(2) "GB"
      ...snip...
      ["Phones"]=>
      array(0) {
      }
      ["ExternalLinks"]=>
      array(0) {
      }
      ["PaymentTerms"]=>
      array(0) {
      }
    }
  }
}

就这些。使用正确的方法、URL、Accept头和负载(如果使用POST)后,您可以发送请求到Xero API的所有部分。令牌续订 - 至少对于合作伙伴应用 - 将自动且对应用程序不可见地为您处理。

另一个包将处理负载解析和请求构建。这个包只关注使用OAuth 1.0a凭据的HTTP访问。Xero API操作包正在开发中(目前可用)在这里:https://github.com/consilience/xero-api-sdk

待办事项

  • 测试(如通常一样)。
  • 是否有更好的方式来处理密钥文件,可能作为流,这样就可以作为路径、字符串、文件资源等提供?
  • 一些更好的异常处理,以便我们可以捕获失败的令牌、删除的授权、一般网络错误等,并适当处理。