Class: QuickbooksApiService
- Inherits:
-
Object
- Object
- QuickbooksApiService
- Defined in:
- app/services/quickbooks_api_service.rb
Overview
QuickbooksApiService provides methods to interact with the QuickBooks Online API.
The service handles OAuth2 authentication, token management, and API operations for integrating with QuickBooks Online. It manages the authorization flow, automatically refreshes expired tokens, and provides methods to interact with QuickBooks resources.
Example usage:
# Get the authorization URL for QuickBooks OAuth flow
auth_url = QuickbooksApiService.(state: 'your-state-param')
# Initialize the service for a specific company
service = QuickbooksApiService.new(company_id: company.id)
# Complete integration after receiving authorization code
integration = service.integrate(code: 'authorization_code', realm_id: 'quickbooks_company_id')
# Get company integration information
company_integration = service.get_company_integration
# Fetch paginated resources
service.get_each_paginated_resource(
path: 'Invoice',
query: "SELECT * FROM Invoice WHERE Balance > '0'"
) do |invoice|
# Process each invoice
puts invoice['Id']
end
# Get invoices for a specific customer
invoices = service.get_invoices_for_customer(customer_id: 'customer_ref_id')
# Update an invoice
updated_invoice = service.update_invoice(
invoice_id: 'invoice_id',
attributes: { 'CustomField' => [{ 'Name' => 'SPS Ready', 'StringValue' => 'Y' }] }
)
Constant Summary collapse
- CLIENT_ID =
QuickBooks API credentials and endpoints from Rails credentials These should be stored in config/credentials.yml.enc
Rails.application.credentials.quickbooks_client_id
- CLIENT_SECRET =
Rails.application.credentials.quickbooks_client_secret
- REDIRECT_URI =
Rails.application.credentials.quickbooks_redirect_uri
- AUTH_URL =
Rails.application.credentials.quickbooks_auth_url
- TOKEN_URL =
Rails.application.credentials.quickbooks_token_url
- API_BASE_URL =
Rails.application.credentials.quickbooks_api_url
- SCOPE =
Rails.application.credentials.quickbooks_scope
- MINIMUM_TOKEN_REMAINING_TIME =
Minimum time remaining before token expiration to trigger a refresh Tokens will be refreshed if they expire within this time window
15.minutes.to_i
Instance Attribute Summary collapse
-
#company_id ⇒ Object
readonly
The company ID that this service instance is working with.
Class Method Summary collapse
-
.consent_url(state:) ⇒ String
Builds the QuickBooks OAuth consent URL.
Instance Method Summary collapse
-
#get_company_integration ⇒ CompanyIntegration
Retrieves the company integration record.
-
#get_each_paginated_resource(path:, query:) {|resource| ... } ⇒ void
Fetches paginated resources from QuickBooks API.
-
#get_invoices_for_customer(customer_id:) {|invoice| ... } ⇒ Array?
Retrieves all invoices for a specific customer with a zero balance.
-
#initialize(company_id:) ⇒ QuickbooksApiService
constructor
Initializes a new QuickbooksApiService instance.
-
#integrate(code:, realm_id:) ⇒ CompanyIntegration
Completes the OAuth integration process after receiving an authorization code.
-
#update_invoice(invoice_id:, attributes:) ⇒ Hash
Updates an invoice in QuickBooks with the given attributes.
Constructor Details
#initialize(company_id:) ⇒ QuickbooksApiService
Initializes a new QuickbooksApiService instance
87 88 89 |
# File 'app/services/quickbooks_api_service.rb', line 87 def initialize(company_id:) @company_id = company_id end |
Instance Attribute Details
#company_id ⇒ Object (readonly)
The company ID that this service instance is working with
82 83 84 |
# File 'app/services/quickbooks_api_service.rb', line 82 def company_id @company_id end |
Class Method Details
.consent_url(state:) ⇒ String
Builds the QuickBooks OAuth consent URL
This URL is used to redirect users to the QuickBooks authorization page where they can authorize our application to access their QuickBooks data.
66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'app/services/quickbooks_api_service.rb', line 66 def (state:) URI::HTTPS.build( host: URI(AUTH_URL).host, path: URI(AUTH_URL).path, query: URI.encode_www_form( response_type: 'code', client_id: CLIENT_ID, redirect_uri: REDIRECT_URI, scope: SCOPE, state: state ) ).to_s end |
Instance Method Details
#get_company_integration ⇒ CompanyIntegration
Retrieves the company integration record
184 185 186 |
# File 'app/services/quickbooks_api_service.rb', line 184 def get_company_integration company_integration end |
#get_each_paginated_resource(path:, query:) {|resource| ... } ⇒ void
This method returns an undefined value.
Fetches paginated resources from QuickBooks API
This method handles pagination automatically and yields each resource to the provided block. It continues fetching pages until no more results are available or the maximum results per page is not reached.
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'app/services/quickbooks_api_service.rb', line 126 def get_each_paginated_resource(path:, query:, &block) tokens = valid_tokens realm_id = tokens['realm_id'] # start_position and max_results are used for pagination they should be strings start_position = '1' max_results = '1000' # 1000 is the maximum allowed by QuickBooks loop do paginated_query = "#{query} STARTPOSITION #{start_position} MAXRESULTS #{max_results}" response = Faraday.get("#{API_BASE_URL}/#{realm_id}/query") do |req| req.headers['Authorization'] = "Bearer #{tokens['access_token']}" req.headers['Accept'] = 'application/json' req.headers['Content-Type'] = 'application/text' req.params['query'] = paginated_query end raise "Get #{path} failed: #{response.body}" unless response.success? parsed = JSON.parse(response.body) resources = parsed.dig('QueryResponse', path.capitalize) break if resources.blank? resources.each(&block) break unless resources.size == max_results start_position += max_results end end |
#get_invoices_for_customer(customer_id:) {|invoice| ... } ⇒ Array?
Retrieves all invoices for a specific customer with a zero balance
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'app/services/quickbooks_api_service.rb', line 162 def get_invoices_for_customer(customer_id:) # get_each_paginated_resource( # path: 'Invoice', # query: "SELECT * FROM Invoice WHERE CustomerRef = '#{customer_id}' AND Balance = '0'" # ) do |invoice| # custom_fields = invoice['CustomField'] || [] # sps_ready = custom_fields.find { |field| field['Name'] == 'SPS Ready' }&.dig('StringValue') # yield invoice if sps_ready == 'Y' # end # This method retrieves all invoices for a specific customer that have a balance of 0 without filtering by SPS Ready. get_each_paginated_resource( path: 'Invoice', query: "SELECT * FROM Invoice WHERE CustomerRef = '#{customer_id}' AND Balance = '0'" ) do |invoice| yield invoice if block_given? end end |
#integrate(code:, realm_id:) ⇒ CompanyIntegration
Completes the OAuth integration process after receiving an authorization code
This method exchanges the authorization code for access/refresh tokens and creates or updates the company integration record.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'app/services/quickbooks_api_service.rb', line 99 def integrate(code:, realm_id:) tokens = exchange_code_for_tokens(code) integration = CompanyIntegration.find_or_initialize_by( company_id: company_id, integration_id: Integration.quickbooks.id ) integration.assign_attributes( credentials: tokens.merge('realm_id' => realm_id), active: true ) integration.save! integration end |
#update_invoice(invoice_id:, attributes:) ⇒ Hash
Updates an invoice in QuickBooks with the given attributes
This method first retrieves the current invoice to get the SyncToken, then merges the provided attributes and sends the update to QuickBooks. The SyncToken is included to prevent concurrency issues.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/services/quickbooks_api_service.rb', line 197 def update_invoice(invoice_id:, attributes:) tokens = valid_tokens realm_id = tokens['realm_id'] # First, we need to get the current invoice to merge with our updates response = Faraday.get("#{API_BASE_URL}/#{realm_id}/invoice/#{invoice_id}") do |req| req.headers['Authorization'] = "Bearer #{tokens['access_token']}" req.headers['Accept'] = 'application/json' req.headers['Content-Type'] = 'application/json' end raise "Get invoice failed: #{response.body}" unless response.success? current_invoice = JSON.parse(response.body)['Invoice'] # Merge the current invoice with our updates # We need to include the SyncToken to avoid concurrency issues update_data = { 'Id' => invoice_id, 'SyncToken' => current_invoice['SyncToken'], 'sparse' => true }.merge(attributes) # POST the updated invoice back to QuickBooks response = Faraday.post("#{API_BASE_URL}/#{realm_id}/invoice") do |req| req.headers['Authorization'] = "Bearer #{tokens['access_token']}" req.headers['Accept'] = 'application/json' req.headers['Content-Type'] = 'application/json' req.body = update_data.to_json end raise "Update invoice failed: #{response.body}" unless response.success? JSON.parse(response.body)['Invoice'] end |