fooman/xero-php-oauth2

Xero PHP SDK for oAuth2,使用OpenAPI spec 3生成,支持PHP 8.1


README

Latest Stable Version Total Downloads Github forks Github stars License

xero-php-oauth2 SDK使开发人员能够轻松地在PHP代码中访问Xero的API,并使用小企业及总账会计数据构建强大、稳健的应用程序和软件。

目录

API客户端文档

此SDK支持以下Xero API集的完整方法覆盖

drawing

示例应用程序

示例应用程序可以帮助您快速入门,包括简单的身份验证流程和高级使用示例。

Xero账户要求

安装

建议使用Composer(对于OSX,建议使用Homebrew)来安装此SDK。

所有第三方库依赖项都由Composer管理,SDK需要PHP 5.6或更高版本。

要通过Composer安装绑定,请将xero-php-oauth2 SDK添加到您的composer.json,然后导航到您的composer.json文件所在的目录并运行以下命令

composer require xeroapi/xero-php-oauth2

如果没有composer.json文件,请运行以下命令创建一个。您需要安装Composer

composer init

配置PHPStorm

我们收到反馈称,PHPStorm IDE默认的文件大小太小,无法加载AccountingApi类。

"PHPStorm似乎无法解析XeroAPI\XeroPHP\Api\AccountingApi类。它只显示'AccountingApi'未定义类,因此无法自动完成任何方法等。"

要修复此问题,请将以下内容添加到idea.properties文件中,以将此限制增加到5000千字节

idea.max.intellisense.filesize=5000

有关如何在Mac/Windows/Linux上配置PHPStorm平台属性的说明,请参阅此处

Laravel

Xero不提供在不同框架等中使用我们的SDK的支持。我们收到了开发社区中Matt @hailwood的建议。他们使用以下包将xero-php-oauth2和Laravel集成。

身份验证

以下是带有授权流程的入门代码。您可以通过创建4个单独的PHP文件并安全地替换您的CLIENT_ID, CLIENT_SECRET和REDIRECT_URI来使用以下代码。

所有API请求都通过Xero的OAuth2.0网关进行,并在client上设置有效的access_token,该access_token将JWT附加到每个请求的头部。

如果您是第一次进行API调用,以下代码展示了使用4个独立的PHP文件实现的认证流程,并将使用这些安全凭据替换为您自己的

  • CLIENT_ID
  • CLIENT_SECRET
  • REDIRECT_URI

您还可以在我们的示例应用中查看SDK的使用。

重要

您代码中的RedirectURI(例如:http://localhost:8888/pathToApp/callback.php)需要指向callback.php文件,并匹配您在创建Xero应用时设置的RedirectURI。

  1. 将您的浏览器指向authorization.php,您将被重定向到Xero,在那里您将登录并选择一个Xero组织进行授权。我们推荐使用演示公司组织,因为此代码可以修改您连接到的组织中的数据。
  2. 完成之后,您将被返回到您的应用中的重定向URI,该URI应指向callback.php。
  3. 在callback.php中,您将获取一个访问令牌,我们将使用此令牌在authorizedResource.php中创建、读取、更新和删除连接的Xero组织中的信息。

authorization.php

<?php
  ini_set('display_errors', 'On');
  require __DIR__ . '/vendor/autoload.php';

  session_start();

  $provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => '__CLIENT_ID__',   
    'clientSecret'            => '__CLIENT_SECRET__',
    'redirectUri'             => '__REDIRECT_URI__',
    'urlAuthorize'            => 'https://login.xero.com/identity/connect/authorize',
    'urlAccessToken'          => 'https://identity.xero.com/connect/token',
    'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'
  ]);

  // Scope defines the data your app has permission to access.
  // Learn more about scopes at https://developer.xero.com/documentation/oauth2/scopes
  $options = [
    'scope' => ['openid email profile offline_access accounting.settings accounting.transactions accounting.contacts accounting.journals.read accounting.reports.read accounting.attachments']
  ];

  // This returns the authorizeUrl with necessary parameters applied (e.g. state).
  $authorizationUrl = $provider->getAuthorizationUrl($options);

  // Save the state generated for you and store it to the session.
  // For security, on callback we compare the saved state with the one returned to ensure they match.
  $_SESSION['oauth2state'] = $provider->getState();

  // Redirect the user to the authorization URL.
  header('Location: ' . $authorizationUrl);
  exit();
