定制 consilience/xero-api-client 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

consilience/xero-api-client

Composer 安装命令:

composer require consilience/xero-api-client

包简介

PSR-18 API client for Xero

README 文档

README

Table of Contents

API package for Xero authenticated access. Leverages PSR-7, PSR-17 and PSR-18.

Handles the OAuth 1.0a requests, and token renewals for the partner application. It does this by offering a decorated PSR-18 client.

Provides support for the authentication flow to capture the OAuth 1.0a tokens.

Features include:

  • Support for Partner Application only at present. (Note: support for Private Applications is being added and will be tidied up soon.
  • Automatic token renewal of a token by its local age, or on an expiry flagged by the remote Xero API.
  • Hook to the application for persistence of the OAuth1 token credentails when they get renewed. This keeps the burden of renewals away from the application.

Simple Usage - Partner Application

Token Persistence

The Xero Partner tokens expire every 30 minutes and need to be renewed. The renewed tokens then must be saved for use on the next API call. To perform the persistence in the examples below, we will invoke the imaginary class TokenStorage. It will have methods:

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

The token details will be an array encoded to a JSON string.

The $token key just identifies a token amoung many in storage. We'll just use 123 as our key.

Authorising an Application

This stage is to allow a user to authorise the application to access their Xero organisation. The result will be a set of token credentials (Access Token) that can be used to access the API.

You can use an alternative package for obtaining authorisation, such as Guzzle and Invoiced/oauth1-xero. Or use this package to reduce dependencies, whatever fits your needs best.

There are a couple of steps in the OAuth 1.0a flow to get the tokens:

  1. Get a temporary token.
  2. Send the user to Xero to authorise the applicarion.
  3. The user returns with a verification key (a CSRF token).
  4. Use the temporary token and the verification key to exchange for the long-term Access Token.

Here are the details. For each stage you will need the authorisation client:

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' => '',
]);

First get a temporary token from 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));

Then use the temporary token to redirect the user to 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;

The user will come back to the callback URL with a verifier:

// 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
);

Now the access token can be stored for using to access the Xero API. We will store it against token key 123 so we can get it back later.

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

Factories

The Authorise client needs a few additional objects to operate:

  • A PSR-17 HTTP factory (to generate Reqeusts and URIs).
  • A PSR-18 client that it decorates.

These can be installed from 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);

or use diactoros:

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

Any other PSR-17 HTTP URI and Request factory, and PSR-18 client can be used.

Alternatively, you can enable auto-discovery and leave the Authorise client to discover the installed factories and create a client for itself.

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

Accessing the Xero API.

Create OAuth1 Token Object

To access the API, start by creating an OAuth 1.0a token object.

For the Partner Application this will be a renewable token that will be updated in storage each time it gets renewed.

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

For the Private Application the oauth token is a lot simpler. Set the token to the consumer key you were given when setting up the private application.

// Example Private Application consumer key.

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

Configure HTTP Client

Now we set up a Partner or Private application client.

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' => '',
]);

The application should be provided, which is used as a User Agent. This helps Xero when they look at their logs.

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

To support Guzzle as the underlying PSR-18 client, and the unserlying PSR-17 message factory through auto-discovery, you will need to install the adapters through composer:

  • guzzlehttp/psr7
  • guzzlehttp/guzzle
  • php-http/guzzle6-adapter
  • http-interop/http-factory-guzzle [needed for the php-http/guzzle6-adapter adaper to work, no idea why]
  • http-interop/http-factory-discovery [later]

Otherwise, the message factory and client can be passed in when instantiating.

Now we can make a request of the API. Any request wil work - GET, POST, PUT and to any Xero endpoint that your application supports.

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) {
      }
    }
  }
}

That's it. With the correct method, URL, Accept header and payload (if using POST) you can send requests to all parts of the Xero API. Token renewals - for the Partner application at least - will be handled for you automatically and invisibly to the application.

Another package will handle the payload parsing and request building. This package is just concerned with the HTTP access with OAuth 1.0a credentials. The Xero API Operations package is in development (and usable now) here: https://github.com/consilience/xero-api-sdk

TODO

  • Tests (as usual).
  • Is there a better way of handling key files, perhaps as streams, so it can be supplied as a path, a string, a file resource etc?
  • Some better exception handling, so we can catch a failed token, redacted authorisation, general network error etc and handle appropriately.

consilience/xero-api-client 适用场景与选型建议

consilience/xero-api-client 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 25 次下载、GitHub Stars 达 3, 最近一次更新时间为 2019 年 07 月 03 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 consilience/xero-api-client 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 consilience/xero-api-client 我们能提供哪些服务?
定制开发 / 二次开发

基于 consilience/xero-api-client 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 25
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 3
  • 点击次数: 8
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 3
  • Watchers: 2
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2019-07-03