Home
Softono
x

xeroapi

Professional software vendor delivering innovative solutions on the Softono platform. Specialized in both open-source and proprietary software development.

Total Products
5

Software by xeroapi

xero-node
Open Source

xero-node

# xero-node [![npm version](https://badge.fury.io/js/xero-node.svg)](https://badge.fury.io/js/xero-node) [![Github forks](https://img.shields.io/github/forks/XeroAPI/xero-node.svg)](https://github.com/XeroAPI/xero-node/network) [![Github stars](https://img.shields.io/github/stars/XeroAPI/xero-node.svg)](https://github.com/XeroAPI/xero-node/stargazers) ![npm](https://img.shields.io/npm/dt/xero-node) The xero-node SDK makes it easy for developers to access Xero's APIs in their JavaScript code, and build robust applications and software using small business & general ledger accounting data. # Table of Contents - [API Client documentation](#api-client-documentation) - [Sample Applications](#sample-applications) - [Xero Account Requirements](#xero-account-requirements) - [Installation](#installation) - [Configuration](#configuration) - [Authentication](#authentication) - [Custom Connections](#custom-connections) - [App Store Subscriptions](#app-store-subscriptions) - [API Clients](#api-clients) - [Helper Methods](#helper-methods) - [Usage Examples](#usage-examples) - [SDK conventions](#sdk-conventions) - [Security](#security) - [Contributing](#contributing) <hr> ## API Client documentation This SDK supports full method coverage for the following Xero API sets: | API Set | Description | | --- | --- | | [`Accounting`](https://xeroapi.github.io/xero-node/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* | [Assets](https://xeroapi.github.io/xero-node/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | | [Bankfeeds](https://xeroapi.github.io/xero-node/bankfeeds/index.html) | The Bankfeeds API facilitates the flow of transaction and statement data | | [Files](https://xeroapi.github.io/xero-node/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | | [Projects](https://xeroapi.github.io/xero-node/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | | [Payroll (AU)](https://xeroapi.github.io/xero-node/payroll-au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/xero-node/payroll-nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (UK)](https://xeroapi.github.io/xero-node/payroll-uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | <img src="https://i.imgur.com/3ISSOwp.png" alt="drawing" width="350"/> <hr> ## Sample Applications Sample apps can get you started quickly with simple auth flows and advanced usage examples. | Sample App | Description | Screenshot | | --- | --- | --- | | [`starter-app`](https://github.com/XeroAPI/xero-node-oauth2-ts-starter) | Basic getting started code samples | <img src="https://i.imgur.com/k208KAv.png" alt="drawing" width="200"/> | [`full-app`](https://github.com/XeroAPI/xero-node-oauth2-app) | Complete app with more complex examples | <img src="https://i.imgur.com/TaMQvnp.png" alt="drawing" width="500"/> | [`custom-connections-starter`](https://github.com/XeroAPI/xero-node-custom-connections-starter) | Basic app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/guides/oauth2/custom-connections) for building M2M integrations to a single org | <img src="https://i.imgur.com/HoQHLuq.png" alt="drawing" width="300"/> | [`xero-node-sso-app`](https://github.com/XeroAPI/xero-node-sso-app) | App showing Xero Single Sign On - as well as basic setup and usage of the Xero App Store `appStoreApi.getSubscription` endpoint | <img src="https://i.imgur.com/4NGowZz.png" alt="drawing" width="300"/> | [`xero-node-sso-form`](https://github.com/XeroAPI/xero-node-sso-form) | App showing Sign up with Xero to Lead | <img src="https://raw.githubusercontent.com/XeroAPI/xero-node-sso-form/main/public/images/ssu-demo-screenshot.png" alt="drawing" width="300"/> <hr> ## Xero Account Requirements - Create a [free Xero user account](https://www.xero.com/us/signup/api/) - Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application - Copy the credentials from your API app and store them using a secure ENV variable strategy - Decide the [neccesary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality # Installation To install this SDK in your project: ``` npm install xero-node ``` --- ## Configuration ```js import { XeroClient } from 'xero-node'; const xero = new XeroClient({ clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', redirectUris: [`http://localhost:${port}/callback`], scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" "), state: 'returnPage=my-sweet-dashboard', // custom params (optional) httpTimeout: 3000, // ms (optional) clockTolerance: 10 // seconds (optional) }); ``` --- ## Authentication All API requests go through Xero's OAuth2.0 gateway and require a valid `access_token` to be set on the `client` which appends the `access_token` [JWT](https://jwt.io/) to the header of each request. If you are making an API call for the first time: 1. Send the user to the Xero authorization URL ```js let consentUrl = await xero.buildConsentUrl(); res.redirect(consentUrl); ``` 2. The user will authorize your application and be sent to your `redirect_uri` ```js process.env.REDIRECT_URI => /callback?code=xyz123 ``` 3. You exchange the temporary `code` for a valid `token_set` ```js const tokenSet = await xero.apiCallback(req.url); // save the tokenSet ``` It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK `client`, and refresh the `access_token` prior to making API calls. ### Token Set | key | value | description | | --- | --- | --- | | id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | | access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | | expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | | refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | | scope: | ["email", "profile", "openid", "accounting.settings", "accounting.transactions", "offline_access"] | The Xero permissions that are embedded in the `access_token` | Example Token Set JSON: ``` { "id_token": "xxx.yyy.zz", "access_token": "xxx.yyy.zzz", "expires_in": 1800, "token_type": "Bearer", "refresh_token": "xxxxxxxxx", "scope": ["email", "profile", "openid", "accounting.settings", "accounting.transactions", "offline_access"] } ``` --- ## Custom Connections Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth2.0's [`client_credentials`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. To use this SDK with a `Custom Connection`: ```js import { XeroClient } from 'xero-node'; const xero = new XeroClient({ clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', grantType: 'client_credentials' }); const tokenSet = await xero.getClientCredentialsToken(); // save the tokenSet const invoices = await xero.accountingApi.getInvoices(''); ``` Because Custom Connections are only valid for a single organisation you don't need to pass the `xero-tenant-id` as the first parameter to every method, or more specifically for this SDK `xeroTenantId` can be an empty string. > Becuase the SDK is generated from the OpenAPI spec the parameter remains which requires you to pass an empty string to use this SDK with a Custom Connection. --- ## App Store Subscriptions If you are implementing subscriptions to participate in Xero's App Store you will need to setup [App Store subscriptions](https://developer.xero.com/documentation/xero-app-store/app-partner-guides/xero-app-store-subscriptions) endpoints. When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API. With your app credentials you can create a client via `client_credentials` grant_type with the `marketplace.billing` scope. This unique access_token will allow you to query any functions in `appStoreApi`. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process. ```ts // => /post-purchase-url const xeroAppStoreClient = new XeroClient({ clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, grantType: 'client_credentials', scopes: ['marketplace.billing'] }); try { await xeroAppStoreClient.getClientCredentialsToken() } catch(e) { console.log('ERROR: ', e) } const subscriptionRequest = await xeroAppStoreClient.appStoreApi.getSubscription(subscripionId) console.log(subscriptionRequest.body) { currentPeriodEnd: 2021-09-02T20:08:58.772Z, endDate: null, id: '03bc74f2-1237-4477-b782-2dfb1a6d8b21', organisationId: '79e8b2e5-c63d-4dce-888f-e0f3e9eac647', plans: [ { id: '6abc26f3-9390-4194-8b25-ce8b9942fda9', name: 'Small', status: 'ACTIVE', subscriptionItems: [ endDate: null, id: '834cff4c-b753-4de2-9e7a-3451e14fa17a', price: { id: '2310de92-c7c0-4bcb-b972-fb7612177bc7', amount: 0.1, currency: 'NZD' }, product: Product { id: '9586421f-7325-4493-bac9-d93be06a6a38', name: '', type: 'FIXED' }, startDate: 2021-08-02T20:08:58.772Z, testMode: true ] } ], startDate: 2021-08-02T20:08:58.772Z, status: 'ACTIVE', testMode: true } ``` You should use the subscription data to provision user access/permissions to your application. ### App Store Subscription Webhooks In additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled. Refer to Xero's documenation to learn more about setting up and receiving webhooks or review [this blogpost](https://devblog.xero.com/keeping-your-integration-in-sync-implementing-xero-webhooks-using-node-express-and-ngrok-6d2976baac6d) explaing webhooks using xero-node sdk. > https://developer.xero.com/documentation/guides/webhooks/overview/ --- ## API Clients You can access the different API sets and their available methods through the following: ```js const xero = new XeroClient({ clientId: 'YOUR_CLIENT_ID', // required clientSecret: 'YOUR_CLIENT_SECRET', // required redirectUris: [`http://localhost:${port}/callback`], // not used for client_credentials auth flow grantType: 'client_credentials', // only used for client_credentials auth flow scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" "), // not used for client_credentials auth flow state: 'returnPage=my-sweet-dashboard', // custom params (optional), not used for client_credentials auth flow httpTimeout: 3000, // ms (optional) clockTolerance: 10 // seconds (optional) }); xero.accountingApi xero.assetApi xero.projectApi xero.filesApi xero.payrollAUApi xero.payrollNZApi xero.payrollUKApi ``` --- ## Helper Methods Once you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a new `client` and refresh the token set. There are two ways to refresh a token ```js // you can refresh the token using the fully initialized client leveraging openid-client import { XeroClient } from 'xero-node'; const xero = new XeroClient({ clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', redirectUris: [`http://localhost:${port}/callback`], scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" ") }); await xero.initialize(); const tokenSet = getTokenSetFromDatabase(userId); // example function name await xero.setTokenSet(tokenSet); if(tokenSet.expired()){ const validTokenSet = await xero.refreshToken(); // save the new tokenset } ``` ```js // or if you already generated a tokenSet and have a valid (< 60 days refresh token), // you can initialize an empty client and refresh by passing the client, secret, and refresh_token import { XeroClient } from 'xero-node'; const tokenSet = getTokenSetFromDatabase(userId); // example function name if(tokenSet.expired()){ const xero = new XeroClient(); const validTokenSet = await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token) // save the new tokenset } ``` A full list of the SDK client's methods: | method | description | | --- | --- | | client.`initialize` | Initializes the Xero Client with the provided configuration | | client.`buildConsentUrl` | Returns a url concatenated from the provided redirect uri, scope, and the issuer ( Xero identity authorize url) | | client.`apiCallback`(callbackUrl) | Leverages openid-client library to exchange temporary auth code for token set | | client.`disconnect`(connectionId) | Removes an individual tenant connection by connection ID | | client.`readTokenSet` | Returns token set currently set on the Xero Client | | client.`setTokenSet`(tokenSet) | Sets a specified token set on the Xero Client | | client.`refreshToken` | Leverages openid-client library to refresh token set currently set on the Xero Client and returns updated token set | | client.`revokeToken` | Revokes a users refresh token and removes all their connections to your app | | client.`formatMsDate`(dateString) | Takes a date string and returns it formatted as an MS Date | | client.`refreshWithRefreshToken`(clientId, clientSecret, refreshToken) | Refresh a token set without leveraging openid-client | | client.`getClientCredentialsToken` | Get a token set without user intervention via the client credentials grant type for custom connections only | | client.`updateTenants`(fullOrgDetails: boolean = true) | GET request to the /connections endpoint. Accepts a boolean to indicate whether or not to also make a GET request to the /organisations endpoint and map full org data to each connection object prior to returning the array of connections | --- ## Usage Examples ### Accounting API ```js import { XeroClient, HistoryRecords, Invoice } from 'xero-node'; const xero = new XeroClient({ clientId: 'YOUR_CLIENT_ID', clientSecret: 'YOUR_CLIENT_SECRET', redirectUris: [`http://localhost:${port}/callback`], scopes: 'openid profile email accounting.settings accounting.transactions offline_access'.split(" ") }); await xero.initialize(); const tokenSet = getTokenSetFromDatabase(userId); // example function name await xero.setTokenSet(tokenSet); if(tokenSet.expired()){ const validTokenSet = await xero.refreshToken(); // save the new tokenset } await xero.updateTenants(); const activeTenantId = xero.tenants[0].tenantId; // GET all Accounts const getAccountsResponse = await xero.accountingApi.getAccounts(activeTenantId); const accountId = getAccountsResponse.body.accounts[0].accountID // GET one Account by ID const getAccountResponse = await xero.accountingApi.getAccount(activeTenantId, accountId); // CREATE an Invoice const invoices = { invoices: [ { type: Invoice.TypeEnum.ACCREC, contact: { contactID: contactId }, lineItems: [ { description: "Acme Tires", quantity: 2.0, unitAmount: 20.0, accountCode: "500", taxType: "NONE", lineAmount: 40.0 } ], date: "2019-03-11", dueDate: "2018-12-10", reference: "Website Design", status: Invoice.StatusEnum.AUTHORISED } ] }; const createdInvoicesResponse = await xero.accountingApi.createInvoices(activeTenantId, invoices) const invoiceId = createdInvoicesResponse.body.invoices[0].invoiceID; // CREATE a History Record const historyRecords: HistoryRecords = { historyRecords: [ { details: "This is a history record" } ] }; const createdInvoiceHistoryResponse = await xero.accountingApi.createInvoiceHistory(activeTenantId, invoiceId, historyRecords); // CREATE Attachment const filename = "xero-dev.png"; const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png"); const readStream = fs.createReadStream(pathToUpload); const contentType = mime.lookup(filename); const accountAttachmentsResponse = await xero.accountingApi.createInvoiceAttachmentByFileName(activeTenantId, invoiceId, filename, readStream, { headers: { 'Content-Type': contentType } }); ``` --- ## SDK conventions ### Querying & Filtering ```js const activeTenantId = 'XERO_TENANT_ID'; const ifModifiedSince: Date = new Date("2020-02-06T12:17:43.202-08:00"); const where = 'Status=="AUTHORISED" AND Type=="SPEND"'; const order = 'Reference ASC'; const page = 1; const unitdp = 4; const response = await xero.accountingApi.getBankTransactions(activeTenantId, ifModifiedSince, where, order, page, unitdp); ``` Note that you should set the query param to undefined instead of null if you wish to ignore a specific filter. ```js const purchaseOrders = xero.accountingApi.getPurchaseOrders(tenant.tenantId, null, null, '2021-01-01', '2021-04-25', null, 1); // http://api-oauth2.xero.com/api.xro/2.0/PurchaseOrders?Status=&DateFrom=2008-01-01&DateTo=2021-04-25&order=&page=1 // "Status=&" is breaking the above query // purchaseOrders will be an empty array const purchaseOrders = xero.accountingApi.getPurchaseOrders(tenant.tenantId, undefined, undefined, '2021-01-01', '2021-04-25', undefined, 1); // http://api-oauth2.xero.com/api.xro/2.0/PurchaseOrders?DateFrom=2008-01-01&DateTo=2021-04-25&order=&page=1 // params are omitted // purchaseOrders array will have results now ``` --- ## Security This repo leverages a certified OA2 and OIDC library called openid-client. For a deeper dive the repo's functionality, check out them directly https://github.com/panva/node-openid-client. ### Preventing CSRF Using Xero-Node When xero.buildConsentUrl is called we call openid-client authorizationUrl method, passing redirect_uri, scope, and state (if present) as arguments and returns a formatted url string made up from the given config. The user is then directed to the consentUrl to begin the auth process with Xero. When the auth process is complete Xero redirects the user to the specified callback route and passes along params including the state if it was initially provided. At this point openid-client takes over verifying params.state and check.state match if provided. If the state does not match the initial user's, the openid-client library throws an error: ``` RPError: state mismatch, expected user=1234, got: user=666 ``` ### JWT Verification Using Xero-Node JWT verification of both the `access_token` and `id_token` are baked into the openid-client library we leverage. When `xero.apiCallback` is called, openid-client `validateJARM` is triggered which also invokes `validateJWT`. If openid-client fails to validate the JWT signature it will throw an error. --- ## Contributing PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. Please add tests for net new functionality and ensure existing test suite passes all tests. ``` npm test ``` ### Versioning We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know. ## Participating in Xero’s developer community This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/xero-node/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it * If you raise an issue then please make sure to fill out the github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/xero-node/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/xero-node/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/).