?>

callback.php

<?php
  ini_set('display_errors', 'On');
  require __DIR__ . '/vendor/autoload.php';
  require_once('storage.php');

  // Storage Classe uses sessions for storing token > extend to your DB of choice
  $storage = new StorageClass();  

  $provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => '__CLIENT_ID__',   
    'clientSecret'            => '__CLIENT_SECRET__',
    'redirectUri'             => '__REDIRECT_URI__', 
    'urlAuthorize'            => 'https://login.xero.com/identity/connect/authorize',
    'urlAccessToken'          => 'https://identity.xero.com/connect/token',
    'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'
  ]);
   
  // If we don't have an authorization code then get one
  if (!isset($_GET['code'])) {
    echo "Something went wrong, no authorization code found";
    exit("Something went wrong, no authorization code found");

  // Check given state against previously stored one to mitigate CSRF attack
  } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
    echo "Invalid State";
    unset($_SESSION['oauth2state']);
    exit('Invalid state');
  } else {
  
    try {
      // Try to get an access token using the authorization code grant.
      $accessToken = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
      ]);
           
      $config = XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$accessToken->getToken() );
      $identityApi = new XeroAPI\XeroPHP\Api\IdentityApi(
        new GuzzleHttp\Client(),
        $config
      );
       
      $result = $identityApi->getConnections();

      // Save my tokens, expiration tenant_id
      $storage->setToken(
        $accessToken->getToken(),
        $accessToken->getExpires(),
        $result[0]->getTenantId(),  
        $accessToken->getRefreshToken(),
        $accessToken->getValues()["id_token"]
      );
   
      header('Location: ' . './authorizedResource.php');
      exit();
     
    } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
      echo "Callback failed";
      exit();
    }
  }
?>

authorizedResource.php

