UNPKG

14.6 kBMarkdownView Raw
1# xero-node
2![npm](https://img.shields.io/npm/v/xero-node?label=xero-node)
3
4## OAuth 2 support
5Version 4.x of Xero NodeJS SDK supports OAuth2 authentication and the following API sets.
6* [accounting](https://developer.xero.com/documentation/api/api-overview)
7* [assets](https://developer.xero.com/documentation/assets-api/overview)
8* [projects](https://developer.xero.com/documentation/projects/overview-projects)
9* [AU Payroll](https://developer.xero.com/documentation/payroll-api/overview)
10* [BankFeeds (Restricted API)](https://developer.xero.com/documentation/bank-feeds-api/overview)
11* [UK Payroll](https://developer.xero.com/documentation/payroll-api-uk/overview)
12* [NZ Payroll](https://developer.xero.com/documentation/payroll-api-nz/overview)
13* [files](https://developer.xero.com/documentation/files-api/overview-files)
14
15
16## Getting Started
17
18### Create a Xero App
19Follow these steps to create your Xero app
20
21* Create a [free Xero user account](https://www.xero.com/us/signup/api/) (if you don't have one)
22* Login to [Xero developer center](https://developer.xero.com/myapps)
23* Click "New App" link
24* Enter the redirect URI (this is your callback url - localhost, etc)
25* Agree to terms and condition and click "Create App".
26* Click "Generate a secret" button.
27* Copy your client id and client secret and save for use later.
28
29## Repo Context & Contributing
30This SDK's functionality is majority generated [from our OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI).
31The exception is the `src/xeroClient.ts` which contains the typescript that is unique to this repository. Contributions are welcome but please keep in mind that majority of SDK is auto-generated from the OpenAPISpec. We try to get changes in that projects to be released on a reasonable cadence.
32
33> Read more about our process in [maintaining our suite of SDK's](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6)
34
35## Testing
36We are working to build out a more robust test suite, and currently just have tests setup for our xeroClient.ts - PR's will now run against a CI build - and as we add more tests to this project community collaboration will be easier to incorporate.
37
38```
39npm test
40```
41
42## Authentication
43
44We use [OAuth2.0](https://oauth.net/2) to generate access tokens that authenticate requests against our API. Each API call will need to have a valid token populated on the API client to succeed. In a tokenSet will be an *access_token* which lasts for 30 minutes, and a *refresh_token* which lasts for 60 days. If you don't want to require your users to re-authenticate each time you want to call the API on their behalf, you will need a datastore for these tokens and will be required to refresh the tokens at least once per 60 days to avoid expiration. The `offline_access` scope is required for refresh tokens to work.
45
46In Xero a user can belong to multiple organisations. Tokens are ultimately associated with a Xero user, who can belong to multiple tenants/organisations. If your user 'Allows Access' to multiple organisations, be hyper aware of which `tenantId` you are passing to each function.
47
48---
49
50**Step 1:** Initialize the `XeroClient`, and redirect user to xero auth flow
51
52**Step 2:** Call `apiCallback` to get your tokenSet
53
54**Step 3:** Call `updateTenants` to populate additional tenant data
55*You will need to have the `accounting.settings` scope in order to use this helper*
56
57**NOTE:** If you have already authorized the user and have stored a valid tokenSet, you can create a `new XeroClient()` and refresh your token without triggering the openid-client dependency:
58```js
59 const tokenSet = getTokenSetFromUserId(user.id) // example function
60 const newXeroClient = new XeroClient()
61 const newTokenSet = await newXeroClient.refreshWithRefreshToken(xero_client_id, xero_client_secret, tokenSet.refresh_token)
62 // refreshWithRefreshToken calls setAccessToken() so the refreshed token will be stored on newXeroClient
63 await newXeroClient.accountingApi.getInvoices('my-tenant-uuid')
64```
65
66---
67
68## Step 1
69* Configure client and generate Authorization URL
70* Choose [XeroAPI Scopes](https://developer.xero.com/documentation/oauth2/scopes) based on the access you need
71* `initialize()` the client to set up the 'openid-client'
72* Build the `consentUrl`
73* Redirect to auth flow
74```js
75const port = process.env.PORT || 3000
76
77const xero = new XeroClient({
78 clientId: 'YOUR_CLIENT_ID',
79 clientSecret: 'YOUR_CLIENT_SECRET',
80 redirectUris: [`http://localhost:${port}/callback`],
81 scopes: 'openid profile email accounting.transactions offline_access'.split(" "),
82 state: 'returnPage=my-sweet-dashboard', // custom params (optional)
83 httpTimeout: 3000 // ms (optional)
84});
85
86// `buildConsentUrl()` will also call `await xero.initialize()`
87let consentUrl = await xero.buildConsentUrl();
88
89res.redirect(consentUrl);
90```
91
92## Step 2
93Call `apiCallback` function with the response url which returns a tokenSet you can save in your datastore for future calls.
94
95*The `tokenSet` can also be accessed from the client as `xero.readTokenSet()`.*
96
97> `http://localhost:${port}/callback`
98```js
99console.log(xero.config.state)
100=> 'returnPage=my-sweet-dashboard'
101
102const tokenSet = await xero.apiCallback(req.url);
103```
104The `tokenSet` is what you should store in your database. That object is what you will need to pass to the client. It contains your access_token and refresh_token as well as other information regarding your connection.
105```js
106{
107 id_token: 'eyJhxxxx.yyy',
108 access_token: 'eyJxxx.yyy.zzz',
109 expires_at: 1231231234,
110 token_type: 'Bearer',
111 refresh_token: 'xxxyyyyzzz',
112 scope: 'openid profile email accounting.settings accounting.reports.read accounting.journals.read accounting.contacts accounting.attachments accounting.transactions offline_access',
113 session_state: 'xxx.yyy'
114}
115```
116
117## Step 3 (convenience step)
118
119Populate the XeroClient's active tenant data.
120
121For most integrations you will want to display the org name and use additional metadata about the connected org. The `/connections` endpoint does not currently serialize all org metadata so requires developers to make an additional call for each org your user connects to get information like default currency.
122
123Calling `await xero.updateTenants()` will query the /connections endpoint and store the resulting information on the client. It has an optional parameter named `fullOrgDetails` that defaults to `true`. If you do not pass `false` to this function you will need to have the `accounting.settings` scope on your token as the `/organisation` endpoint that is called, requires it.
124
125If you don't need additional org data (like currency, shortCode, etc) calling the helper with false param `await xero.updateTenants(false)` will not kick off additional org meta data calls.
126
127```js
128// updateTenants fullOrgDetails param will default to true
129const tenants = await xero.updateTenants()
130console.log(xero.tenants)
131[
132 {
133 id: 'xxx-yyy-zzz-xxx-yyy',
134 tenantId: 'xxx-yyy-zzz-xxx-yyy',
135 tenantType: 'ORGANISATION',
136 createdDateUtc: 'UTC-DateString',
137 updatedDateUtc: 'UTC-DateString',
138 tenantName: 'Demo Company (US)',
139 orgData: {
140 organisationID: 'xxx-yyy-zzz-xxx-yyy',
141 name: 'My first org',
142 version: 'US',
143 shortCode: '!2h37s',
144 ...
145 }
146 }
147]
148
149// if you pass false, the client will not fetch additional metadata about each org connection
150const tenants = await xero.updateTenants(false)
151console.log(xero.tenants)
152[
153 {
154 id: 'xxx-yyy-zzz-xxx-yyy',
155 tenantId: 'xxx-yyy-zzz-xxx-yyy',
156 tenantType: 'ORGANISATION',
157 createdDateUtc: 'UTC-DateString',
158 updatedDateUtc: 'UTC-DateString',
159 tenantName: 'Demo Company (US)'
160 }
161]
162
163// You can also remove a connection by passing `disconnect()` the `.id` which is that tenant's connection id.
164await xero.disconnect(xero.tenants[0].id)
165```
166
167---
168## Making **offline_access** calls
169
170Once you have a valid token/tokenSet saved you can set the tokenSet on the client without going through the callback by calling `setTokenSet`.
171
172For example - once a user authenticates you can refresh the token (which will also set the new token on the client) to make authorized api calls.
173
174There are two ways to refresh a token.
175
176```js
177// refreshToken()
178const validTokenSet = await xero.refreshToken()
179```
180
181If you already generated a valid access token, you can initialize an empty client and refresh any saved access_tokens by passing the client, secret, and refresh_token to refreshWithRefreshToken()
182```js
183const newXeroClient = new XeroClient()
184const refreshedTokenSet = await newXeroClient.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
185```
186
187Making Authorized API calls:
188
189```js
190const tokenSet = getTokenSetFromDatabase(userId) // example function name
191
192await xero.setTokenSet(tokenSet)
193
194// you can call this to fetch/set your connected tenant data on your client, or you could also store this information in a database so you don't need to updateTenants every time you connect to API
195await xero.updateTenants()
196
197await xero.accountingApi.getInvoices(xero.tenants[0].tenantId)
198```
199
200## SDK Documentation
201* Version 3 (OAuth1.0a) https://xeroapi.github.io/xero-node/v3/index.html (*deprecated end of 2020*)
202* Accounting API: https://xeroapi.github.io/xero-node/v4/accounting/index.html
203* Assets API: https://xeroapi.github.io/xero-node/v4/assets/index.html
204* AU Payroll API: https://xeroapi.github.io/xero-node/v4/payroll-au/index.html
205* Bankfeeds API: https://xeroapi.github.io/xero-node/v4/bankfeeds/index.html
206* UK Payroll API: https://xeroapi.github.io/xero-node/v4/payroll-uk/index.html
207* Files API: https://xeroapi.github.io/xero-node/v4/files/index.html
208
209
210### Basics
211```js
212// example flow of initializing and using the client after someone has already authenticated and you have saved their tokenSet
213const xero = new XeroClient({
214 clientId: 'YOUR_CLIENT_ID',
215 clientSecret: 'YOUR_CLIENT_SECRET',
216 redirectUris: [`http://localhost:${port}/callback`],
217 scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
218});
219await xero.initialize();
220
221const tokenSet = getYourTokenSetFromSavedLocation(currentUser)
222
223await xero.setTokenSet(tokenSet)
224...
225
226const activeTenantId = xero.tenants[0].tenantId
227
228const getOrgs = await xero.accountingApi.getOrganisations(activeTenantId)
229const orgCountry= getOrgs.body.organisations[0].countryCode
230
231const contactsResponse = await xero.accountingApi.getContacts(activeTenantId)
232const contactId = getContactsResponse.body.contacts[0].contactID
233
234---
235import { XeroClient, Invoice } from "xero-node";
236
237const invoices = {
238 invoices: [
239 {
240 type: Invoice.TypeEnum.ACCREC,
241 contact: {
242 contactID: contactId
243 },
244 lineItems: [
245 {
246 description: "Acme Tires",
247 quantity: 2.0,
248 unitAmount: 20.0,
249 accountCode: "500",
250 taxType: "NONE",
251 lineAmount: 40.0
252 }
253 ],
254 date: "2019-03-11",
255 dueDate: "2018-12-10",
256 reference: "Website Design",
257 status: Invoice.StatusEnum.AUTHORISED
258 }
259 ]
260};
261
262const createdInvoice = await xero.accountingApi.createInvoices(activeTenantId, invoices)
263
264---
265
266// getting files as PDF
267const getAsPdf = await xero.accountingApi.getPurchaseOrderAsPdf(
268 req.session.activeTenant.tenantId,
269 getPurchaseOrdersResponse.body.purchaseOrders[0].purchaseOrderID,
270 { headers: { accept: 'application/pdf' } }
271)
272
273// CREATE ATTACHMENT
274const filename = "xero-dev.png";
275const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png");
276const readStream = fs.createReadStream(pathToUpload);
277const contentType = mime.lookup(filename);
278
279const accountAttachmentsResponse: any = await xero.accountingApi.createAccountAttachmentByFileName(req.session.activeTenant.tenantId, accountId, filename, readStream, {
280 headers: {
281 'Content-Type': contentType
282 }
283});
284
285// UPLOAD A FILE
286import * as fs from "fs";
287const mime = require("mime-types");
288const path = require("path");
289
290const tenantId = 'valid-xero-tenant-id-uuid'
291const folderId = 'valid-folder-uuid-goes-here'
292
293const filename = "xero-dev.png";
294const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png");
295const readStream = fs.createReadStream(pathToUpload);
296const contentType = mime.lookup(filename);
297
298const uploadFile = await xero.filesApi.uploadFile(tenantId, folderId, readStream, filename, contentType);
299```
300
301# Sample App
302For more robust examples in how to utilize our accounting api we have *(roughly)* every single endpoint mapped out with an example in our sample app - complete with showing the Xero data dependencies required for interaction with many objects ( ie. types, assoc. accounts, tax types, date formats).
303
304Just visit the repo https://github.com/XeroAPI/xero-node-oauth2-app configure your credentials & get started.
305
306## Other Helper functions
307```js
308xero.tenants
309
310// This needs to be called to setup relevant openid-client on the XeroClient
311await xero.initialize()
312
313// buildConsentUrl calls `await xero.initialize()` so if you wont't need to call initialize() if your using the client to send user through the auth flow.
314await xero.buildConsentUrl()
315
316// tokenSet and its expiration
317const tokenSet = await xero.readTokenSet();
318const now = new Date().getTime()
319
320if (tokenSet.expires_in > now) {
321 const validTokenSet = await xero.refreshToken()
322 // or you can refresh the token without needing to initialize the openid-client
323 // helpful for background processes where you want to limit any dependencies
324 await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
325}
326
327tokenSet.expires_in // returns seconds
328tokenSet.expires_at // returns milliseconds
329new Date(tokenSet.expires_at * 1000).toLocaleString()) // readable expiration
330
331// some endpoints date fields require
332// the MS date format for POST'ing data
333const dateString = "1990-02-05"
334const birthday = await xero.formatMsDate(dateString)
335
336await xero.disconnect(xero.tenants[0].id)
337
338await xero.readIdTokenClaims()
339
340await xero.readTokenSet()
341
342const tokenSet = await xero.readTokenSet()
343await xero.setTokenSet(tokenSet)
344
345// You can revoke a user's refresh token and remove all their connections to your app by making a request to the revocation endpoint.
346await xero.revokeToken()
347```