Accounting API Tools
253 Github Stars
xero-python
Open Source

xero-python

# xero-python [![PyPI version](https://badge.fury.io/py/xero-python.svg)](https://badge.fury.io/py/xero-python) [![Github forks](https://img.shields.io/github/forks/XeroAPI/xero-python.svg)](https://github.com/XeroAPI/xero-python/network) [![Github stars](https://img.shields.io/github/stars/XeroAPI/xero-python.svg)](https://github.com/XeroAPI/xero-python/stargazers) [![Downloads](https://pepy.tech/badge/xero-python)](https://pepy.tech/project/xero-python) The xero-python SDK makes it easy for developers to access Xero's APIs in their python code, and build robust applications and software using small business & general ledger accounting data. # Table of Contents - [API Client documentation](#api-client-documentation) - [Sample Applications](#sample-applications) - [Xero Account Requirements](#xero-account-requirements) - [Installation](#installation) - [Configuration](#configuration) - [Authentication](#authentication) - [Custom Connections](#custom-connections) - [App Store Subscriptions](#app-store-subscriptions) - [API Clients](#api-clients) - [Helper Methods](#helper-methods) - [Usage Examples](#usage-examples) - [SDK conventions](#sdk-conventions) - [Running Test(s) in Local](#running-tests-in-local) - [Participating in Xero’s developer community](#participating-in-xeros-developer-community) - [Contributing](#contributing) <hr> ## API Client documentation This SDK supports full method coverage for the following Xero API sets: | API Set | Description | | --- | --- | | [`Accounting`](https://xeroapi.github.io/xero-python/v1/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* | [Assets](https://xeroapi.github.io/xero-python/v1/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | | [Files](https://xeroapi.github.io/xero-python/v1/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | | [Projects](https://xeroapi.github.io/xero-python/v1/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | | [Payroll (AU)](https://xeroapi.github.io/xero-python/v1/payroll-au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (UK)](https://xeroapi.github.io/xero-python/v1/payroll-uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/xero-python/v1/payroll-nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | <img src="https://i.imgur.com/feMhCtI.png" alt="drawing" width="350"/> <hr> ## Sample Applications Sample apps can get you started quickly with simple auth flows and advanced usage examples. | Sample App | Description | Screenshot | | --- | --- | --- | | [`starter-app`](https://github.com/XeroAPI/xero-python-oauth2-starter) | Basic getting started code samples | <img src="https://i.imgur.com/gJA93yT.png" alt="drawing" width="200"/> | [`full-app`](https://github.com/XeroAPI/xero-python-oauth2-app) | Complete app with more complex examples | <img src="https://i.imgur.com/1YRqMmc.png" alt="drawing" width="500"/> | [`custom-connections-starter`](https://github.com/XeroAPI/xero-python-custom-connections-starter) | Basic app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) for building M2M integrations to a single org | <img src="https://i.imgur.com/uOlaWg8.png" alt="drawing" width="300"/> <hr> ## Xero Account Requirements - Create a [free Xero user account](https://www.xero.com/us/signup/api/) - Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application - Copy the credentials from your API app and store them using a secure ENV variable strategy - Decide the [neccesary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality # Installation To install this SDK in your project: ``` pip install xero-python ``` --- ## Configuration ```python # -*- coding: utf-8 -*- import os from functools import wraps from io import BytesIO from logging.config import dictConfig from flask import Flask, session from flask_oauthlib.contrib.client import OAuth, OAuth2Application from flask_session import Session from xero_python.accounting import AccountingApi from xero_python.assets import AssetApi from xero_python.project import ProjectApi from xero_python.payrollau import PayrollAuApi from xero_python.payrolluk import PayrollUkApi from xero_python.payrollnz import PayrollNzApi from xero_python.file import FilesApi from xero_python.api_client import ApiClient, serialize from xero_python.api_client.configuration import Configuration from xero_python.api_client.oauth2 import OAuth2Token from xero_python.exceptions import AccountingBadRequestException, PayrollUkBadRequestException from xero_python.identity import IdentityApi from xero_python.utils import getvalue import logging_settings from utils import jsonify, serialize_model dictConfig(logging_settings.default_settings) # configure main flask application app = Flask(__name__) app.config.from_object("default_settings") app.config.from_pyfile("config.py", silent=True) if app.config["ENV"] != "production": # allow oauth2 loop to run over http (used for local testing only) os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" # configure persistent session cache Session(app) # configure flask-oauthlib application oauth = OAuth(app) xero = oauth.remote_app( name="xero", version="2", client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"], endpoint_url="https://api.xero.com/", authorization_url="https://login.xero.com/identity/connect/authorize", access_token_url="https://identity.xero.com/connect/token", refresh_token_url="https://identity.xero.com/connect/token", scope="offline_access openid profile email accounting.transactions " "accounting.transactions.read accounting.reports.read " "accounting.journals.read accounting.settings accounting.settings.read " "accounting.contacts accounting.contacts.read accounting.attachments " "accounting.attachments.read assets projects " "files " "payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings", ) # type: OAuth2Application # configure xero-python sdk client api_client = ApiClient( Configuration( debug=app.config["DEBUG"], oauth2_token=OAuth2Token( client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] ), ), pool_threads=1, ) # configure token persistence and exchange point between flask-oauthlib and xero-python @xero.tokengetter @api_client.oauth2_token_getter def obtain_xero_oauth2_token(): return session.get("token") @xero.tokensaver @api_client.oauth2_token_saver def store_xero_oauth2_token(token): session["token"] = token session.modified = True ``` --- ## Authentication All API requests go through Xero's OAuth 2.0 gateway and require a valid `access_token` to be set on the `client` which appends the `access_token` [JWT](https://jwt.io/) to the header of each request. If you are making an API call for the first time: 1. Send the user to the Xero authorization URL ```python @app.route("/login") def login(): redirect_url = url_for("oauth_callback", _external=True) session["state"] = app.config["STATE"] try: response = xero.authorize(callback_uri=redirect_url, state=session["state"]) except Exception as e: print(e) raise return response ``` 2. The user will authorize your application and be sent to your `redirect_uri`. This is when and where to check that the returned "state" param matches that which was previously defined. If the "state" params match, calling the oauth library's `authorized_response()` method will swap the temporary auth code for an access token which you can store and use for subsequent API calls. ```python @app.route("/callback") def oauth_callback(): if request.args.get("state") != session["state"]: return "Error, state doesn't match, no token for you." try: response = xero.authorized_response() except Exception as e: print(e) raise if response is None or response.get("access_token") is None: return "Access denied: response=%s" % response store_xero_oauth2_token(response) return redirect(url_for("index", _external=True)) ``` 3. Call the Xero API like so: ```python @app.route("/accounting_invoice_read_all") @xero_token_required def accounting_invoice_read_all(): code = get_code_snippet("INVOICES","READ_ALL") #[INVOICES:READ_ALL] xero_tenant_id = get_xero_tenant_id() accounting_api = AccountingApi(api_client) try: invoices_read = accounting_api.get_invoices( xero_tenant_id ) except AccountingBadRequestException as exception: output = "Error: " + exception.reason json = jsonify(exception.error_data) else: output = "Total invoices found: {}.".format(len(invoices_read.invoices) ) json = serialize_model(invoices_read) #[/INVOICES:READ_ALL] return render_template( "output.html", title="Invoices",code=code, output=output, json=json, len = 0, set="accounting", endpoint="invoice", action="read_all" ) ``` It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK `client`, and refresh the `access_token` prior to making API calls. ### Token Set | key | value | description | | --- | --- | --- | | id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | | access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | | expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | | refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | | scope: | ["email", "profile", "openid", "accounting.transactions", "offline_access"] | The Xero permissions that are embedded in the `access_token` | Example Token Set JSON: ``` { "id_token": "xxx.yyy.zz", "access_token": "xxx.yyy.zzz", "expires_in": 1800, "token_type": "Bearer", "refresh_token": "xxxxxxxxx", "scope": ["email", "profile", "openid", "accounting.transactions", "offline_access"] } ``` --- ## Custom Connections Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth 2.0's [`client_credentials`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. To use this SDK with a Custom Connections: ```python # -*- coding: utf-8 -*- import os from functools import wraps from io import BytesIO from logging.config import dictConfig from flask import Flask, url_for, render_template, session, redirect, json, send_file from flask_session import Session from xero_python.accounting import AccountingApi, ContactPerson, Contact, Contacts from xero_python.api_client import ApiClient, serialize from xero_python.api_client.configuration import Configuration from xero_python.api_client.oauth2 import OAuth2Token from xero_python.exceptions import AccountingBadRequestException from xero_python.identity import IdentityApi from xero_python.utils import getvalue import logging_settings from utils import jsonify, serialize_model dictConfig(logging_settings.default_settings) # configure main flask application app = Flask(__name__) app.config.from_object("default_settings") app.config.from_pyfile("config.py", silent=True) # configure persistent session cache Session(app) # configure xero-python sdk client api_client = ApiClient( Configuration( debug=app.config["DEBUG"], oauth2_token=OAuth2Token( client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] ), ), pool_threads=1, ) # configure token persistence and exchange point between app session and xero-python @api_client.oauth2_token_getter def obtain_xero_oauth2_token(): return session.get("token") @api_client.oauth2_token_saver def store_xero_oauth2_token(token): session["token"] = token session.modified = True @app.route("/get_token") def get_token(): try: # no user auth flow, no exchanging temp code for token xero_token = api_client.get_client_credentials_token() except Exception as e: print(e) raise # todo validate state value if xero_token is None or xero_token.get("access_token") is None: return "Access denied: response=%s" % xero_token store_xero_oauth2_token(xero_token) return redirect(url_for("index", _external=True)) @app.route("/accounting_invoice_read_all") @xero_token_required def accounting_invoice_read_all(): code = get_code_snippet("INVOICES","READ_ALL") #[INVOICES:READ_ALL] accounting_api = AccountingApi(api_client) try: invoices_read = accounting_api.get_invoices('') except AccountingBadRequestException as exception: output = "Error: " + exception.reason json = jsonify(exception.error_data) else: output = "Total invoices found: {}.".format(len(invoices_read.invoices) ) json = serialize_model(invoices_read) #[/INVOICES:READ_ALL] return render_template( "output.html", title="Invoices",code=code, output=output, json=json, len = 0, set="accounting", endpoint="invoice", action="read_all" ) ``` Because Custom Connections are only valid for a single organisation you don't need to pass the xero-tenant-id as the first parameter to every method, or more specifically for this SDK xeroTenantId can be an empty string. > Because the SDK is generated from the OpenAPI spec the parameter remains. For now you are required to pass an empty string to use this SDK with a Custom Connection. --- ## App Store Subscriptions If you are implementing subscriptions to participate in Xero's App Store you will need to setup [App Store subscriptions](https://developer.xero.com/documentation/guides/how-to-guides/xero-app-store-referrals/) endpoints. When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API. With your app credentials you can create a client via `client_credentials` grant_type with the `marketplace.billing` scope. This unique access_token will allow you to query any functions in `AppStoreApi`. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process. ```python # configure xero-python sdk client api_client = ApiClient( Configuration( debug=app.config["DEBUG"], oauth2_token=OAuth2Token( client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] ), ), pool_threads=1, ) try: # pass True for app_store_billing - defaults to False if no value provided xero_token = api_client.get_client_credentials_token(True) except Exception as e: print(e) raise app_store_api = AppStoreApi(api_client) subscription = app_store_api.get_subscription(subscription_id) print(subscription) { 'current_period_end': datetime.datetime(2021, 9, 2, 14, 8, 58, 772536, tzinfo=tzutc()), 'end_date': None, 'id': '03bc74f2-1237-4477-b782-2dfb1a6d8b21', 'organisation_id': '79e8b2e5-c63d-4dce-888f-e0f3e9eac647', 'plans':[ { 'id': '6abc26f3-9390-4194-8b25-ce8b9942fda9', 'name': 'Small', 'status': 'ACTIVE', 'subscription_items': [ { 'end_date': None, 'id': '834cff4c-b753-4de2-9e7a-3451e14fa17a', 'price': { 'amount': Decimal('0.1000'), 'currency': 'NZD', 'id': '2310de92-c7c0-4bcb-b972-fb7612177bc7' }, 'product': { 'id': '9586421f-7325-4493-bac9-d93be06a6a38', 'name': '', 'type': 'FIXED', 'seat_unit': None }, 'start_date': datetime.datetime(2021, 8, 2, 14, 8, 58, 772536, tzinfo=tzutc()), 'test_mode': True } ] } ], 'start_date': datetime.datetime(2021, 8, 2, 14, 8, 58, 772536, tzinfo=tzutc()), 'status': 'ACTIVE', 'test_mode': True } ``` You should use the subscription data to provision user access/permissions to your application. ### App Store Subscription Webhooks In additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled. Refer to Xero's documenation to learn more about setting up and receiving webhooks. > https://developer.xero.com/documentation/guides/webhooks/overview/ --- ## API Clients You can access the different API sets and their available methods through the following: ```python accounting_api = AccountingApi(api_client) read_accounts = accounting_api.get_accounts(xero_tenant_id) asset_api = AssetApi(api_client) read_assets = asset_api.get_assets(xero_tenant_id) # ... all the API sets follow the same pattern ``` --- ## Helper Methods Once you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a new `client` and refresh the token set. ```python # configure xero-python sdk client api_client = ApiClient( Configuration( debug=app.config["DEBUG"], oauth2_token=OAuth2Token( client_id=app.config["CLIENT_ID"], client_secret=app.config["CLIENT_SECRET"] ), ), pool_threads=1, ) # configure token persistence and exchange point between app session and xero-python @api_client.oauth2_token_getter def obtain_xero_oauth2_token(): return session.get("token") @api_client.oauth2_token_saver def store_xero_oauth2_token(token): session["token"] = token session.modified = True # get existing token set token_set = get_token_set_from_database(user_id); // example function name # set token set to the api client store_xero_oauth2_token(token_set) # refresh token set on the api client api_client.refresh_oauth2_token() # call the Xero API accounting_api = AccountingApi(api_client) read_accounts = accounting_api.get_accounts(xero_tenant_id) ``` A full list of the SDK client's methods: | method | description | params | returns | --- | --- | --- | --- | | api_client.`oauth2_token_saver` | A decorator to register a callback function for saving refreshed token while the old token has expired | token_saver: the callback function accepting `token` argument | token_saver to allow this method be used as decorator | | api_client.`oauth2_token_getter` | A decorator to register a callback function for getting oauth2 token | token_getter: the callback function returning oauth2 token dictionary | token_getter to allow this method be used as decorator | | api_client.`revoke_oauth2_token` | Revokes a users refresh token and removes all their connections to your app | N/A | empty OAuth2 token | | api_client.`refresh_oauth2_token` | Refreshes OAuth2 token set | N/A | new token set | | api_client.`set_oauth2_token` | Sets OAuth2 token directly on the client | dict token: standard token dictionary | N/A | | api_client.`get_oauth2_token` | Get OAuth2 token dictionary | N/A | dict | --- ## Usage Examples ### Accounting API ```python from xero_python.accounting import AccountingApi from xero_python.utils import getvalue accounting_api = AccountingApi(api_client) # Get Accounts read_accounts = accounting_api.get_accounts(xero_tenant_id) account_id = getvalue(read_accounts, "accounts.0.account_id", "") # Get Account by ID read_one_account = accounting_api.get_account(xero_tenant_id, account_id) # Create Invoice # get contact read_contacts = accounting_api.get_contacts(xero_tenant_id) contact_id = getvalue(read_contacts, "contacts.0.contact_id", "") # get account where = "Type==\"SALES\"&&Status==\"ACTIVE\"" read_accounts = accounting_api.get_accounts( xero_tenant_id, where=where ) account_id = getvalue(read_accounts, "accounts.0.account_id", "") # build Invoices contact = Contact( contact_id=contact_id ) line_item = LineItem( account_code=account_id, description= "Consulting", quantity=1.0, unit_amount=10.0, ) invoice = Invoice( line_items=[line_item], contact=contact, due_date= dateutil.parser.parse("2020-09-03T00:00:00Z"), date= dateutil.parser.parse("2020-07-03T00:00:00Z"), type="ACCREC" ) invoices = Invoices(invoices=[invoice]) created_invoices = accounting_api.create_invoices(xero_tenant_id, invoices=invoices) invoice_id = getvalue(read_invoices, "invoices.0.invoice_id", "") # Create Attachment include_online = True file_name = "helo-heros.jpg" path_to_upload = Path(__file__).resolve().parent.joinpath(file_name) open_file = open(path_to_upload, 'rb') body = open_file.read() content_type = mimetypes.MimeTypes().guess_type(file_name)[0] created_invoice_attachments_by_file_name = accounting_api.create_invoice_attachment_by_file_name( xero_tenant_id, invoice_id, file_name, body, include_online, ) ``` --- ## SDK conventions --- ## Running Test(s) in Local For Running Test cases PRISM Mock Server needs to be started in the local machine. Steps to Run Test(s) * Install PRISM from npm using the command: **npm install -g @stoplight/prism-cli** * Verify Installation: **prism --version** * Navigate to **tests--> utils--> ** folder in the terminal * Execute the script **./start-prism.sh** * This will start the PRISM Server in Local * Run **pytest** to run the Python test cases. ### Querying & Filtering Describe the support for query options and filtering ```python # configure api_client for use with xero-python sdk client api_client = ApiClient( Configuration( debug=false, oauth2_token=OAuth2Token( client_id="YOUR_CLIENT_ID", client_secret="YOUR_CLIENT_SECRET" ), ), pool_threads=1, ) api_client.set_oauth2_token("YOUR_ACCESS_TOKEN") def accounting_get_invoices(): api_instance = AccountingApi(api_client) xero_tenant_id = 'YOUR_XERO_TENANT_ID' if_modified_since = dateutil.parser.parse("2020-02-06T12:17:43.202-08:00") where = 'Status=="DRAFT"' order = 'InvoiceNumber ASC' ids = ["00000000-0000-0000-0000-000000000000"] invoice_numbers = ["INV-001", "INV-002"] contact_ids = ["00000000-0000-0000-0000-000000000000"] statuses = ["DRAFT", "SUBMITTED"] include_archived = 'true' created_by_my_app = 'false' summary_only = 'true' api_response = api_instance.get_invoices( xero_tenant_id, if_modified_since, where, order, ids, invoice_numbers, contact_ids, statuses, page, include_archived, created_by_my_app, unitdp, summary_only ) ``` --- ## Participating in Xero’s developer community This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/xero-python/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it * If you raise an issue then please make sure to fill out the Github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/xero-python/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/xero-python/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/). ### Contributing PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. ### Versioning We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know.