<?php
  ini_set('display_errors', 'On');
  require __DIR__ . '/vendor/autoload.php';
  require_once('storage.php');

  // Use this class to deserialize error caught
  use XeroAPI\XeroPHP\AccountingObjectSerializer;

  // Storage Classe uses sessions for storing token > extend to your DB of choice
  $storage = new StorageClass();
  $xeroTenantId = (string)$storage->getSession()['tenant_id'];

  if ($storage->getHasExpired()) {
    $provider = new \League\OAuth2\Client\Provider\GenericProvider([
      'clientId'                => '__CLIENT_ID__',
      'clientSecret'            => '__CLIENT_SECRET__',
      'redirectUri'             => 'http://localhost:8888/xero-php-oauth2-starter/callback.php',
      'urlAuthorize'            => 'https://login.xero.com/identity/connect/authorize',
      'urlAccessToken'          => 'https://identity.xero.com/connect/token',
      'urlResourceOwnerDetails' => 'https://identity.xero.com/resources'
    ]);

    $newAccessToken = $provider->getAccessToken('refresh_token', [
      'refresh_token' => $storage->getRefreshToken()
    ]);

    // Save my token, expiration and refresh token
    $storage->setToken(
        $newAccessToken->getToken(),
        $newAccessToken->getExpires(),
        $xeroTenantId,
        $newAccessToken->getRefreshToken(),
        $newAccessToken->getValues()["id_token"] );
  }

  $config = XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$storage->getSession()['token'] );	
  
  $accountingApi = new XeroAPI\XeroPHP\Api\AccountingApi(
    new GuzzleHttp\Client(),
    $config
  );

  $assetApi = new XeroAPI\XeroPHP\Api\AssetApi(
    new GuzzleHttp\Client(),
    $config
  );  

  $identityApi = new XeroAPI\XeroPHP\Api\IdentityApi(
    new GuzzleHttp\Client(),
    $config
  );  

  $projectApi = new XeroAPI\XeroPHP\Api\ProjectApi(
    new GuzzleHttp\Client(),
    $config
  );  

  $message = "no API calls";
  if (isset($_GET['action'])) {
    if ($_GET["action"] == 1) {
      // Get Organisation details
      $apiResponse = $accountingApi->getOrganisations($xeroTenantId);
      $message = 'Organisation Name: ' . $apiResponse->getOrganisations()[0]->getName();
    } else if ($_GET["action"] == 2) {
      // Create Contact
      try {
        $person = new XeroAPI\XeroPHP\Models\Accounting\ContactPerson;
        $person->setFirstName("John")
                ->setLastName("Smith")
                ->setEmailAddress("john.smith@24locks.com")
                ->setIncludeInEmails(true);

        $arr_persons = [];
        array_push($arr_persons, $person);

        $contact = new XeroAPI\XeroPHP\Models\Accounting\Contact;
        $contact->setName('FooBar')
                ->setFirstName("Foo")
                ->setLastName("Bar")
                ->setEmailAddress("ben.bowden@24locks.com")
                ->setContactPersons($arr_persons);
        
        $arr_contacts = [];
        array_push($arr_contacts, $contact);
        $contacts = new XeroAPI\XeroPHP\Models\Accounting\Contacts;
        $contacts->setContacts($arr_contacts);

        $apiResponse = $accountingApi->createContacts($xeroTenantId,$contacts);
        $message = 'New Contact Name: ' . $apiResponse->getContacts()[0]->getName();
      } catch (\XeroAPI\XeroPHP\ApiException $e) {
        $error = AccountingObjectSerializer::deserialize(
          $e->getResponseBody(),
          '\XeroAPI\XeroPHP\Models\Accounting\Error',
          []
        );
        $message = "ApiException - " . $error->getElements()[0]["validation_errors"][0]["message"];
      }
    } else if ($_GET["action"] == 3) {
      $if_modified_since = new \DateTime("2019-01-02T19:20:30+01:00"); // \DateTime | Only records created or modified since this timestamp will be returned
      $if_modified_since = null;
      $where = 'Type=="ACCREC"'; // string
      $where = null;
      $order = null; // string
      $ids = null; // string[] | Filter by a comma-separated list of Invoice Ids.
      $invoice_numbers = null; // string[] |  Filter by a comma-separated list of Invoice Numbers.
      $contact_ids = null; // string[] | Filter by a comma-separated list of ContactIDs.
      $statuses = array("DRAFT", "SUBMITTED");;
      $page = 1; // int | e.g. page=1 – Up to 100 invoices will be returned in a single API call with line items
      $include_archived = null; // bool | e.g. includeArchived=true - Contacts with a status of ARCHIVED will be included
      $created_by_my_app = null; // bool | When set to true you'll only retrieve Invoices created by your app
      $unitdp = null; // int | e.g. unitdp=4 – You can opt in to use four decimal places for unit amounts

      try {
        $apiResponse = $accountingApi->getInvoices($xeroTenantId, $if_modified_since, $where, $order, $ids, $invoice_numbers, $contact_ids, $statuses, $page, $include_archived, $created_by_my_app, $unitdp);
        if ( count($apiResponse->getInvoices()) > 0 ) {
          $message = 'Total invoices found: ' . count($apiResponse->getInvoices());
        } else {
          $message = "No invoices found matching filter criteria";
        }
      } catch (Exception $e) {
          echo 'Exception when calling AccountingApi->getInvoices: ', $e->getMessage(), PHP_EOL;
      }
    } else if ($_GET["action"] == 4) {
      // Create Multiple Contacts
      try {
        $contact = new XeroAPI\XeroPHP\Models\Accounting\Contact;
        $contact->setName('George Jetson')
                ->setFirstName("George")
                ->setLastName("Jetson")
                ->setEmailAddress("george.jetson@aol.com");

        // Add the same contact twice - the first one will succeed, but the
        // second contact will throw a validation error which we'll catch.
        $arr_contacts = [];
        array_push($arr_contacts, $contact);
        array_push($arr_contacts, $contact);
        $contacts = new XeroAPI\XeroPHP\Models\Accounting\Contacts;
        $contacts->setContacts($arr_contacts);

        $apiResponse = $accountingApi->createContacts($xeroTenantId,$contacts,false);
        $message = 'First contacts created: ' . $apiResponse->getContacts()[0]->getName();

        if ($apiResponse->getContacts()[1]->getHasValidationErrors()) {
          $message = $message . '<br> Second contact validation error : ' . $apiResponse->getContacts()[1]->getValidationErrors()[0]["message"];
        }
      } catch (\XeroAPI\XeroPHP\ApiException $e) {
        $error = AccountingObjectSerializer::deserialize(
          $e->getResponseBody(),
          '\XeroAPI\XeroPHP\Models\Accounting\Error',
          []
        );
        $message = "ApiException - " . $error->getElements()[0]["validation_errors"][0]["message"];
      }
    } else if ($_GET["action"] == 5) {
      // DELETE the org FIRST Connection returned
      $connections = $identityApi->getConnections();
      $id = $connections[0]->getId();
      $result = $identityApi->deleteConnection($id);
    }
  }
