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
|
5 | Version 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 |
|
16 | We'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
|
21 | Follow 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
|
34 | This SDK's functionality is majority generated [from our OpenAPISpec](https://github.com/XeroAPI/Xero-OpenAPI).
|
35 | The 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
|
40 | We 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 | ```
|
43 | npm test
|
44 | ```
|
45 |
|
46 | ## Authentication
|
47 |
|
48 | We 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 |
|
50 | In 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
|
79 | const port = process.env.PORT || 3000
|
80 |
|
81 | const 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()`
|
91 | let consentUrl = await xero.buildConsentUrl();
|
92 |
|
93 | res.redirect(consentUrl);
|
94 | ```
|
95 |
|
96 | ## Step 2
|
97 | Call `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
|
103 | console.log(xero.config.state)
|
104 | => 'returnPage=my-sweet-dashboard'
|
105 |
|
106 | const tokenSet = await xero.apiCallback(req.url);
|
107 | ```
|
108 | The `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 |
|
123 | Populate the XeroClient's active tenant data.
|
124 |
|
125 | For 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 |
|
127 | Calling `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 |
|
129 | If 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
|
133 | const tenants = await xero.updateTenants()
|
134 | console.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
|
154 | const tenants = await xero.updateTenants(false)
|
155 | console.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.
|
168 | await xero.disconnect(xero.tenants[0].id)
|
169 | ```
|
170 |
|
171 | ---
|
172 | ## Making **offline_access** calls
|
173 |
|
174 | Once you have a valid token/tokenSet saved you can set the tokenSet on the client without going through the callback by calling `setTokenSet`.
|
175 |
|
176 | For 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 |
|
178 | There are two ways to refresh a token.
|
179 |
|
180 | ```js
|
181 | // refreshToken()
|
182 | const validTokenSet = await xero.refreshToken()
|
183 | ```
|
184 |
|
185 | If 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
|
187 | const newXeroClient = new XeroClient()
|
188 | const refreshedTokenSet = await newXeroClient.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)
|
189 | ```
|
190 |
|
191 | Making Authorized API calls:
|
192 |
|
193 | ```js
|
194 | const tokenSet = getTokenSetFromDatabase(userId) // example function name
|
195 |
|
196 | await 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
|
199 | await xero.updateTenants()
|
200 |
|
201 | await 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
|
216 | const 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 | });
|
222 | await xero.initialize();
|
223 |
|
224 | const tokenSet = getYourTokenSetFromSavedLocation(currentUser)
|
225 |
|
226 | await xero.setTokenSet(tokenSet)
|
227 | ...
|
228 |
|
229 | const activeTenantId = xero.tenants[0].tenantId
|
230 |
|
231 | const getOrgs = await xero.accountingApi.getOrganisations(activeTenantId)
|
232 | const orgCountry= getOrgs.body.organisations[0].countryCode
|
233 |
|
234 | const contactsResponse = await xero.accountingApi.getContacts(activeTenantId)
|
235 | const contactId = getContactsResponse.body.contacts[0].contactID
|
236 |
|
237 | ---
|
238 | import { XeroClient, Invoice } from "xero-node";
|
239 |
|
240 | const 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 |
|
265 | const createdInvoice = await xero.accountingApi.createInvoices(activeTenantId, invoices)
|
266 |
|
267 | ---
|
268 |
|
269 | // getting files as PDF
|
270 | const 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
|
277 | const filename = "xero-dev.png";
|
278 | const pathToUpload = path.resolve(__dirname, "../public/images/xero-dev.png");
|
279 | const readStream = fs.createReadStream(pathToUpload);
|
280 | const contentType = mime.lookup(filename);
|
281 |
|
282 | const 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
|
290 | For 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 |
|
292 | Just visit the repo https://github.com/XeroAPI/xero-node-oauth2-app configure your credentials & get started.
|
293 |
|
294 | ## Other Helper functions
|
295 | ```js
|
296 | xero.tenants
|
297 |
|
298 | // This needs to be called to setup relevant openid-client on the XeroClient
|
299 | await 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.
|
302 | await xero.buildConsentUrl()
|
303 |
|
304 | // tokenSet and its expiration
|
305 | const tokenSet = await xero.readTokenSet();
|
306 | const now = new Date().getTime()
|
307 |
|
308 | if (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 |
|
315 | tokenSet.expires_in // returns seconds
|
316 | tokenSet.expires_at // returns milliseconds
|
317 | new Date(tokenSet.expires_at * 1000).toLocaleString()) // readable expiration
|
318 |
|
319 | // some endpoints date fields require
|
320 | // the MS date format for POST'ing data
|
321 | const dateString = "1990-02-05"
|
322 | const birthday = await xero.formatMsDate(dateString)
|
323 |
|
324 | await xero.disconnect(xero.tenants[0].id)
|
325 |
|
326 | await xero.readIdTokenClaims()
|
327 |
|
328 | await xero.readTokenSet()
|
329 |
|
330 | const tokenSet = await xero.readTokenSet()
|
331 | await xero.setTokenSet(tokenSet)
|
332 | ```
|