Accounting API Tools
184 Github Stars
xero-php-oauth2
Open Source

xero-php-oauth2

# xero-php-oauth2 [![Latest Stable Version](http://poser.pugx.org/xeroapi/xero-php-oauth2/v)](https://packagist.org/packages/xeroapi/xero-php-oauth2) [![Total Downloads](http://poser.pugx.org/xeroapi/xero-php-oauth2/downloads)](https://packagist.org/packages/xeroapi/xero-php-oauth2) [![Github forks](https://img.shields.io/github/forks/XeroAPI/xero-php-oauth2.svg)](https://github.com/XeroAPI/xero-php-oauth2/network) [![Github stars](https://img.shields.io/github/stars/XeroAPI/xero-php-oauth2.svg)](https://github.com/XeroAPI/xero-php-oauth2/stargazers) [![License](http://poser.pugx.org/xeroapi/xero-php-oauth2/license)](https://packagist.org/packages/xeroapi/xero-php-oauth2) The `xero-php-oauth2` SDK makes it easy for developers to access Xero's APIs in their **PHP** code, and build robust applications and software using small business & general ledger accounting data. # Table of Contents - [API Client documentation](#api-client-documentation) - [Sample Applications](#sample-applications) - [Xero Account Requirements](#xero-account-requirements) - [Installation](#installation) - [Authentication](#authentication) - [Configuration](#configuration) - [Custom Connections](#custom-connections) - [API Clients](#api-clients) - [SDK conventions](#sdk-conventions) - [Contributing](#contributing) <hr> ## API Client documentation This SDK supports full method coverage for the following Xero API sets: | API Set | Description | | --- | --- | | [`Accounting methods`](https://xeroapi.github.io/xero-php-oauth2/docs/v2/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* | [Assets](https://xeroapi.github.io/xero-php-oauth2/docs/v2/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | | [Files](https://xeroapi.github.io/xero-php-oauth2/docs/v2/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | | [Projects](https://xeroapi.github.io/xero-php-oauth2/docs/v2/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | | [Payroll (AU)](https://xeroapi.github.io/xero-php-oauth2/docs/v2/payroll_au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (UK)](https://xeroapi.github.io/xero-php-oauth2/docs/v2/payroll_uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/xero-php-oauth2/docs/v2/payroll_nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | | [Object Model Documentation](https://github.com/XeroAPI/xero-php-oauth2/tree/master/doc) | Additional format of method docs, as well as full object model and field definition documentation can be found by clicking through the desired file paths | <img src="https://i.imgur.com/7vONVOR.png" alt="drawing" width="350"/> <hr> ## Sample Applications Sample apps can get you started quickly with simple auth flows and advanced usage examples. | Sample App | Description | | --- | --- | | [`starter-app`](https://github.com/XeroAPI/Xero-php-oauth2-starter) | Basic getting started code samples | [`full-app`](https://github.com/XeroAPI/Xero-php-oauth2-app) | Complete app with more complex examples | [`custom-connections-starter`](https://github.com/XeroAPI/xero-php-oauth2-custom-connections-starter) | Basic app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) for building M2M integrations to a single orgs <hr> ## Xero Account Requirements - Create a [free Xero user account](https://www.xero.com/us/signup/api/) - Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application - Copy the credentials from your API app and store them using a secure ENV variable strategy - Decide the [neccesary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality # Installation To install this SDK in your project we recommend using [Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-macos) (For OSX we recommend using [Homebrew](https://formulae.brew.sh/formula/composer)). All third party libraries dependencies managed with Composer and the SDK requires `PHP 8.1` and later. To install the bindings via [Composer](http://getcomposer.org/), and add the xero-php-oauth2 sdk to your `composer.json` and navigate to where your composer.json file is and run the command: ``` composer require xeroapi/xero-php-oauth2 ``` If no `composer.json` file exists, create one by running the following command. You'll need [Composer](http://getcomposer.org/) installed. ``` composer init ``` ### Configure PHPStorm We've received feedback that PHPStorm IDE default file size is too small to load the AccountingApi class. "PHPStorm seems unable to resolve the XeroAPI\XeroPHP\Api\AccountingApi class. It just shows Undefined class 'AccountingApi' and therefore can't autocomplete any of the methods etc." To fix this, add the following to the idea.properties file to increase this limit to 5000 kilobytes idea.max.intellisense.filesize=5000 Instructions here for [configuring PHPStorm](https://www.jetbrains.com/help/phpstorm/tuning-the-ide.html#configure-platform-properties) platform properties on Mac/Windows/Linux ### Laravel Xero doesn't offer support on how to use of our SDKs in different frameworks, etc. We had a recommendation by Matt @hailwood in our developer community. They integrates xero-php-oauth2 and Laravel using the following package. * https://github.com/webfox/laravel-xero-oauth2 * https://packagist.org/packages/webfox/laravel-xero-oauth2 --- ## Authentication Below is starter code with the authorization flow. You can use the code below by creating 4 separate PHP files and securely replacing your **__CLIENT_ID__, __CLIENT_SECRET__ and __REDIRECT_URI__** All API requests go through Xero's OAuth2.0 gateway and require a valid `access_token` to be set on the `client` which appends the `access_token` [JWT](https://jwt.io/) to the header of each request. If you are making an API call for the first time the code below shows the auth flow using 4 separate PHP files and will work with these secure credentials replaced with your own: * __CLIENT_ID__ * __CLIENT_SECRET__ * __REDIRECT_URI__ > You can also see usage of the sdk in our [sample app](https://github.com/XeroAPI/xero-php-oauth2-starter). ### Important The RedirectURI (ex. http://localhost:8888/pathToApp/callback.php) in your code needs to point to the callback.php file and match the RedirectURI you set when creating your Xero app. 1. Point your browser to authorization.php, you'll be redirected to Xero where you'll login and select a Xero org to authorize. We recommend the **Demo Company** org, since this code can modify data in the org you connect to. 2. Once complete, you'll be returned to your app to the redirect URI which should point to the callback.php. 3. In callback.php, you'll obtain an access token which we'll use in authorizedResource.php to create, read, update and delete information in the connected Xero org. ### authorization.php ```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 <?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 <?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("[email protected]") ->setIncludeInEmails(true); $arr_persons = []; array_push($arr_persons, $person); $contact = new XeroAPI\XeroPHP\Models\Accounting\Contact; $contact->setName('FooBar') ->setFirstName("Foo") ->setLastName("Bar") ->setEmailAddress("[email protected]") ->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("[email protected]"); // 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 <?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; } } } ?> ``` --- ## Configuration The `storage.php` is a simple recommendation. In your own app you should be securely persisting the token set data in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API you will need to access the previously generated token set, initialize it on the SDK `client`, and refresh the `access_token` prior to making API calls. ### Token Set | key | value | description | | --- | --- | --- | | id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | | access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | | expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | | refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | | scope: | "email profile openid accounting.transactions offline_access" | The Xero permissions that are embedded in the `access_token` | --- ## Custom Connections Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth2.0's [`client_credentis`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. We also have a [starter application](https://github.com/XeroAPI/Xero-php-oauth2-custom-connections-starter) for more code samples of this auth flow in PHP. To use this SDK with a Custom Connections: ```php $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()); } ``` Because Custom Connections are only valid for a single organisation you don't need to pass the `xero-tenant-id` as the first parameter to every method, or more specifically for this SDK `xeroTenantId` can be an empty string. --- ## App Store Subscriptions If you are implementing subscriptions to participate in Xero's App Store you will need to setup [App Store subscriptions](https://developer.xero.com/documentation/guides/how-to-guides/xero-app-store-referrals/) endpoints. When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API. With your app credentials you can create a client via `client_credentials` grant_type with the `marketplace.billing` scope. This unique access_token will allow you to query any functions in `appStoreApi`. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process. ```php // => /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); ``` You should use this subscription data to provision user access/permissions to your application. ### App Store Subscription Webhooks In additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled. Refer to Xero's documenation to learn more about setting up and receiving webhooks. > https://developer.xero.com/documentation/guides/webhooks/overview/ ## API Clients You can access the different API sets and their available methods through the following API sets: * AccountingApi * AssetApi * ProjectApi * FilesApi * PayrollAuApi * PayrollNzApi * PayrollUkApi * AppStoreApi ```php <?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; } ?> ``` Or for the Assets API: ```php <?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; } ?> ``` > Full method docs can be browsed here: https://xeroapi.github.io/xero-php-oauth2/docs/v2/accounting/index.html --- ## SDK conventions ### Accessing HTTP Headers Every function has with it a more verbose `WithHttpInfo` option in case you need to build logic around any request headers. For example: * `getInvoices` -> `getInvoicesWithHttpInfo` * `getContacts` -> `getContactsWithHttpInfo` This will return an array of 3 elements from the HTTP request. 1. The object deserialized via the accounting object 2. The HTTP status code 3. The Response Headers ```php return [ AccountingObjectSerializer::deserialize($content, '\XeroAPI\XeroPHP\Models\Accounting\Organisations', []), $response->getStatusCode(), $response->getHeaders() ]; ``` ```php $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 decoding and Signup with Xero Looking to implement [Signup with Xero](https://developer.xero.com/documentation/oauth2/sign-in)? We've added built in decoding and verification for both Access tokens and ID token in xero-php-oauth2. Json Web Tokens (JWT) claims are pieces of information asserted about a subject. The code below shows how to securely read claims about the access token (a user authentication) and abut the id token (a user's identity & profile). ```php // 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()); ``` ### Methods to access Dates in Accounting have changed since version 2.x Both our Accounting and AU Payroll APIs use [Microsoft .NET JSON format](https://developer.xero.com/documentation/api/requests-and-responses#JSON) i.e. "\/Date(1439434356790)\/". Our other APIs use standard date formatting i.e. "2020-03-24T18:43:43.860852". Building our SDKs from OpenAPI specs with such different date formats has been challenging. For this reason, we've decided dates in MS .NET JSON format will be strings with NO date or date-time format in our OpenAPI specs. This means developers wanting to use our OpenAPI specs with code generators won't run into deserialization issues trying to handle MS .NET JSON format dates. The side effect is accounting and AU payroll models now have two getter methods. For example, getDateOfBirth() returns the string "\/Date(1439434356790)\/" while getDateOfBirthAsDate() return a standard date "2020-05-14". Since you can override methods in Java setDateOfBirth() can accept a String or a LocalDate. ```php //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)/"); ``` --- ## Contributing PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. ### Versioning We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know. ### Participating in Xero’s developer community This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/xero-ruby/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it * If you raise an issue then please make sure to fill out the github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/xero-ruby/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/xero-ruby/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/).