?>
<html>
  <body>
    <ul>
      <li><a href="authorizedResource.php?action=1">Get Organisation Name</a></li>
      <li><a href="authorizedResource.php?action=2">Create one Contact</a></li>
      <li><a href="authorizedResource.php?action=3">Get Invoice with Filters</a></li>
      <li><a href="authorizedResource.php?action=4">Create multiple contacts and summarizeErrors</a></li>
      <li><a href="authorizedResource.php?action=5">Delete an organisation connection</a></li>
    </ul>
    <div>
    <?php
      echo($message );
    ?>
    </div>
  </body>
</html>

storage.php

<?php
class StorageClass
{
	function __construct() {
		if( !isset($_SESSION) ){
      $this->init_session();
    	}
   	}

   	public function init_session(){
    session_start();
	}

    public function getSession() {
    	return $_SESSION['oauth2'];
    }

 	public function startSession($token, $secret, $expires = null)
	{
    session_start();
	}

	public function setToken($token, $expires = null, $tenantId, $refreshToken, $idToken)
	{    
    $_SESSION['oauth2'] = [
      'token' => $token,
      'expires' => $expires,
      'tenant_id' => $tenantId,
      'refresh_token' => $refreshToken,
      'id_token' => $idToken
    ];
	}

	public function getToken()
	{
    //If it doesn't exist or is expired, return null
    if (empty($this->getSession())
      || ($_SESSION['oauth2']['expires'] !== null
      && $_SESSION['oauth2']['expires'] <= time())
    ) {
      return null;
    }
    return $this->getSession();
	}

	public function getAccessToken()
	{
    return $_SESSION['oauth2']['token'];
	}

	public function getRefreshToken()
	{
    return $_SESSION['oauth2']['refresh_token'];
	}

	public function getExpires()
	{
    return $_SESSION['oauth2']['expires'];
	}

	public function getXeroTenantId()
	{
    return $_SESSION['oauth2']['tenant_id'];
	}

	public function getIdToken()
	{
    return $_SESSION['oauth2']['id_token'];
	}

	public function getHasExpired()
	{
		if (!empty($this->getSession())) 
		{
			if(time() > $this->getExpires())
			{
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}
}
?>

配置

storage.php是一个简单的建议。在您的应用中,您应该安全地持久化与已认证Xero API连接的用户相关的令牌集数据。每次您想调用Xero API时,都需要访问之前生成的令牌集,在SDK的client上初始化它,并在进行API调用之前刷新access_token

令牌集

自定义连接

自定义连接是Xero的一个高级选项,用于构建与单个组织的M2M集成。自定义连接使用OAuth2.0的client_credentials授权,消除了交换临时代码以获取令牌集的步骤。

我们还有一个入门应用程序,其中包含更多此认证流程的PHP代码示例。

要使用此SDK与自定义连接

  $provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => '__CLIENT_ID__',
    'clientSecret'            => '__CLIENT_SECRET__',
    'redirectUri'             => '__REDIRECT_URI__ ',
    'urlAuthorize'            => 'https://login.xero.com/identity/connect/authorize',
    'urlAccessToken'          => 'https://identity.xero.com/connect/token',
    'urlResourceOwnerDetails' => 'https://identity.xero.com/resources'
  ]);

  try {
    // Try to get an access token using the client credentials grant.
    $accessToken = $provider->getAccessToken('client_credentials');
    echo($accessToken->getToken());
  } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
    // Failed to get the access token
    exit($e->getMessage());
  }

