UNPKG

14.1 kBMarkdownView Raw
1# xero-node
2![npm](https://img.shields.io/npm/v/xero-node?label=xero-node)
3
4## Release of SDK with oAuth 2 support
5Version 4.x of Xero NodeJS SDK only 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
13## Looking for OAuth 1.0a support?
14[![npm package](https://img.shields.io/badge/npm%20package-3.1.2-blue.svg)](https://www.npmjs.com/package/xero-node/v/3.1.2)
15
16We've moved this code into the [oauth1 branch](https://github.com/XeroAPI/xero-node/tree/oauth1).
17
18## Getting Started
19
20### Create a Xero App
21Follow these steps to create your Xero app
22
23* Create a [free Xero user account](https://www.xero.com/us/signup/api/) (if you don't have one)
24* Login to [Xero developer center](https://developer.xero.com/myapps)
25* Click "New App" link
26* Enter your App name, company url, privacy policy url.
27* Enter the redirect URI (this is your callback url - localhost, etc)
28* Agree to terms and condition and click "Create App".
29* Click "Generate a secret" button.
30* Copy your client id and client secret and save for use later.
31* Click the "Save" button. You secret is now hidden.
32
33## Repo Context & Contributing
34This SDK's functionality is majority generated [from our OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI).
35The 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.
36
37> Read more about our process in [maintaining our suite of SDK's](https://devblog.xero.com/building-sdks-for-the-future-b79ff726dfd6)
38
39## Testing
40We 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.
41
42```
43npm test
44```
45
46## Authentication
47
48We 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.
49
50In 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.
51
52---
53
54**Step 1:** Initialize the `XeroClient`, and redirect user to xero auth flow
55
56**Step 2:** Call `apiCallback` to get your tokenSet
57
58**Step 3:** Call `updateTenants` to populate additional tenant data
59*You will need to have the `accounting.settings` scope in order to use this helper*
60
61**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:
62```js
63 const tokenSet = getTokenSetFromUserId(user.id) // example function
64 const newXeroClient = new XeroClient()
65 const newTokenSet = await newXeroClient.refreshWithRefreshToken(xero_client_id, xero_client_secret, tokenSet.refresh_token)
66 // refreshWithRefreshToken calls setAccessToken() so the refreshed token will be stored on newXeroClient
67 await newXeroClient.accountingApi.getInvoices('my-tenant-uuid)
68```
69
70---
71
72## Step 1
73* Configure client and generate Authorization URL
74* Choose [XeroAPI Scopes](https://developer.xero.com/documentation/oauth2/scopes) based on the access you need
75* `initialize()` the client to set up the 'openid-client'
76* Build the `consentUrl`
77* Redirect to auth flow
78```js
79const port = process.env.PORT || 3000
80
81const xero = new XeroClient({
82 clientId: 'YOUR_CLIENT_ID',
83 clientSecret: 'YOUR_CLIENT_SECRET',
84 redirectUris: [`http://localhost:${port}/callback`],
85 scopes: 'openid profile email accounting.transactions offline_access'.split(" "),
86 state: 'returnPage=my-sweet-dashboard', // custom params (optional)
87 httpTimeout: 3000 // ms (optional)
88});
89
90// `buildConsentUrl()` will also call `await xero.initialize()`
91let consentUrl = await xero.buildConsentUrl();
92
93res.redirect(consentUrl);
94```
95
96## Step 2
97Call `apiCallback` function with the response url which returns a tokenSet you can save in your datastore for future calls.
98
99*The `tokenSet` can also be accessed from the client as `xero.readTokenSet()`.*
100
101> `http://localhost:${port}/callback`
102```js
103console.log(xero.config.state)
104=> 'returnPage=my-sweet-dashboard'
105
106const tokenSet = await xero.apiCallback(req.url);
107```
108The `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.
109```js
110{
111 id_token: 'eyJhxxxx.yyy',
112 access_token: 'eyJxxx.yyy.zzz',
113 expires_at: 1231231234,
114 token_type: 'Bearer',
115 refresh_token: 'xxxyyyyzzz',
116 scope: 'openid profile email accounting.settings accounting.reports.read accounting.journals.read accounting.contacts accounting.attachments accounting.transactions offline_access',
117 session_state: 'xxx.yyy'
118}
119```
120
121## Step 3 (convenience step)
122
123Populate the XeroClient's active tenant data.
124
125For 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.
126
127Calling `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.
128
129If 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.
130
131```js
132// updateTenants fullOrgDetails param will default to true
133const tenants = await xero.updateTenants()
134console.log(xero.tenants)
135[
136 {
137 id: 'xxx-yyy-zzz-xxx-yyy',
138 tenantId: 'xxx-yyy-zzz-xxx-yyy',
139 tenantType: 'ORGANISATION',
140 createdDateUtc: 'UTC-DateString',
141 updatedDateUtc: 'UTC-DateString',
142 tenantName: 'Demo Company (US)',
143 orgData: {
144 organisationID: 'xxx-yyy-zzz-xxx-yyy',
145 name: 'My first org',
146 version: 'US',
147 shortCode: '!2h37s',
148 ...
149 }
150 }
151]
152
153// if you pass false, the client will not fetch additional metadata about each org connection
154const tenants = await xero.updateTenants(false)
155console.log(xero.tenants)
156[
157 {
158 id: 'xxx-yyy-zzz-xxx-yyy',
159 tenantId: 'xxx-yyy-zzz-xxx-yyy',
160 tenantType: 'ORGANISATION',
161 createdDateUtc: 'UTC-DateString',
162 updatedDateUtc: 'UTC-DateString',
163 tenantName: 'Demo Company (US)'
164 }
165]
166
167// You can also remove a connection by passing `disconnect()` the `.id` which is that tenant's connection id.
168await xero.disconnect(xero.tenants[0].id)
169```
170
171---
172## Making **offline_access** calls
173
174Once you have a valid token/tokenSet saved you can set the tokenSet on the client without going through the callback by calling `setTokenSet`.
175
176For 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.
177
178There are two ways to refresh a token.
179
180```js
181// refreshToken()
182const validTokenSet = await xero.refreshToken()
183```
184
185If 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()
186```js
187const newXeroClient = new XeroClient()
188const refreshedTokenSet = await newXeroClient.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
189```
190
191Making Authorized API calls:
192
193```js
194const tokenSet = getTokenSetFromDatabase(userId) // example function name
195
196await xero.setTokenSet(tokenSet)
197
198// 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
199await xero.updateTenants()
200
201await xero.accountingApi.getInvoices(xero.tenants[0].tenantId)
202```
203
204## SDK Documentation
205* Version 3 (OAuth1.0a documentation) https://xeroapi.github.io/xero-node/v3/index.html (*deprecated end of 2020*)
206* Accounting API documentation: https://xeroapi.github.io/xero-node/v4/accounting/index.html
207* Assets API documentation: https://xeroapi.github.io/xero-node/v4/assets/index.html
208* AU Payroll API documentation: https://xeroapi.github.io/xero-node/v4/payroll-au/index.html
209* Bankfeeds API documentation: https://xeroapi.github.io/xero-node/v4/bankfeeds/index.html
210* UK Payroll API documentation: https://xeroapi.github.io/xero-node/v4/payroll-uk/index.html
211
212
213### Basics
214```js
215// example flow of initializing and using the client after someone has already authenticated and you have saved their tokenSet
216const xero = new XeroClient({
217 clientId: 'YOUR_CLIENT_ID',
218 clientSecret: 'YOUR_CLIENT_SECRET',
219 redirectUris: [`http://localhost:${port}/callback`],
220 scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
221});
222await xero.initialize();
223
224const tokenSet = getYourTokenSetFromSavedLocation(currentUser)
225
226await xero.setTokenSet(tokenSet)
227...
228
229const activeTenantId = xero.tenants[0].tenantId
230
231const getOrgs = await xero.accountingApi.getOrganisations(activeTenantId)
232const orgCountry= getOrgs.body.organisations[0].countryCode
233
234const contactsResponse = await xero.accountingApi.getContacts(activeTenantId)
235const contactId = getContactsResponse.body.contacts[0].contactID
236
237---
238import { XeroClient, Invoice } from "xero-node";
239
240const invoices = {
241 invoices: [
242 {
243 type: Invoice.TypeEnum.ACCREC,
244 contact: {
245 contactID: contactId
246 },
247 lineItems: [
248 {
249 description: "Acme Tires",
250 quantity: 2.0,
251 unitAmount: 20.0,
252 accountCode: "500",
253 taxType: "NONE",
254 lineAmount: 40.0
255 }
256 ],
257 date: "2019-03-11",
258 dueDate: "2018-12-10",
259 reference: "Website Design",
260 status: Invoice.StatusEnum.AUTHORISED
261 }
262 ]
263};
264
265const createdInvoice = await xero.accountingApi.createInvoices(activeTenantId, invoices)
266
267---
268
269// getting files as PDF
270const getAsPdf = await xero.accountingApi.getPurchaseOrderAsPdf(
271 req.session.activeTenant.tenantId,
272 getPurchaseOrdersResponse.body.purchaseOrders[0].purchaseOrderID,
273 { headers: { accept: 'application/pdf' } }
274)
275
276// CREATE ATTACHMENT
277const filename = "xero-dev.png";
278const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png");
279const readStream = fs.createReadStream(pathToUpload);
280const contentType = mime.lookup(filename);
281
282const accountAttachmentsResponse: any = await xero.accountingApi.createAccountAttachmentByFileName(req.session.activeTenant.tenantId, accountId, filename, readStream, {
283 headers: {
284 'Content-Type': contentType
285 }
286});
287```
288
289# Sample App
290For 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).
291
292Just visit the repo https://github.com/XeroAPI/xero-node-oauth2-app configure your credentials & get started.
293
294## Other Helper functions
295```js
296xero.tenants
297
298// This needs to be called to setup relevant openid-client on the XeroClient
299await xero.initialize()
300
301// 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.
302await xero.buildConsentUrl()
303
304// tokenSet and its expiration
305const tokenSet = await xero.readTokenSet();
306const now = new Date().getTime()
307
308if (tokenSet.expires_in > now) {
309 const validTokenSet = await xero.refreshToken()
310 // or you can refresh the token without needing to initialize the openid-client
311 // helpful for background processes where you want to limit any dependencies
312 await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
313}
314
315tokenSet.expires_in // returns seconds
316tokenSet.expires_at // returns milliseconds
317new Date(tokenSet.expires_at * 1000).toLocaleString()) // readable expiration
318
319// some endpoints date fields require
320// the MS date format for POST'ing data
321const dateString = "1990-02-05"
322const birthday = await xero.formatMsDate(dateString)
323
324await xero.disconnect(xero.tenants[0].id)
325
326await xero.readIdTokenClaims()
327
328await xero.readTokenSet()
329
330const tokenSet = await xero.readTokenSet()
331await xero.setTokenSet(tokenSet)
332```