AI Tools Accounting
105 Github Stars
Xero-Java
Open Source

Xero-Java

# Xero-Java [![Xero-Java](https://maven-badges.herokuapp.com/maven-central/com.github.xeroapi/xero-java/badge.svg?style=plastic)](https://maven-badges.herokuapp.com/maven-central/com.github.xeroapi/xero-java) [![Github forks](https://img.shields.io/github/forks/XeroAPI/Xero-Java.svg)](https://github.com/XeroAPI/Xero-Java/network) [![Github stars](https://img.shields.io/github/stars/XeroAPI/Xero-Java.svg)](https://github.com/XeroAPI/Xero-Java/stargazers) The **Xero-Java** SDK makes it easy for developers to access Xero's APIs in their **Java** code, and build robust applications and software using small business & general ledger accounting data. # Table of Contents - [API Client documentation](#api-client-documentation) - [Sample Applications](#sample-applications) - [Xero Account Requirements](#xero-account-requirements) - [Installation](#installation) - [Authentication](#authentication) - [Custom Connections](#custom-connections) - [App Store Subscriptions](#app-store-subscriptions) - [API Clients](#api-clients) - [Usage Examples](#usage-examples) - [Running Test(s) in Local](#running-tests-in-local) - [SDK conventions](#sdk-conventions) - [Participating in Xero’s developer community](#participating-in-xeros-developoer-community) <hr> ## API Client documentation This SDK supports full method coverage for the following Xero API sets: | API Set | Description | | --- | --- | | [`Accounting`](https://xeroapi.github.io/Xero-Java/v4/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* | [Assets](https://xeroapi.github.io/Xero-Java/v4/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | | [Files](https://xeroapi.github.io/Xero-Java/v4/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | | [Finance](https://xeroapi.github.io/Xero-Java/v4/finance/index.html) | The Finance API exposes finacial functions that may help lenders gain the confidence they need to provide capital | | [Projects](https://xeroapi.github.io/Xero-Java/v4/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | | [Payroll (AU)](https://xeroapi.github.io/Xero-Java/v4/payroll_au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (UK)](https://xeroapi.github.io/Xero-Java/v4/payroll_uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/Xero-Java/v4/payroll_nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/Xero-Java/v4/bankfeeds/index.html) | The Bankfeeds API exposes Bankfeed functions - this is a restricted API - [Contact us](https://www.xero.com/partner-programs/financialweb/contact/) to get permission to use| <img src="https://i.imgur.com/cvuZGNN.png" alt="drawing" width="350"/> <hr> ## Sample Applications Sample apps can get you started quickly with simple auth flows and advanced usage examples. | Sample App | Description | | --- | --- | | [`starter-app`](https://github.com/XeroAPI/xero-java-oauth2-starter) | Basic getting started code samples | [`full-app`](https://github.com/XeroAPI/xero-java-oauth2-app) | Complete app with more examples | [`custom-connections-starter`](https://github.com/XeroAPI/xero-java-custom-connections-starter) | Basic app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) for building M2M integrations to a single org <hr> ## Xero Account Requirements - Create a [free Xero user account](https://www.xero.com/us/signup/api/) - Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application - Copy the credentials from your API app and store them using a secure ENV variable strategy - Decide the [necessary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality ## Installation Add the Xero Java SDK dependency to project via maven, gradle, sbt or other build tools can be found on [maven central](https://search.maven.org/search?q=g:com.github.xeroapi). ```xml <dependency> <groupId>com.github.xeroapi</groupId> <artifactId>xero-java</artifactId> <version>4.X.X</version> </dependency> ``` --- ## Authentication All API requests go through Xero's OAuth 2.0 gateway and require a valid `access_token` to be set on the `client` which appends the `access_token` [JWT](https://jwt.io/) to the header of each request. The code below shows how to perform the OAuth 2 authorization code flow. 1. *Authorization.java* 2. *Callback.java* 3. *TokenStorage.java* 4. *TokenRefresh.java* Create your [Xero app](https://developer.xero.com/myapps) to obtain your clientId, clientSecret and set your redirectUri. The redirectUri is your server that Xero will send a user back to once authorization is complete (aka callback url). You can add or remove resources from the scopeList for your integration. We have a [list of all available scopes](https://developer.xero.com/documentation/oauth2/scopes). Lastly, you'll generate an authorization URL and redirect the user to Xero for authorization. *Authorization.java* ```java package com.xero.example; import java.io.IOException; import java.util.ArrayList; import java.util.Random; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.DataStoreFactory; import com.google.api.client.util.store.MemoryDataStoreFactory; @WebServlet("/Authorization") public class Authorization extends HttpServlet { private static final long serialVersionUID = 1L; final String clientId = "--CLIENT-ID--"; final String clientSecret = "--CLIENT-SECRET--"; final String redirectURI = "http://localhost:8080/starter/Callback"; final String TOKEN_SERVER_URL = "https://identity.xero.com/connect/token"; final String AUTHORIZATION_SERVER_URL = "https://login.xero.com/identity/connect/authorize"; final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport(); final JsonFactory JSON_FACTORY = new JacksonFactory(); final String secretState = "secret" + new Random().nextInt(999_999); /** * @see HttpServlet#HttpServlet() */ public Authorization() { super(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ArrayList<String> scopeList = new ArrayList<String>(); scopeList.add("openid"); scopeList.add("email"); scopeList.add("profile"); scopeList.add("offline_access"); scopeList.add("accounting.settings"); scopeList.add("accounting.transactions"); scopeList.add("accounting.contacts"); scopeList.add("accounting.journals.read"); scopeList.add("accounting.reports.read"); scopeList.add("accounting.attachments"); // Save your secretState variable and compare in callback to prevent CSRF TokenStorage store = new TokenStorage(); store.saveItem(response, "state", secretState); DataStoreFactory DATA_STORE_FACTORY = new MemoryDataStoreFactory(); AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(), HTTP_TRANSPORT, JSON_FACTORY, new GenericUrl(TOKEN_SERVER_URL), new ClientParametersAuthentication(clientId, clientSecret), clientId, AUTHORIZATION_SERVER_URL) .setScopes(scopeList).setDataStoreFactory(DATA_STORE_FACTORY).build(); String url = flow.newAuthorizationUrl().setClientId(clientId).setScopes(scopeList).setState(secretState) .setRedirectUri(redirectURI).build(); response.sendRedirect(url); } } ``` After the user has selected an organisation to authorise, they will be returned to your application specified in the redirectUri. Below is an example Callback servlet. You'll get a *code* from callback url query string and use it to request you access token. An access token can be associate with one or more Xero orgs, so you'll need to call Xero's identity service (https://api.xero.com/Connections). You'll receive an array of xero-tenant-id's (that identify the organisation(s) authorized). Use both the access token and the tenant id to access resources via the API. Lastly, we save the access token, refresh token and Xero tenant id. We've mocked up a TokenStorage class for this demo. *Callback.java* ```java package com.xero.example; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.auth0.jwt.interfaces.DecodedJWT; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.DataStoreFactory; import com.google.api.client.util.store.MemoryDataStoreFactory; import com.xero.api.ApiClient; import com.xero.api.client.IdentityApi; import com.xero.models.identity.Connection; @WebServlet("/Callback") public class Callback extends HttpServlet { private static final long serialVersionUID = 1L; final String clientId = "--CLIENT-ID--"; final String clientSecret = "--CLIENT-SECRET--"; final String redirectURI = "http://localhost:8080/starter/Callback"; final String TOKEN_SERVER_URL = "https://identity.xero.com/connect/token"; final String AUTHORIZATION_SERVER_URL = "https://login.xero.com/identity/connect/authorize"; final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport(); final JsonFactory JSON_FACTORY = new JacksonFactory(); final ApiClient defaultClient = new ApiClient(); /** * @see HttpServlet#HttpServlet() */ public Callback() { super(); } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse * response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String code = "123"; if (request.getParameter("code") != null) { code = request.getParameter("code"); } // Retrieve your stored secretState variable TokenStorage store = new TokenStorage(); String secretState =store.get(request, "state"); // Compare to state prevent CSRF if (request.getParameter("state") != null && secretState.equals(request.getParameter("state").toString())) { ArrayList<String> scopeList = new ArrayList<String>(); scopeList.add("openid"); scopeList.add("email"); scopeList.add("profile"); scopeList.add("offline_access"); scopeList.add("accounting.settings"); scopeList.add("accounting.transactions"); scopeList.add("accounting.contacts"); scopeList.add("accounting.journals.read"); scopeList.add("accounting.reports.read"); scopeList.add("accounting.attachments"); DataStoreFactory DATA_STORE_FACTORY = new MemoryDataStoreFactory(); AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(), HTTP_TRANSPORT, JSON_FACTORY, new GenericUrl(TOKEN_SERVER_URL), new ClientParametersAuthentication(clientId, clientSecret), clientId, AUTHORIZATION_SERVER_URL) .setScopes(scopeList).setDataStoreFactory(DATA_STORE_FACTORY).build(); TokenResponse tokenResponse = flow.newTokenRequest(code).setRedirectUri(redirectURI).execute(); try { DecodedJWT verifiedJWT = defaultClient.verify(tokenResponse.getAccessToken()); ApiClient defaultIdentityClient = new ApiClient("https://api.xero.com", null, null, null, null); IdentityApi idApi = new IdentityApi(defaultIdentityClient); List<Connection> connection = idApi.getConnections(tokenResponse.getAccessToken(),null); store.saveItem(response, "token_set", tokenResponse.toPrettyString()); store.saveItem(response, "access_token", verifiedJWT.getToken()); store.saveItem(response, "refresh_token", tokenResponse.getRefreshToken()); store.saveItem(response, "expires_in_seconds", tokenResponse.getExpiresInSeconds().toString()); store.saveItem(response, "xero_tenant_id", connection.get(0).getTenantId().toString()); response.sendRedirect("./AuthenticatedResource"); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Invalid state - possible CSFR"); } } } ``` TokenStorage class uses cookies to store your access token, refresh token and Xero tenant id. Of course, you'd want to create your own implementation of Token Storage to store information in a database. This class is merely for demo purposes so you can trying out the SDK. *TokenStorage.java* ```java package com.xero.example; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class TokenStorage { public TokenStorage() { super(); } public String get(HttpServletRequest request, String key) { String item = null; Cookie[] cookies = request.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { if (cookies[i].getName().equals(key)) { item = cookies[i].getValue(); } } } return item; } public void clear(HttpServletResponse response) { HashMap<String, String> map = new HashMap<String, String>(); map.put("jwt_token", ""); map.put("id_token", ""); map.put("access_token", ""); map.put("refresh_token", ""); map.put("expires_in_seconds", ""); map.put("xero_tenant_id", ""); save(response, map); } public void saveItem(HttpServletResponse response, String key, String value) { Cookie t = new Cookie(key, value); response.addCookie(t); } public void save(HttpServletResponse response, HashMap<String, String> map) { Set<Entry<String, String>> set = map.entrySet(); Iterator<Entry<String, String>> iterator = set.iterator(); while (iterator.hasNext()) { Map.Entry<?, ?> mentry = iterator.next(); String key = (String) mentry.getKey(); String value = (String) mentry.getValue(); Cookie t = new Cookie(key, value); response.addCookie(t); } } } ``` TokenRefresh class is an example of how you can check if your access token is expired and perform a token refresh if needed. This example uses the TokenStorage class to persist you new access token and refresh token when performing a refresh. You are welcome to modify or replace this class to suit your needs. *TokenRefresh.java* ```java package com.xero.example; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import org.slf4j.LoggerFactory; import org.slf4j.Logger; import com.auth0.jwt.JWT; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import com.google.api.client.auth.oauth2.RefreshTokenRequest; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.auth.oauth2.TokenResponseException; import com.google.api.client.http.BasicAuthentication; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.xero.api.ApiClient; public class TokenRefresh { final static Logger logger = LoggerFactory.getLogger(TokenRefresh.class); final String clientId = "--CLIENT-ID--"; final String clientSecret = "--CLIENT-SECRET--"; final String TOKEN_SERVER_URL = "https://identity.xero.com/connect/token"; final ApiClient defaultClient = new ApiClient(); public TokenRefresh() { super(); } public String checkToken(String accessToken, String refreshToken, HttpServletResponse response) throws IOException { String currToken = null; try { DecodedJWT jwt = JWT.decode(accessToken); if (jwt.getExpiresAt().getTime() > System.currentTimeMillis()) { System.out.println("Refresh Token : NOT NEEDED - return current token"); currToken = accessToken; } else { System.out.println("Refresh Token : BEGIN"); try { TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new JacksonFactory(), new GenericUrl(TOKEN_SERVER_URL), refreshToken) .setClientAuthentication(new BasicAuthentication(this.clientId, this.clientSecret)) .execute(); System.out.println("Refresh Token : SUCCESS"); try { DecodedJWT verifiedJWT = defaultClient.verify(tokenResponse.getAccessToken()); // DEMO PURPOSE ONLY - You'll need to implement your own token storage solution TokenStorage store = new TokenStorage(); store.saveItem(response, "token_set", tokenResponse.toPrettyString()); store.saveItem(response, "access_token", verifiedJWT.getToken()); store.saveItem(response, "refresh_token", tokenResponse.getRefreshToken()); store.saveItem(response, "expires_in_seconds", tokenResponse.getExpiresInSeconds().toString()); currToken = verifiedJWT.getToken(); } catch (Exception e) { e.printStackTrace(); } } catch (TokenResponseException e) { System.out.println("Refresh Token : EXCEPTION"); if (e.getDetails() != null) { System.out.println("Error: " + e.getDetails().getError()); if (e.getDetails().getErrorDescription() != null) { System.out.println(e.getDetails().getErrorDescription()); } if (e.getDetails().getErrorUri() != null) { System.out.println(e.getDetails().getErrorUri()); } } else { System.out.println("Refresh Token : EXCEPTION"); System.out.println(e.getMessage()); } } } } catch (JWTDecodeException exception) { System.out.println("Refresh Token : INVALID TOKEN"); System.out.println(exception.getMessage()); } return currToken; } } ``` It is recommended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK `client`, and refresh your `access_token` prior to making API calls. ### Token Set | key | value | description | | --- | --- | --- | | id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | | access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | | expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | | refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | | scope: | "email profile openid accounting.transactions offline_access" | The Xero permissions that are embedded in the `access_token` | --- ## Custom Connections Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth 2.0's [`client_credentials`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. > [Sample Application full code example](https://github.com/XeroAPI/xero-java-custom-connections-starter) To use this SDK with a Custom Connection: ```java final String clientId = "--CLIENT-ID--"; final String clientSecret = "--CLIENT-SECRET--"; final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport(); final JsonFactory JSON_FACTORY = new JacksonFactory(); ArrayList<String> appStoreScopeList = new ArrayList<String>(); appStoreScopeList.add("accounting.transactions"); // client_credentials TokenResponse tokenResponse = new ClientCredentialsTokenRequest(HTTP_TRANSPORT, JSON_FACTORY, new GenericUrl("https://identity.xero.com/connect/token")) .setScopes(appStoreScopeList) .setClientAuthentication( new BasicAuthentication(clientId, clientSecret)) .execute(); ``` Because Custom Connections are only valid for a single organisation you don't need to set the specific `xero-tenant-id` anymore which can now simply be set as an empy string. --- ## App Store Subscriptions If you are implementing subscriptions to participate in Xero's App Store you will need to setup [App Store subscriptions](https://developer.xero.com/documentation/guides/how-to-guides/xero-app-store-referrals/) endpoints. When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API. With your app credentials you can create a client via `client_credentials` grant_type with the `marketplace.billing` scope. This unique access_token will allow you to interact with functions in `AppStoreApi`. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process. ```java final String clientId = "--APP-STORE-CLIENT-ID--"; final String clientSecret = "--APP-STORE-CLIENT-SECRET--"; final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport(); final JsonFactory JSON_FACTORY = new JacksonFactory(); ArrayList<String> appStoreScopeList = new ArrayList<String>(); scopesList.add("marketplace.billing"); // client_credentials TokenResponse tokenResponse = new ClientCredentialsTokenRequest(HTTP_TRANSPORT, JSON_FACTORY, new GenericUrl("https://identity.xero.com/connect/token")) .setScopes(scopesList) .setClientAuthentication( new BasicAuthentication(clientId, clientSecret)) .execute(); // => /post-purchase-url?subscriptionId=03bc74f2-1237-4477-b782-2dfb1a6d8b21 AppStoreApi appStoreApi = new AppStoreApi(defaultIdentityClient); Subscription subscription = appStoreApi.getSubscription(tokenResponse.getAccessToken(), "03bc74f2-1237-4477-b782-2dfb1a6d8b21"); ``` ## API Clients You can access the different API sets and their available methods through the following: ```java ApiClient defaultClient = new ApiClient(); // Get Singleton - instance of sub client accountingApi = AccountingApi.getInstance(defaultClient); assetApi = AssetApi.getInstance(defaultClient); bankFeedsApi = BankfeedsApi.getInstance(defaultClient); filesApi = FilesApi.getInstance(defaultClient); projectApi = ProjectNzApi.getInstance(defaultClient); identityApi = IdentityApi.getInstance(defaultClient); payrollAuApi = PayrollAuApi.getInstance(defaultClient); payrollUkApi = PayrollUkApi.getInstance(defaultClient); payrollNzApi = PayrollNzApi.getInstance(defaultClient); appStoreApi = AppStoreApi.getInstance(defaultClient); ``` --- ## Usage Examples The Xero Java SDK contains Client classes (AccountingApi, etc) which have helper methods to perform (Create, Read, Update and Delete) actions on each endpoints. AccountingApi is designed as a Singleton. Use the getInstance method of the class class and use with API models to interact with Java Objects. *Token expiration should be checked prior to making API calls* *AuthenticatedResource.java* ```java package com.xero.example; import java.io.IOException; import java.util.UUID; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.threeten.bp.OffsetDateTime; import com.xero.api.ApiClient; import com.xero.api.XeroApiException; import com.xero.api.client.AccountingApi; import com.xero.models.accounting.*; @WebServlet("/AuthenticatedResource") public class AuthenticatedResource extends HttpServlet { private static final long serialVersionUID = 1L; private AccountingApi accountingApi; public AuthenticatedResource() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Get Tokens and Xero Tenant Id from Storage TokenStorage store = new TokenStorage(); String savedAccessToken =store.get(request, "access_token"); String savedRefreshToken = store.get(request, "refresh_token"); String xeroTenantId = store.get(request, "xero_tenant_id"); // Check expiration of token and refresh if necessary // This should be done prior to each API call to ensure your accessToken is valid String accessToken = new TokenRefresh().checkToken(savedAccessToken,savedRefreshToken,response); // Init AccountingApi client ApiClient defaultClient = new ApiClient(); // Get Singleton - instance of accounting client accountingApi = AccountingApi.getInstance(defaultClient); try { // Get All Contacts Contacts contacts = accountingApi.getContacts(accessToken,xeroTenantId,null, null, null, null, null, null); System.out.println("How many contacts did we find: " + contacts.getContacts().size()); /* CREATE ACCOUNT */ Account acct = new Account(); acct.setName("Office Expense for Me"); acct.setCode("66000"); acct.setType(com.xero.models.accounting.AccountType.EXPENSE); Accounts newAccount = accountingApi.createAccount(accessToken,xeroTenantId,acct); System.out.println("New account created: " + newAccount.getAccounts().get(0).getName()); /* READ ACCOUNT using a WHERE clause */ String where = "Status==\"ACTIVE\"&&Type==\"BANK\""; Accounts accountsWhere = accountingApi.getAccounts(accessToken,xeroTenantId,null, where, null); /* READ ACCOUNT using the ID */ Accounts accounts = accountingApi.getAccounts(accessToken,xeroTenantId,null, null, null); UUID accountID = accounts.getAccounts().get(0).getAccountID(); Accounts oneAccount = accountingApi.getAccount(accessToken,xeroTenantId,accountID); /* UPDATE ACCOUNT */ UUID newAccountID = newAccount.getAccounts().get(0).getAccountID(); newAccount.getAccounts().get(0).setDescription("Monsters Inc."); newAccount.getAccounts().get(0).setStatus(null); Accounts updateAccount = accountingApi.updateAccount(accessToken,xeroTenantId,newAccountID, newAccount); /* DELETE ACCOUNT */ UUID deleteAccountID = newAccount.getAccounts().get(0).getAccountID(); Accounts deleteAccount = accountingApi.deleteAccount(accessToken,xeroTenantId,deleteAccountID); System.out.println("Delete account - Status? : " + deleteAccount.getAccounts().get(0).getStatus()); // GET INVOICE MODIFIED in LAST 24 HOURS OffsetDateTime invModified = OffsetDateTime.now(); invModified.minusDays(1); Invoices InvoiceList24hour = accountingApi.getInvoices(accessToken,xeroTenantId,invModified, null, null, null, null, null, null, null, null, null, null); System.out.println("How many invoices modified in last 24 hours?: " + InvoiceList24hour.getInvoices().size()); response.getWriter().append("API calls completed at: ").append(request.getContextPath()); } catch (XeroBadRequestException e) { // 400 // ACCOUNTING VALIDATION ERROR if (e.getElements() != null && e.getElements().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (Element item : e.getElements()) { for (ValidationError err : item.getValidationErrors()) { System.out.println("Accounting Validation Error Msg: " + err.getMessage()); } } } } catch (XeroUnauthorizedException e) { // 401 System.out.println("Exception message: " + e.getMessage()); } catch (XeroForbiddenException e) { // 403 System.out.println("Exception message: " + e.getMessage()); } catch (XeroNotFoundException e) { // 404 System.out.println("Exception message: " + e.getMessage()); } catch (XeroMethodNotAllowedException e) { // 405 System.out.println("Exception message: " + e.getMessage()); } catch (XeroRateLimitException e) { // 429 System.out.println("Exception message: " + e.getMessage()); } catch (XeroServerErrorException e) { // 500 System.out.println("Exception message: " + e.getMessage()); } catch (Exception e) { System.out.println(e.getMessage()); } } } ``` ### Revoking Token You can revoke a user's refresh token and remove all their connections to your app by making a request to the revocation endpoint. We've added a helpful method to the ApiClient class. The code below shows how to pass the id, secret and refresh token to execute the revoke method. Success ```java try { ApiClient apiClient = new ApiClient(); HttpResponse revokeResponse = apiClient.revoke(clientId, clientSecret, refreshToken); System.out.println("Revoke success: " + revokeResponse.getStatusCode()); } catch (Exception e) { System.out.println(e.getMessage()); } ``` ## Running Test(s) in Local For Running Test cases PRISM Mock Server needs to be started in the local machine. Steps to Run Test(s) * Install PRISM from npm using the command: **npm install -g @stoplight/prism-cli** * Verify Installation: **prism --version** * Navigate to **Xero-Java--> src--> test--> util** folder in the terminal * Execute the script **./start-prism.sh** * This will start the PRISM Server in Local * Run **mvn clean verify -DskipTests=false** to build the Java code along with Test Cases. --- ## SDK conventions ### Working with dates Both our Accounting and AU Payroll APIs use [Microsoft .NET JSON format](https://developer.xero.com/documentation/api/accounting/requests-and-responses/#JSON) i.e. "/Date(1439434356790)/". Our other APIs use standard date formatting i.e. "2020-03-24T18:43:43.860852". Building our SDKs from OpenAPI specs with such different date formats has been challenging. For this reason, we've decided dates in MS .NET JSON format will be strings with NO date or date-time format in our OpenAPI specs. This means developers wanting to use our OpenAPI specs with code generators won't run into deserialization issues trying to handle MS .NET JSON format dates. The side effect is accounting and AU payroll models now have two getter methods. For example, getDateOfBirth() returns the string "/Date(1439434356790)/" while getDateOfBirthAsDate() return a standard date "2020-05-14". Since you can overload methods in Java setDateOfBirth() can accept a String or a LocalDate. This is a breaking change between version 3.x and 4.x. ### Exception Handling As we work to expand API coverage in our SDKs through new OpenAPI specs, we've discovered that error messages returned by different Xero API sets can vary greatly. Specifically, we found the format of validation errors differ enough that our current exception handling resulted in details being lost as exceptions bubbled up. To address this we've refactored exception handling and we are deprecating the general purpose XeroApiException class and replacing it with unique exceptions. Below are the unique exception classes, but will add more as needed. **This is a breaking change between version 3.x and 4.x.** | code | class | description | |------|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------| | N/A | XeroException | All Xero exceptions extend from XeroException | | N/A | XeroAuthenticationException | XeroUnauthorizedException and XeroUnauthorizedException extend from XeroAuthenticationException | | 400 | XeroBadRequestException | A validation exception has occurred - typical cause invalid data. Look at data returned for error details | | 401 | XeroUnauthorizedException | Invalid authorization credentials. Extends XeroAuthenticationException | | 403 | XeroForbiddenException | Not authorized to access a resource - typical cause is problem with scopes. Extends XeroAuthenticationException | | 404 | XeroNotFoundException | The resource you have specified cannot be found | | 405 | XeroMethodNotAllowedException | Method not allowed on the organisation - typical cause the API is not available in the organisation i.e. UK Payroll on Australian org. | | 429 | XeroRateLimitException | All Xero rate limit exceptions extend XeroRateLimitException. | | 429 | XeroAppMinuteRateLimitException | The API app minute rate limit for your organisation/application pairing has been exceeded. | | 429 | XeroDailyRateLimitException | The API daily rate limit for your organisation/application pairing has been exceeded. | | 429 | XeroMinuteRateLimitException | The API minute rate limit for your organisation/application pairing has been exceeded. | | 500 | XeroServerErrorException | An unhandled error with the Xero API | | 501 | XeroNotImplementedException | Method not implemented for the organisation | | 503 | XeroNotAvailableException | The organisation temporarily cannot be connected to or API is currently unavailable – typically due to a scheduled outage | Below is a try/catch example ```java class TryCatchExample { void someOperation() { try { // Create contact with the same name as an existing contact will generate a validation error. Contact contact = new Contact(); contact.setName("Test user"); Contacts createContact1 = accountingApi.createContact(accessToken, xeroTenantId, contact); Contacts createContact2 = accountingApi.createContact(accessToken, xeroTenantId, contact); } catch (XeroBadRequestException e) { // 400 // ACCOUNTING VALIDATION ERROR if (e.getElements() != null && e.getElements().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (Element item : e.getElements()) { for (ValidationError err : item.getValidationErrors()) { System.out.println("Accounting Validation Error Msg: " + err.getMessage()); } } // FIXED ASSETS VALIDATION ERROR } else if (e.getFieldValidationErrorsElements() != null && e.getFieldValidationErrorsElements().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (FieldValidationErrorsElement ele : e.getFieldValidationErrorsElements()) { System.out.println("Asset Field Validation Error Msg: " + ele.getDetail()); } // BANKFEEDS - Statement VALIDATION ERROR } else if (e.getStatementItems() != null && e.getStatementItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (Statement statement : e.getStatementItems()) { System.out.println("Bank Feed - Statement Msg: " + statement.getFeedConnectionId()); for (com.xero.models.bankfeeds.Error statementError : statement.getErrors()) { System.out.println("Bank Feed - Statement Error Msg: " + statementError.getDetail()); } } // AU PAYROLL - Employee VALIDATION ERROR } else if (e.getEmployeeItems() != null && e.getEmployeeItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (com.xero.models.payrollau.Employee emp : e.getEmployeeItems()) { for (com.xero.models.payrollau.ValidationError err : emp.getValidationErrors()) { System.out.println("Payroll AU Employee Validation Error Msg: " + err.getMessage()); } } // AU PAYROLL - Payroll Calendar VALIDATION ERROR } else if (e.getPayrollCalendarItems() != null && e.getPayrollCalendarItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (com.xero.models.payrollau.PayrollCalendar item : e.getPayrollCalendarItems()) { for (com.xero.models.payrollau.ValidationError err : item.getValidationErrors()) { System.out.println("Payroll AU Payroll Calendar Validation Error Msg: " + err.getMessage()); } } // AU PAYROLL - PayRun VALIDATION ERROR } else if (e.getPayRunItems() != null && e.getPayRunItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (com.xero.models.payrollau.PayRun item : e.getPayRunItems()) { for (com.xero.models.payrollau.ValidationError err : item.getValidationErrors()) { System.out.println("Payroll AU Payroll Calendar Validation Error Msg: " + err.getMessage()); } } // AU PAYROLL - SuperFund VALIDATION ERROR } else if (e.getSuperFundItems() != null && e.getSuperFundItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (com.xero.models.payrollau.SuperFund item : e.getSuperFundItems()) { for (com.xero.models.payrollau.ValidationError err : item.getValidationErrors()) { System.out.println("Payroll AU SuperFund Validation Error Msg: " + err.getMessage()); } } // AU PAYROLL - Timesheet VALIDATION ERROR } else if (e.getTimesheetItems() != null && e.getTimesheetItems().size() > 0) { System.out.println("Xero Exception: " + e.getStatusCode()); for (com.xero.models.payrollau.Timesheet item : e.getTimesheetItems()) { for (com.xero.models.payrollau.ValidationError err : item.getValidationErrors()) { System.out.println("Payroll AU Timesheet Validation Error Msg: " + err.getMessage()); } } // UK PAYROLL - PROBLEM ERROR } else if (e.getPayrollUkProblem() != null && ((e.getPayrollUkProblem().getDetail() != null && e.getPayrollUkProblem().getTitle() != null) || (e.getPayrollUkProblem().getInvalidFields() != null && e.getPayrollUkProblem().getInvalidFields().size() > 0))) { System.out.println("Xero Exception: " + e.getStatusCode()); System.out.println("Problem title: " + e.getPayrollUkProblem().getTitle()); System.out.println("Problem detail: " + e.getPayrollUkProblem().getDetail()); if (e.getPayrollUkProblem().getInvalidFields() != null && e.getPayrollUkProblem().getInvalidFields().size() > 0) { for (com.xero.models.payrolluk.InvalidField field : e.getPayrollUkProblem().getInvalidFields()) { System.out.println("Invalid Field name: " + field.getName()); System.out.println("Invalid Field reason: " + field.getReason()); } } } else { System.out.println("Error Msg: " + e.getMessage()); } } catch (XeroUnauthorizedException e) { // 401 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); } catch (XeroForbiddenException e) { // 403 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); } catch (XeroNotFoundException e) { // 404 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); } catch (XeroMethodNotAllowedException e) { // 405 if (e.getPayrollUkProblem() != null) { System.out.println("Xero Exception: " + e.getStatusCode()); System.out.println("Problem title: " + e.getPayrollUkProblem().getTitle()); System.out.println("Problem detail: " + e.getPayrollUkProblem().getDetail()); if (e.getPayrollUkProblem().getInvalidFields() != null && e.getPayrollUkProblem().getInvalidFields().size() > 0) { for (com.xero.models.payrolluk.InvalidField field : e.getPayrollUkProblem().getInvalidFields()) { System.out.println("Invalid Field name: " + field.getName()); System.out.println("Invalid Field reason: " + field.getReason()); } } } } catch (XeroAppMinuteRateLimitException | XeroDailyRateLimitException | XeroMinuteRateLimitException e) { // 429 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); System.out.println("Remaining app minute limit: " + e.getAppLimitRemaining()); System.out.println("Remaining daily limit: " + e.getDayLimitRemaining()); System.out.println("Remaining minute limit: " + e.getMinuteLimitRemaining()); System.out.println("Retry after seconds: " + e.getRetryAfterSeconds()); } catch (XeroRateLimitException e) { // 429 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); System.out.println("Remaining app minute limit: " + e.getAppLimitRemaining()); System.out.println("Remaining daily limit: " + e.getDayLimitRemaining()); System.out.println("Remaining minute limit: " + e.getMinuteLimitRemaining()); System.out.println("Retry after seconds: " + e.getRetryAfterSeconds()); } catch (XeroServerErrorException e) { // 500 System.out.println("Exception status code: " + e.getStatusCode()); System.out.println("Exception message: " + e.getMessage()); } catch (Exception e) { // other Exceptions } } } ``` ### Logging We've replace a specific logging plugin (org.apache.logging.log4j) with a logging facade org.slf4j. With version 4.x we'll use SLF4J and allow you to plug in the logging library of your choice at deployment time. This [blog post](https://www.baeldung.com/slf4j-with-log4j2-logback) explains how to add log4j2 for logging. To configure, add a log4j.properties file to the Resources directory. ### Looking for version 3.x of the SDK? Codebase, samples and setup instructions located in [java-3.x branch](https://github.com/XeroAPI/Xero-Java/tree/java-3.x). --- ## Participating in Xero’s developer community This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/Xero-Java/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it * If you raise an issue then please make sure to fill out the Github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/Xero-Java/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/Xero-Java/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/). ### Contributing PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. ### Versioning We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know.