由于自定义连接仅适用于单个组织,因此您不需要将xero-tenant-id作为每个方法的第一个参数传递,或者更具体地说,对于此SDK,xeroTenantId可以是一个空字符串。

应用商店订阅

如果您正在实施订阅以参与Xero的应用商店,您需要设置应用商店订阅端点。

当计划成功购买时,用户将被重定向回设置过程中指定的URL。Xero应用商店将订阅ID附加到此URL,因此您可以通过订阅API立即确定用户已订阅的计划。

您可以使用应用凭据通过client_credentials授权类型和marketplace.billing作用域创建一个客户端。此独特的访问令牌将允许您查询appStoreApi中的任何功能。用于查询应用商店端点的客户端凭据令牌仅适用于已完成应用商店注册流程的应用。

// => /post-purchase-url?subscriptionId=03bc74f2-1237-4477-b782-2dfb1a6d8b21

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
  'clientId'                => '__CLIENT_ID__',
  'clientSecret'            => '__CLIENT_SECRET__',
  'urlAuthorize'            => 'https://login.xero.com/identity/connect/authorize',
  'urlAccessToken'          => 'https://identity.xero.com/connect/token',
  'urlResourceOwnerDetails' => 'https://identity.xero.com/resources'
]);

$apiInstance = new XeroAPI\XeroPHP\Api\AppStoreApi(
    new GuzzleHttp\Client(),
    $config
);

$accessToken = $provider->getAccessToken('client_credentials');

$apiResponse = $apiInstance->getSubscription($subscriptionId);

echo($apiResponse);

您应使用此订阅数据为您的应用程序提供用户访问/权限。

应用商店订阅Webhooks

除了通过URL传递订阅ID外,当发生购买或升级时,您将通过webhook收到通知。然后您可以使用webhook有效负载中的订阅ID来查询AppStore端点,并确定用户已购买、升级、降级或取消订阅了哪个计划。

请参阅Xero的文档以了解有关设置和接收webhooks的更多信息。

https://developer.xero.com/documentation/guides/webhooks/overview/

API客户端

您可以通过以下API集合访问不同的API集合及其可用方法:

  • AccountingApi
  • AssetApi
  • ProjectApi
  • FilesApi
  • PayrollAuApi
  • PayrollNzApi
  • PayrollUkApi
  • AppStoreApi
<?php
require_once(__DIR__ . '/vendor/autoload.php');

// Configure OAuth2 access token for authorization: OAuth2
$config = XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( 'YOUR_ACCESS_TOKEN' );       

$apiInstance = new XeroAPI\XeroPHP\Api\AccountingApi(
    new GuzzleHttp\Client(),
    $config
);
$xeroTenantId = "YOUR_XERO_TENANT_ID";

$account = new XeroAPI\XeroPHP\Models\Accounting\Account;
$account->setCode('123456');
$account->setName('FooBar');
$account->setType(XeroAPI\XeroPHP\Models\Accounting\AccountType::EXPENSE);
$account->setDescription('Hello World');

try {
  $result = $apiInstance->createAccount($xeroTenantId, $account);
} catch (Exception $e) {
  echo 'Exception when calling AccountingApi->createAccount: ', $e->getMessage(), PHP_EOL;
}
?>

或者对于资产API:

<?php
require_once(__DIR__ . '/vendor/autoload.php');

