Class: XeroKiwi::Client
- Inherits:
-
Object
- Object
- XeroKiwi::Client
- Defined in:
- lib/xero_kiwi/client.rb
Overview
Entry point for talking to Xero. Holds the OAuth2 token state, knows how to refresh it (when given client credentials), and exposes resource methods that auto-refresh before each request.
# Simple — access token only, no refresh capability.
client = XeroKiwi::Client.new(access_token: "ya29...")
# Full — refresh-capable, with persistence callback.
client = XeroKiwi::Client.new(
access_token: creds.access_token,
refresh_token: creds.refresh_token,
expires_at: creds.expires_at,
client_id: ENV["XERO_CLIENT_ID"],
client_secret: ENV["XERO_CLIENT_SECRET"],
on_token_refresh: ->(token) { creds.update!(token.to_h) }
)
client.token # => XeroKiwi::Token
client.token.expired? # => false
client.refresh_token! # manual force refresh
client.connections # auto-refreshes if expiring; reactive on 401
Defined Under Namespace
Classes: ResponseHandler
Constant Summary collapse
- BASE_URL =
"https://api.xero.com"- DEFAULT_USER_AGENT =
"XeroKiwi/#{XeroKiwi::VERSION} (+https://github.com/douglasgreyling/xero-kiwi)".freeze
- RETRY_STATUSES =
HTTP statuses we treat as transient. faraday-retry honours Retry-After automatically when the status is in this list.
[429, 502, 503, 504].freeze
- DEFAULT_RETRY_OPTIONS =
{ max: 4, interval: 0.5, interval_randomness: 0.5, backoff_factor: 2, retry_statuses: RETRY_STATUSES, methods: %i[get head options put delete post], # Faraday::RetriableResponse is the *internal* signal faraday-retry uses # to flag a status-code retry. It MUST be in this list, or the middleware # can't catch its own retry signal and 429s/503s never get retried. exceptions: [ Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::RetriableResponse, Errno::ETIMEDOUT ] }.freeze
Instance Attribute Summary collapse
-
#token ⇒ Object
readonly
Returns the value of attribute token.
Instance Method Summary collapse
-
#branding_theme(tenant_id, branding_theme_id) ⇒ Object
Fetches a single Branding Theme by ID for the given tenant.
-
#branding_themes(tenant_id) ⇒ Object
Fetches the Branding Themes for the given tenant.
-
#can_refresh? ⇒ Boolean
True if this client was constructed with refresh credentials AND the current token still carries a refresh_token to use.
-
#connections ⇒ Object
Fetches the list of tenants the current access token has access to.
-
#contact(tenant_id, contact_id) ⇒ Object
Fetches a single Contact by ID for the given tenant.
-
#contact_group(tenant_id, contact_group_id) ⇒ Object
Fetches a single Contact Group by ID for the given tenant.
-
#contact_groups(tenant_id) ⇒ Object
Fetches the Contact Groups for the given tenant.
-
#contacts(tenant_id) ⇒ Object
Fetches the Contacts for the given tenant.
-
#credit_note(tenant_id, credit_note_id) ⇒ Object
Fetches a single Credit Note by ID for the given tenant.
-
#credit_notes(tenant_id) ⇒ Object
Fetches the Credit Notes for the given tenant.
-
#delete_connection(connection_or_id) ⇒ Object
Disconnects a tenant.
-
#initialize(access_token:, refresh_token: nil, expires_at: nil, client_id: nil, client_secret: nil, on_token_refresh: nil, adapter: nil, user_agent: DEFAULT_USER_AGENT, retry_options: {}) ⇒ Client
constructor
A new instance of Client.
-
#invoice(tenant_id, invoice_id) ⇒ Object
Fetches a single Invoice by ID for the given tenant.
-
#invoices(tenant_id) ⇒ Object
Fetches the Invoices for the given tenant.
-
#online_invoice_url(tenant_id, invoice_id) ⇒ Object
Fetches the online invoice URL for a sales (ACCREC) invoice.
-
#organisation(tenant_id) ⇒ Object
Fetches the Organisation for the given tenant.
-
#overpayment(tenant_id, overpayment_id) ⇒ Object
Fetches a single Overpayment by ID for the given tenant.
-
#overpayments(tenant_id) ⇒ Object
Fetches the Overpayments for the given tenant.
-
#payment(tenant_id, payment_id) ⇒ Object
Fetches a single Payment by ID for the given tenant.
-
#payments(tenant_id) ⇒ Object
Fetches the Payments for the given tenant.
-
#prepayment(tenant_id, prepayment_id) ⇒ Object
Fetches a single Prepayment by ID for the given tenant.
-
#prepayments(tenant_id) ⇒ Object
Fetches the Prepayments for the given tenant.
-
#refresh_token! ⇒ Object
Forces a refresh regardless of expiry.
-
#revoke_token! ⇒ Object
Revokes the current refresh token at Xero, invalidating it and every access token issued from it.
-
#user(tenant_id, user_id) ⇒ Object
Fetches a single User by ID for the given tenant.
-
#users(tenant_id) ⇒ Object
Fetches the Users for the given tenant.
Constructor Details
#initialize(access_token:, refresh_token: nil, expires_at: nil, client_id: nil, client_secret: nil, on_token_refresh: nil, adapter: nil, user_agent: DEFAULT_USER_AGENT, retry_options: {}) ⇒ Client
Returns a new instance of Client.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/xero_kiwi/client.rb', line 56 def initialize( access_token:, refresh_token: nil, expires_at: nil, client_id: nil, client_secret: nil, on_token_refresh: nil, adapter: nil, user_agent: DEFAULT_USER_AGENT, retry_options: {} ) @token = Token.new( access_token: access_token, refresh_token: refresh_token, expires_at: expires_at ) @client_id = client_id @client_secret = client_secret @on_token_refresh = on_token_refresh @adapter = adapter @user_agent = user_agent @retry_options = DEFAULT_RETRY_OPTIONS.merge() @refresh_mutex = Mutex.new end |
Instance Attribute Details
#token ⇒ Object (readonly)
Returns the value of attribute token.
54 55 56 |
# File 'lib/xero_kiwi/client.rb', line 54 def token @token end |
Instance Method Details
#branding_theme(tenant_id, branding_theme_id) ⇒ Object
Fetches a single Branding Theme by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/brandingthemes
387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/xero_kiwi/client.rb', line 387 def branding_theme(tenant_id, branding_theme_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "branding_theme_id is required" if branding_theme_id.nil? || branding_theme_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/BrandingThemes/#{branding_theme_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::BrandingTheme.from_response(response.body).first end end |
#branding_themes(tenant_id) ⇒ Object
Fetches the Branding Themes for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/brandingthemes
372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/xero_kiwi/client.rb', line 372 def branding_themes(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/BrandingThemes") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::BrandingTheme.from_response(response.body) end end |
#can_refresh? ⇒ Boolean
True if this client was constructed with refresh credentials AND the current token still carries a refresh_token to use.
437 438 439 |
# File 'lib/xero_kiwi/client.rb', line 437 def can_refresh? !@client_id.nil? && !@client_secret.nil? && @token.refreshable? end |
#connections ⇒ Object
Fetches the list of tenants the current access token has access to. See: developer.xero.com/documentation/best-practices/managing-connections/connections
83 84 85 86 87 88 |
# File 'lib/xero_kiwi/client.rb', line 83 def connections with_authenticated_request do response = http.get("/connections") Connection.from_response(response.body) end end |
#contact(tenant_id, contact_id) ⇒ Object
Fetches a single Contact by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/contacts
154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/xero_kiwi/client.rb', line 154 def contact(tenant_id, contact_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "contact_id is required" if contact_id.nil? || contact_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Contacts/#{contact_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Contact.from_response(response.body).first end end |
#contact_group(tenant_id, contact_group_id) ⇒ Object
Fetches a single Contact Group by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/contactgroups
185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/xero_kiwi/client.rb', line 185 def contact_group(tenant_id, contact_group_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "contact_group_id is required" if contact_group_id.nil? || contact_group_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/ContactGroups/#{contact_group_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::ContactGroup.from_response(response.body).first end end |
#contact_groups(tenant_id) ⇒ Object
Fetches the Contact Groups for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/contactgroups
170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/xero_kiwi/client.rb', line 170 def contact_groups(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/ContactGroups") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::ContactGroup.from_response(response.body) end end |
#contacts(tenant_id) ⇒ Object
Fetches the Contacts for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/contacts
139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/xero_kiwi/client.rb', line 139 def contacts(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Contacts") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Contact.from_response(response.body) end end |
#credit_note(tenant_id, credit_note_id) ⇒ Object
Fetches a single Credit Note by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/creditnotes
247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/xero_kiwi/client.rb', line 247 def credit_note(tenant_id, credit_note_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "credit_note_id is required" if credit_note_id.nil? || credit_note_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/CreditNotes/#{credit_note_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::CreditNote.from_response(response.body).first end end |
#credit_notes(tenant_id) ⇒ Object
Fetches the Credit Notes for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/creditnotes
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/xero_kiwi/client.rb', line 232 def credit_notes(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/CreditNotes") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::CreditNote.from_response(response.body) end end |
#delete_connection(connection_or_id) ⇒ Object
Disconnects a tenant. Accepts either a XeroKiwi::Connection (we use its ‘id`) or a raw connection-id string. Returns true on the 204. The access token may still be valid for other connections after this —only the named tenant is detached.
404 405 406 407 408 409 410 411 412 |
# File 'lib/xero_kiwi/client.rb', line 404 def delete_connection(connection_or_id) id = extract_connection_id(connection_or_id) raise ArgumentError, "connection id is required" if id.nil? || id.empty? with_authenticated_request do http.delete("/connections/#{id}") true end end |
#invoice(tenant_id, invoice_id) ⇒ Object
Fetches a single Invoice by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/invoices
340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/xero_kiwi/client.rb', line 340 def invoice(tenant_id, invoice_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "invoice_id is required" if invoice_id.nil? || invoice_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Invoices/#{invoice_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Invoice.from_response(response.body).first end end |
#invoices(tenant_id) ⇒ Object
Fetches the Invoices for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/invoices
325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/xero_kiwi/client.rb', line 325 def invoices(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Invoices") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Invoice.from_response(response.body) end end |
#online_invoice_url(tenant_id, invoice_id) ⇒ Object
Fetches the online invoice URL for a sales (ACCREC) invoice. Returns the URL string, or nil if not available. Cannot be used on DRAFT invoices. See: developer.xero.com/documentation/api/accounting/invoices
356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/xero_kiwi/client.rb', line 356 def online_invoice_url(tenant_id, invoice_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "invoice_id is required" if invoice_id.nil? || invoice_id.to_s.empty? data = with_authenticated_request do http.get("/api.xro/2.0/Invoices/#{invoice_id}/OnlineInvoice") do |req| req.headers["Xero-Tenant-Id"] = tid end end data.body.dig("OnlineInvoices", 0, "OnlineInvoiceUrl") end |
#organisation(tenant_id) ⇒ Object
Fetches the Organisation for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/organisation
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/xero_kiwi/client.rb', line 93 def organisation(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Organisation") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Organisation.from_response(response.body) end end |
#overpayment(tenant_id, overpayment_id) ⇒ Object
Fetches a single Overpayment by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/overpayments
278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/xero_kiwi/client.rb', line 278 def overpayment(tenant_id, overpayment_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "overpayment_id is required" if overpayment_id.nil? || overpayment_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Overpayments/#{overpayment_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Overpayment.from_response(response.body).first end end |
#overpayments(tenant_id) ⇒ Object
Fetches the Overpayments for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/overpayments
263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/xero_kiwi/client.rb', line 263 def overpayments(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Overpayments") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Overpayment.from_response(response.body) end end |
#payment(tenant_id, payment_id) ⇒ Object
Fetches a single Payment by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/payments
309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/xero_kiwi/client.rb', line 309 def payment(tenant_id, payment_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "payment_id is required" if payment_id.nil? || payment_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Payments/#{payment_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Payment.from_response(response.body).first end end |
#payments(tenant_id) ⇒ Object
Fetches the Payments for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/payments
294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/xero_kiwi/client.rb', line 294 def payments(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Payments") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Payment.from_response(response.body) end end |
#prepayment(tenant_id, prepayment_id) ⇒ Object
Fetches a single Prepayment by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/prepayments
216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/xero_kiwi/client.rb', line 216 def prepayment(tenant_id, prepayment_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "prepayment_id is required" if prepayment_id.nil? || prepayment_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Prepayments/#{prepayment_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Prepayment.from_response(response.body).first end end |
#prepayments(tenant_id) ⇒ Object
Fetches the Prepayments for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/prepayments
201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/xero_kiwi/client.rb', line 201 def prepayments(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Prepayments") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::Prepayment.from_response(response.body) end end |
#refresh_token! ⇒ Object
Forces a refresh regardless of expiry. Returns the new Token. Raises TokenRefreshError if refresh credentials are missing or if Xero rejects the refresh.
429 430 431 432 433 |
# File 'lib/xero_kiwi/client.rb', line 429 def refresh_token! raise TokenRefreshError.new(nil, nil, "client has no refresh capability") unless can_refresh? @refresh_mutex.synchronize { perform_refresh } end |
#revoke_token! ⇒ Object
Revokes the current refresh token at Xero, invalidating it and every access token issued from it. Use this for “disconnect Xero” / logout flows. After this call, treat the client as dead — subsequent API calls will 401. The caller is responsible for cleaning up any persisted credential record.
419 420 421 422 423 424 |
# File 'lib/xero_kiwi/client.rb', line 419 def revoke_token! raise TokenRefreshError.new(nil, nil, "client has no refresh capability") unless can_refresh? revoker.revoke_token(refresh_token: @token.refresh_token) true end |
#user(tenant_id, user_id) ⇒ Object
Fetches a single User by ID for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/users
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/xero_kiwi/client.rb', line 123 def user(tenant_id, user_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? raise ArgumentError, "user_id is required" if user_id.nil? || user_id.to_s.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Users/#{user_id}") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::User.from_response(response.body).first end end |
#users(tenant_id) ⇒ Object
Fetches the Users for the given tenant. Accepts a tenant-id string or a XeroKiwi::Connection (we use its tenant_id). See: developer.xero.com/documentation/api/accounting/users
108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/xero_kiwi/client.rb', line 108 def users(tenant_id) tid = extract_tenant_id(tenant_id) raise ArgumentError, "tenant_id is required" if tid.nil? || tid.empty? with_authenticated_request do response = http.get("/api.xro/2.0/Users") do |req| req.headers["Xero-Tenant-Id"] = tid end Accounting::User.from_response(response.body) end end |