JavaScript Libraries & Components Accounting
84 Github Stars
xero-ruby
Open Source

xero-ruby

# xero-ruby [![Gem Version](https://badge.fury.io/rb/xero-ruby.svg)](https://badge.fury.io/rb/xero-ruby) [![Github forks](https://img.shields.io/github/forks/XeroAPI/xero-ruby.svg)](https://github.com/XeroAPI/xero-ruby/network) [![Github stars](https://img.shields.io/github/stars/XeroAPI/xero-ruby.svg)](https://github.com/XeroAPI/xero-ruby/stargazers) ![total downloads](https://ruby-gem-downloads-badge.herokuapp.com/xero-ruby?type=total) The xero-ruby SDK makes it easy for developers to access Xero's APIs in their Ruby code, and build robust applications and software using small business & general ledger accounting data. # Table of Contents - [API Client documentation](#api-client-documentation) - [Sample Applications](#sample-applications) - [Xero Account Requirements](#xero-account-requirements) - [Installation](#installation) - [Configuration](#configuration) - [Authentication](#authentication) - [Custom Connections](#custom-connections) - [App Store Subscriptions](#app-store-subscriptions) - [API Clients](#api-clients) - [Helper Methods](#helper-methods) - [Usage Examples](#usage-examples) - [SDK conventions](#sdk-conventions) - [Contributing](#contributing) <hr> ## API Client documentation This SDK supports full method coverage for the following Xero API sets: | API Set | Description | | --- | --- | | [`Accounting`](https://xeroapi.github.io/xero-ruby/accounting/index.html) | The Accounting API exposes accounting functions of the main Xero application *(most commonly used)* | [Assets](https://xeroapi.github.io/xero-ruby/assets/index.html) | The Assets API exposes fixed asset related functions of the Xero Accounting application | | [Files](https://xeroapi.github.io/xero-ruby/files/index.html) | The Files API provides access to the files, folders, and the association of files within a Xero organisation | | [Projects](https://xeroapi.github.io/xero-ruby/projects/index.html) | Xero Projects allows businesses to track time and costs on projects/jobs and report on profitability | | [Payroll (AU)](https://xeroapi.github.io/xero-ruby/payroll_au/index.html) | The (AU) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (UK)](https://xeroapi.github.io/xero-ruby/payroll_uk/index.html) | The (UK) Payroll API exposes payroll related functions of the payroll Xero application | | [Payroll (NZ)](https://xeroapi.github.io/xero-ruby/payroll_nz/index.html) | The (NZ) Payroll API exposes payroll related functions of the payroll Xero application | | [`Ruby Models`](/docs/) | Directory of markdown files, describing the object models for all supported API sets | <img src="https://i.imgur.com/0MsvkGB.png" alt="drawing" width="350"/> <hr> ## Sample Applications Sample apps can get you started quickly with simple auth flows to advanced usage. | Sample App | Description | Screenshot | | --- | --- | --- | | [`xero-ruby-oauth2-starter`](https://github.com/XeroAPI/Xero-ruby-oauth2-starter) | A Sinatra application showing the basic getting started code to work with the sdk | <img src="https://i.imgur.com/9H4F98M.png" alt="drawing" width="300"/> | [`xero-ruby-oauth2-app`](https://github.com/XeroAPI/Xero-ruby-oauth2-app) | Complete rails app with +95% of api set examples, complex filters, pagination, and user/token management in postgres | <img src="https://i.imgur.com/XsAp9Ww.png" alt="drawing" width="500"/> | [`xero-ruby-custom-connections-starter`](https://github.com/XeroAPI/xero-ruby-custom-connections-starter) | A getting started Sinatra app showing Custom Connections - a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) for building M2M integrations to a single org | <img src="https://i.imgur.com/YEkScui.png" alt="drawing" width="300"/> | [`xero-ruby-sso-form`](https://github.com/XeroAPI/xero-ruby-sso-form) | A basic Sinatra app showing how to implement SSU to Lead | <img src="https://i.imgur.com/Nf95GVd.png" alt="drawing" width="300"/> <hr> ## Xero Account Requirements - Create a [free Xero user account](https://www.xero.com/us/signup/api/) - Login to your Xero developer [dashboard](https://developer.xero.com/app/manage) and create an API application - Copy the credentials from your API app and store them using a secure ENV variable strategy - Decide the [neccesary scopes](https://developer.xero.com/documentation/oauth2/scopes) for your app's functionality # Installation To install this gem to your project ``` gem install 'xero-ruby' ``` Or add to your gemfile and run `bundle install` ``` gem 'xero-ruby' ``` --- ## Configuration ```ruby require 'xero-ruby' creds = { client_id: ENV['CLIENT_ID'], client_secret: ENV['CLIENT_SECRET'], redirect_uri: ENV['REDIRECT_URI'], scopes: ENV['SCOPES'], state: "Optional value to pass through auth flow" } xero_client ||= XeroRuby::ApiClient.new(credentials: creds) ``` For additional [config](/lib/xero-ruby/configuration.rb) options you can pass an optional named parameter `config: {}` ```ruby config = { timeout: 30, debugging: true } @xero_client ||= XeroRuby::ApiClient.new(credentials: creds, config: config) ``` --- ## Authentication All API requests go through Xero's OAuth2.0 gateway and require a valid `access_token` to be set on the `xero_client`, which autmoatically appends the `access_token` [JWT](https://jwt.io/) to the header of each request. If you are making an API call for the first time: 1. Send the user to the Xero authorization URL ```ruby @authorization_url = xero_client.authorization_url redirect_to @authorization_url ``` 2. The user will authorize your API application and be sent to your `redirect_uri` ```ruby ENV['REDIRECT_URI'] => /xero-callback?code=xyz123 ``` 3. You exchange a temporary `authorization_code` for a valid `token_set` ```ruby @token_set = @xero_client.get_token_set_from_callback(params) # save the @token_set ``` It is reccomended that you store this token set JSON in a datastore in relation to the user who has authenticated the Xero API connection. Each time you want to call the Xero API, you will need to access the previously generated token set, initialize it on the SDK `xero_client`, and refresh the `access_token` prior to making API calls. ### Token Set | key | value | description | | --- | --- | --- | | id_token: | "xxx.yyy.zzz" | [OpenID Connect](https://openid.net/connect/) token returned if `openid profile email` scopes accepted | | access_token: | "xxx.yyy.zzz" | [Bearer token](https://oauth.net/2/jwt/) with a 30 minute expiration required for all API calls | | expires_in: | 1800 | Time in seconds till the token expires - 1800s is 30m | | refresh_token: | "XXXXXXX" | Alphanumeric string used to obtain a new Token Set w/ a fresh access_token - 60 day expiry | | scope: | "email profile openid accounting.transactions offline_access" | The Xero permissions that are embedded in the `access_token` | Example Token Set JSON: ``` { "id_token": "xxx.yyy.zz", "access_token": "xxx.yyy.zzz", "expires_in": 1800, "token_type": "Bearer", "refresh_token": "xxxxxxxxx", "scope": "email profile openid accounting.transactions offline_access" } ``` --- ## Custom Connections Custom Connections are a Xero [premium option](https://developer.xero.com/documentation/oauth2/custom-connections) used for building M2M integrations to a single organisation. A custom connection uses OAuth2.0's [`client_credentials`](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant which eliminates the step of exchanging the temporary code for a token set. To use this SDK with a Custom Connections: ```ruby CREDENTIALS = { client_id: ENV['CLIENT_ID'], client_secret: ENV['CLIENT_SECRET'], grant_type: 'client_credentials' } xero_client = XeroRuby::ApiClient.new(credentials: CREDENTIALS) @token_set = xero_client.get_client_credentials_token # save @token_set @invoices = xero_client.accounting_api.get_invoices('').invoices ``` Because Custom Connections are only valid for a single organisation you don't need to pass the `xero-tenant-id` as the first parameter to every method. However - due to the nature of how our SDK's are generated from our OpenAPI spec, the parameter remains which requires you to pass an empty string for now to use the SDK with a Custom Connection. --- ## App Store Subscriptions If you are implementing subscriptions to participate in Xero's App Store you will need to setup [App Store subscriptions](https://developer.xero.com/documentation/guides/how-to-guides/xero-app-store-referrals/) endpoints. When a plan is successfully purchased, the user is redirected back to the URL specified in the setup process. The Xero App Store appends the subscription Id to this URL so you can immediately determine what plan the user has subscribed to through the subscriptions API. With your app credentials you can create a client via `client_credentials` grant_type with the `marketplace.billing` scope. This unique access_token will allow you to query any functions in `appStoreApi`. Client Credentials tokens to query app store endpoints will only work for apps that have completed the App Store on-boarding process. ```ruby // => /post-purchase-url?subscriptionId=03bc74f2-1237-4477-b782-2dfb1a6d8b21 subscription_id = params[:subscriptionId] xero_app_store_client ||= XeroRuby::ApiClient.new(credentials: { client_id: ENV['CLIENT_ID'], client_secret: ENV['CLIENT_SECRET'], grant_type: 'client_credentials' scopes: 'marketplace.billing openid profile email' }) xero_app_store_client.get_client_credentials_token @subscription = xero_app_store_client.app_store_api.get_subscription(subscription_id) puts @subscription.to_attributes { :current_period_end => Thu, 02 Sep 2021 14:08:58 +0000, :id => "03bc74f2-1237-4477-b782-2dfb1a6d8b21", :organisation_id => "79e8b2e5-c63d-4dce-888f-e0f3e9eac647", :plans => [ { :id => "6abc26f3-9390-4194-8b25-ce8b9942fda9", :name => "Small", :status => "ACTIVE", :subscription_items => [ { :id => "834cff4c-b753-4de2-9e7a-3451e14fa17a", :price => { :amount => 0.1e0, :currency => "NZD", :id => "2310de92-c7c0-4bcb-b972-fb7612177bc7" }, :product => { :id => "9586421f-7325-4493-bac9-d93be06a6a38", :name => "", :type => "FIXED" }, :start_date => Mon, 02 Aug 2021 14:08:58 +0000, :test_mode => true } ] } ], :start_date => Mon, 02 Aug 2021 14:08:58 +0000, :status => "ACTIVE", :test_mode => true } ``` You should use this subscription data to provision user access/permissions to your application. ### App Store Subscription Webhooks In additon to a subscription Id being passed through the URL, when a purchase or an upgrade takes place you will be notified via a webhook. You can then use the subscription Id in the webhook payload to query the AppStore endpoints and determine what plan the user purchased, upgraded, downgraded or cancelled. Refer to Xero's documenation to learn more about setting up and receiving webhooks. > https://developer.xero.com/documentation/guides/webhooks/overview/ ## API Clients You can access the different API sets and their available methods through the following: ```ruby xero_client = XeroRuby::ApiClient.new(credentials: creds) xero_client.accounting_api xero_client.asset_api xero_client.project_api xero_client.files_api xero_client.payroll_au_api xero_client.payroll_nz_api xero_client.payroll_uk_api ``` --- ## Helper Methods Once you have a valid Token Set in your datastore, the next time you want to call the Xero API simply initialize a new `xero_client` and refresh the token set. ```ruby xero_client = XeroRuby::ApiClient.new(credentials: creds) if xero_client.token_expired? @token_set = xero_client.refresh_token_set(user.token_set) else @token_set = xero_client.set_token_set(user.token_set) end # example strategy user.token_set = @token_set if !@token_set["error"] user.xero_connections = xero_client.connections user.active_tenant_id = xero_client.last_connection user.save! xero_client.accounting_api.get_invoices(xero_client.last_connection['tenantId']).invoices ``` This will set the access_token on the client and return the refreshed `token_set` that you need to save in your datastore to keep the connection alive until you or the user disconnect. A full list of the SDK client's methods: | method | description | | --- | --- | | xero_client.`authorization_url` | returns the authorize URL string to send a new user to for API authorization | | xero_client.`get_token_set_from_callback`(params) | returns and generates a `token_set` from a temporary code for an `authorization_code` configured client only. Params are the url params. | | xero_client.`get_client_credentials_token` | returns and generates a `token_set` for a `client_credentials` configured client only | | xero_client.`refresh_token_set`(token_set) | returns a refreshed token_set | | xero_client.`revoke_token`(token_set) | removes all a user's org connections and revokes the refresh_token | | xero_client.`disconnect`(connection_id) | disconnects an org connection from a user's token permissions | | xero_client.`connections` | returns an array of the user's currently connected Xero orgs | | xero_client.`last_connection` | returns the `xero-tenant-id` of the most recently connected Xero org | | xero_client.`set_token_set`(token_set) | returns a boolean and sets a token on the client - `token_set` containing an access_token & refresh_token | | xero_client.`set_access_token`(access_token) | How you can set an `access_token` by itself | | xero_client.`set_id_token`(id_token) | How you can set an `id_token` by itself | | xero_client.`token_expired?` | returns a boolean if token_set['access_token'] is expired | | xero_client.`token_set` | returns the token set if one is set on the client | | xero_client.`access_token` | returns the `access_token` if one is set on the client | | xero_client.`id_token` | returns the `id_token` if one is set on the client | | xero_client.`decoded_access_token` | Decoded JWT exposing OAuth2.`0` meta details about the token | | xero_client.`decoded_id_token` | Decoded JWT containing the user meta details that can be used to implement SSO or SSU to Lead | --- ## Usage Examples ### Accounting API ```ruby require 'xero-ruby' xero_client.refresh_token_set(user_token_set) tenant_id = xero_client.last_connection # Get Accounts accounts = xero_client.accounting_api.get_accounts(tenant_id).accounts # Create Invoice invoices = { invoices: [{ type: XeroRuby::Accounting::Invoice::ACCREC, contact: { contact_id: contacts[0].contact_id }, line_items: [{ description: "Big Agency", quantity: BigDecimal("2.0"), unit_amount: BigDecimal("50.99"), account_code: "600", tax_type: XeroRuby::Accounting::TaxType::NONE }], date: "2019-03-11", due_date: "2018-12-10", reference: "Website Design", status: XeroRuby::Accounting::Invoice::DRAFT }]} invoice = xero_client.accounting_api.create_invoices(tenant_id, invoices).invoices.first # return data as a snake_case hash puts invoices.to_attributes => {type: 'ACCREC', line_items: [...]} puts invoices.to_hash(downcase: false) => {'Type': 'ACCREC', 'LineItems': [...]} # Create History payment = xero_client.accounting_api.get_payments(tenant_id).payments.first history_records = { history_records: [{ details: "This payment now has some History!" }]} payment_history = xero_client.accounting_api.create_payment_history(tenant_id, payment.payment_id, history_records) # Create Attachment account = xero_client.accounting_api.get_accounts(tenant_id).accounts.first file_name = "an-account-filename.png" opts = { include_online: true } file = File.read(Rails.root.join('app/assets/images/xero-api.png')) attachment = xero_client.accounting_api.create_account_attachment_by_file_name(tenant_id, @account.account_id, file_name, file, opts) ``` --- ## SDK conventions ### BigDecimal All monetary and fields and a couple quantity fields utilize BigDecimal ```ruby puts invoice.unit_amount => 0.2099e2 puts invoice.unit_amount.class => BigDecimal puts invoice.unit_amount.to_s("F") => "20.99" # Rails method-number_to_currency number_to_currency(invoice.unit_amount, :unit => "$") ``` ### Querying & Filtering Examples for `opts` (aka _options_) parameters that most endpoints support. If you have a complex filering/sorting/where usage that is not supported please [open an issue](https://github.com/XeroAPI/xero-ruby/issues). ```ruby # Invoices opts = { page: 1, where: { type: ['=', XeroRuby::Accounting::Invoice::ACCREC], fully_paid_on_date: (DateTime.now - 6.month)..DateTime.now, amount_due: ['>=', 0], reference: ['=', "Website Design"], invoice_number: ['=', "INV-0001"], contact_id: ['=', 'contact-uuid-xxxx-xxx-xxxxxxx'], contact_number: ['=', "the-contact-number"], date: (DateTime.now - 2.year)..DateTime.now # ▲ you can pass a range ▼ or a date & operator date: ['>=', DateTime.now - 2.year], status: ['=', XeroRuby::Accounting::Invoice::PAID] } } xero_client.accounting_api.get_invoices(tenant_id, opts).invoices # Contacts opts = { if_modified_since: (DateTime.now - 1.weeks).to_s, # ▼ ordering by strings needs PascalCase convention order: 'UpdatedDateUtc DESC', where: { is_customer: ['==', true], is_supplier: ['==', true], name: ['StartsWith', 'Rick'] } } xero_client.accounting_api.get_contacts(tenant_id, opts).contacts # for more complex chained filtering that requires a null check pass those in as a string # see https://developer.xero.com/documentation/api/requests-and-responses for more opts = { where: { email_address: '!=null&&EmailAddress.StartsWith("chris.knight@")' } } # Bank Transactions opts = { if_modified_since: (DateTime.now - 1.year).to_s, where: { type: ['==', XeroRuby::Accounting::BankTransaction::SPEND] }, order: 'UpdatedDateUtc DESC', page: 2, unitdp: 4 # (Unit Decimal Places) } xero_client.accounting_api.get_bank_transactions(tenant_id, opts).bank_transactions # Bank Transfers opts = { if_modified_since: (DateTime.now - 1.month).to_s, where: { amount: [">=" , 999.99] }, order: 'Amount ASC' } xero_client.accounting_api.get_bank_transfers(tenant_id, opts).bank_transfers ``` - Not all `opts` parameter combinations are available for all endpoints - Some opts string values may need PascalCasing to match casing defined in our [core API docs](https://developer.xero.com/documentation/api/api-overview) like `opts = { order: 'UpdatedDateUtc DESC'}` --- ## Contributing PRs, issues, and discussion are highly appreciated and encouraged. Note that the majority of this project is generated code based on [Xero's OpenAPI specs](https://github.com/XeroAPI/Xero-OpenAPI) - PR's will be evaluated and pre-merge will be incorporated into the root generation templates. Please add tests for net new functionality and make existing test suite succeeds. `$ rspec spec/` ### Versioning We do our best to keep OS industry `semver` standards, but we can make mistakes! If something is not accurately reflected in a version's release notes please let the team know. ### Developing locally To develop or test this gem locally against your project we find the following development pattern easiest ```bash cd xero-ruby gem build mv xero-ruby-<vsn>.gem xero-ruby.gem pwd => /Users/your.user/code/sdks/xero-ruby/ cd .. cd xero-ruby-oauth2-app/ # xero-ruby-oauth2-app/Gemfile gem 'xero-ruby', path: '/Users/your.user/code/sdks/xero-ruby/' bundle install ``` ## Participating in Xero’s developer community This SDK is one of a number of SDK’s that the Xero Developer team builds and maintains. We are grateful for all the contributions that the community makes. Here are a few things you should be aware of as a contributor: * Xero has adopted the Contributor Covenant [Code of Conduct](https://github.com/XeroAPI/xero-ruby/blob/master/CODE_OF_CONDUCT.md), we expect all contributors in our community to adhere to it * If you raise an issue then please make sure to fill out the github issue template, doing so helps us help you * You’re welcome to raise PRs. As our SDKs are generated we may use your code in the core SDK build instead of merging your code * We have a [contribution guide](https://github.com/XeroAPI/xero-ruby/blob/master/CONTRIBUTING.md) for you to follow when contributing to this SDK * Curious about how we generate our SDK’s? Have a [read of our process](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6) and have a look at our [OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI) * This software is published under the [MIT License](https://github.com/XeroAPI/xero-ruby/blob/master/LICENSE) For questions that aren’t related to SDKs please refer to our [developer support page](https://developer.xero.com/support/).

Accounting API Tools
64 Github Stars