// Configure OAuth2 access token for authorization: OAuth2
$config = XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( 'YOUR_ACCESS_TOKEN' );       

$apiInstance = new XeroAPI\XeroPHP\Api\AssetApi(
    new GuzzleHttp\Client(),
    $config
);
$xeroTenantId = "YOUR_XERO_TENANT_ID";
$status = ;
$page = 1;
$pageSize = 5;
$orderBy = "AssetName";
$sortDirection = "ASC";
$filterBy = "Company Car";

try {
  $result = $apiInstance->getAssets($xeroTenantId, $status, $page, $pageSize, $orderBy, $sortDirection, $filterBy);
} catch (Exception $e) {
  echo 'Exception when calling AssetApi->getAssets: ', $e->getMessage(), PHP_EOL;
}
?>

完整的方法文档可以在此浏览:https://xeroapi.github.io/xero-php-oauth2/docs/v2/accounting/index.html

SDK约定

访问HTTP头部

每个函数都有一个更详细的WithHttpInfo选项,以便您在需要围绕任何请求头部构建逻辑时使用。

例如

  • getInvoices -> getInvoicesWithHttpInfo
  • getContacts -> getContactsWithHttpInfo

这将返回HTTP请求的3个元素的数组。

  1. 通过会计对象反序列化的对象
  2. HTTP状态码
  3. 响应头
return [
  AccountingObjectSerializer::deserialize($content, '\XeroAPI\XeroPHP\Models\Accounting\Organisations', []),
  $response->getStatusCode(),
  $response->getHeaders()
];
$apiResponse = $apiInstance->getInvoicesWithHttpInfo($xeroTenantId, $if_modified_since, $where, $order, $ids, $invoice_numbers, $contact_ids, $statuses, $page,$include_archived, $created_by_my_app, $unitdp);
echo '$apiResponse: ' . json_encode($apiResponse[2]);

$apiResponse: {"Content-Type":["application\/json; charset=utf-8"],"Content-Length":["2116"],"Server":["nginx"],"Xero-Correlation-Id":["9a8fb7f7-e3e6-4f66-a170-88effabe9f4e"],"X-AppMinLimit-Remaining":["9997"],"X-MinLimit-Remaining":["57"],"X-DayLimit-Remaining":["4954"],"Expires":["Fri, 23 Jul 2021 17:32:31 GMT"],"Cache-Control":["max-age=0, no-cache, no-store"],"Pragma":["no-cache"],"Date":["Fri, 23 Jul 2021 17:32:31 GMT"],"Connection":["keep-alive"],"X-Client-TLS-ver":["tls1.3"]}

JWT解码和Xero注册

想要实现Xero注册?我们在xero-php-oauth2中添加了对访问令牌和ID令牌的内置解码和验证。

Json Web Tokens (JWT)声称为主题断言的信息片段。

以下代码展示了如何安全地读取关于访问令牌(用户认证)和关于ID令牌(用户的身份和配置文件)的声明。

  // DECODE & VERIFY ACCESS_TOKEN
  $accessToken = (string)$storage->getSession()['token'];
  $jwtAccessTokenClaims = new XeroAPI\XeroPHP\JWTClaims();
  $jwtAccessTokenClaims->decodeAccessToken($accessToken);

  echo($jwtAccessTokenClaims->getNbf());
  echo($jwtAccessTokenClaims->getExp());
  echo($jwtAccessTokenClaims->getIss());
  echo($jwtAccessTokenClaims->getAudValue());
  echo($jwtAccessTokenClaims->getClientId());
  echo($jwtAccessTokenClaims->getAuthTime());
  echo($jwtAccessTokenClaims->getXeroUserId());
  echo($jwtAccessTokenClaims->getGlobalSessionId());
  echo($jwtAccessTokenClaims->getJti());
  echo($jwtAccessTokenClaims->getAuthenticationEventId());
  // scopes are an array therfore we dump not echo them.
  var_dump($jwtAccessTokenClaims->getScope());
  
  //DECODE & VERIFY ID_TOKEN 
  $IdToken = (string)$storage->getSession()['id_token'];
  $jwtIdTokenClaims = new XeroAPI\XeroPHP\JWTClaims();
  $jwtIdTokenClaims->decodeIdToken($IdToken);

  // 13 Claims are available
  echo($jwtIdTokenClaims->getNbf());
  echo($jwtIdTokenClaims->getExp());
  echo($jwtIdTokenClaims->getIss());
  echo($jwtIdTokenClaims->getAudValue());
  echo($jwtIdTokenClaims->getIat());
  echo($jwtIdTokenClaims->getAtHash());
  echo($jwtIdTokenClaims->getSid());
  echo($jwtIdTokenClaims->getSub());
  echo($jwtIdTokenClaims->getAuthTime());
  echo($jwtIdTokenClaims->getPreferredUsername());
  echo($jwtIdTokenClaims->getEmail());
  echo($jwtIdTokenClaims->getGivenName());
  echo($jwtIdTokenClaims->getFamilyName());

自2.x版本以来,访问会计中的日期的方法已更改

我们的会计和AU工资API使用Microsoft .NET JSON格式,即"/Date(1439434356790)/"。我们的其他API使用标准的日期格式,即"2020-03-24T18:43:43.860852"。从具有不同日期格式的OpenAPI规范构建我们的SDK具有挑战性。

因此,我们决定在OpenAPI规范中,MS .NET JSON格式的日期将是没有任何日期或日期时间格式的字符串。这意味着想要使用我们的OpenAPI规范与代码生成器开发的开发者不会在处理MS .NET JSON格式日期时遇到反序列化问题。

副作用是会计和AU工资模型现在有两个getter方法。例如,getDateOfBirth()返回字符串"/Date(1439434356790)/",而getDateOfBirthAsDate()返回标准日期"2020-05-14"。由于您可以在Java中重写方法setDateOfBirth(),它可以接受一个String或LocalDate。

//Get account by id
$result = $apiInstance->getAccount($xeroTenantId,$accountId); 	

// display formatted date
echo($result->getAccounts()[0]->getUpdatedDateUtcAsDate()->format('Y-m-d H:i:s') ):

// display string in MS .NET JSON format \/Date(1439434356790)\/
echo($result->getAccounts()[0]->getUpdatedDateUtc() ):

//When setting a date for accounting or AU Payroll, remember to use the correct method
// For example setStartDate has a 2nd  method with "AsDate" if you wish to pass a native date
// This converts the date object to MS DateFormat
$leaveapplication->setStartDateAsDate(new DateTime('2020-05-02'));

// You'll get an error from the AU Payroll API if you try setStartDate("2020-05-02")
// But if you want to pass in MS Dateformat, this string will work.
$leaveapplication->setStartDate("/Date(1547164800000+0000)/");

贡献

PRs、问题和讨论非常受欢迎并受到鼓励。请注意,本项目的绝大部分代码是基于Xero的OpenAPI规范生成的 - 将对PR进行评估,并在预合并之前将其纳入根生成模板中。

版本控制

我们尽最大努力保持OS行业semver标准,但我们可能会犯错!如果某个版本的发布说明中没有准确反映某些内容,请告知团队。

参与Xero的开发商社区

此SDK是Xero开发团队构建和维护的多个SDK之一。我们感谢社区的所有贡献。

以下是一些您作为贡献者应该注意的事项:

  • Xero已采用贡献者公约行为准则,我们期望我们社区的贡献者遵守它
  • 如果您提出问题,请确保填写github问题模板,这样做有助于我们帮助您
  • 您可以提出PRs。由于我们的SDK是生成的,我们可能会在核心SDK构建中使用您的代码,而不是合并您的代码
  • 我们为您提供了贡献指南,以便您在贡献此SDK时遵循。
  • 想了解我们如何生成SDK吗?阅读我们的开发过程,并查看我们的OpenAPISpec
  • 本软件遵循MIT许可证发布。

对于与SDK无关的问题,请参阅我们的开发者支持页面