quickbooks-ruby 0.4.0 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +5 -5
  2. data/lib/quickbooks-ruby.rb +65 -13
  3. data/lib/quickbooks/faraday/middleware/gzip.rb +74 -0
  4. data/lib/quickbooks/model/base_model.rb +13 -2
  5. data/lib/quickbooks/model/batch_request.rb +11 -6
  6. data/lib/quickbooks/model/batch_response.rb +11 -6
  7. data/lib/quickbooks/model/bill.rb +3 -0
  8. data/lib/quickbooks/model/change_data_capture.rb +50 -0
  9. data/lib/quickbooks/model/company_currency.rb +19 -0
  10. data/lib/quickbooks/model/company_info.rb +2 -1
  11. data/lib/quickbooks/model/custom_field.rb +18 -0
  12. data/lib/quickbooks/model/customer.rb +6 -1
  13. data/lib/quickbooks/model/customer_type.rb +15 -0
  14. data/lib/quickbooks/model/deposit_line_detail.rb +2 -2
  15. data/lib/quickbooks/model/description_line_detail.rb +7 -0
  16. data/lib/quickbooks/model/effective_tax_rate.rb +15 -0
  17. data/lib/quickbooks/model/exchange_rate.rb +23 -0
  18. data/lib/quickbooks/model/group_line_detail.rb +2 -1
  19. data/lib/quickbooks/model/invoice.rb +7 -1
  20. data/lib/quickbooks/model/invoice_change.rb +2 -1
  21. data/lib/quickbooks/model/invoice_group_line_detail.rb +14 -0
  22. data/lib/quickbooks/model/invoice_line_item.rb +34 -4
  23. data/lib/quickbooks/model/item.rb +11 -3
  24. data/lib/quickbooks/model/item_group_detail.rb +9 -0
  25. data/lib/quickbooks/model/item_group_line.rb +20 -0
  26. data/lib/quickbooks/model/line.rb +12 -0
  27. data/lib/quickbooks/model/line_ex.rb +9 -0
  28. data/lib/quickbooks/model/linked_transaction.rb +7 -0
  29. data/lib/quickbooks/model/physical_address.rb +2 -2
  30. data/lib/quickbooks/model/preferences.rb +28 -4
  31. data/lib/quickbooks/model/purchase_change.rb +7 -0
  32. data/lib/quickbooks/model/purchase_line_item.rb +1 -0
  33. data/lib/quickbooks/model/purchase_order.rb +3 -1
  34. data/lib/quickbooks/model/refund_receipt_change.rb +8 -0
  35. data/lib/quickbooks/model/report.rb +9 -7
  36. data/lib/quickbooks/model/sales_receipt.rb +4 -2
  37. data/lib/quickbooks/model/tax_rate.rb +1 -1
  38. data/lib/quickbooks/model/time_activity.rb +2 -1
  39. data/lib/quickbooks/model/transfer.rb +21 -0
  40. data/lib/quickbooks/model/vendor.rb +1 -0
  41. data/lib/quickbooks/service/access_token.rb +12 -11
  42. data/lib/quickbooks/service/base_service.rb +148 -24
  43. data/lib/quickbooks/service/batch.rb +1 -1
  44. data/lib/quickbooks/service/change_data_capture.rb +24 -0
  45. data/lib/quickbooks/service/company_currency.rb +18 -0
  46. data/lib/quickbooks/service/credit_memo.rb +6 -0
  47. data/lib/quickbooks/service/custom_field.rb +20 -0
  48. data/lib/quickbooks/service/customer.rb +5 -0
  49. data/lib/quickbooks/service/customer_type.rb +20 -0
  50. data/lib/quickbooks/service/estimate.rb +6 -0
  51. data/lib/quickbooks/service/exchange_rate.rb +25 -0
  52. data/lib/quickbooks/service/invoice.rb +24 -1
  53. data/lib/quickbooks/service/item.rb +5 -0
  54. data/lib/quickbooks/service/payment.rb +13 -0
  55. data/lib/quickbooks/service/preferences.rb +1 -1
  56. data/lib/quickbooks/service/purchase_change.rb +16 -0
  57. data/lib/quickbooks/service/purchase_order.rb +16 -0
  58. data/lib/quickbooks/service/refund_receipt_change.rb +16 -0
  59. data/lib/quickbooks/service/responses/methods.rb +17 -0
  60. data/lib/quickbooks/service/responses/oauth2_http_response.rb +43 -0
  61. data/lib/quickbooks/service/responses/oauth_http_response.rb +17 -0
  62. data/lib/quickbooks/service/sales_receipt.rb +30 -0
  63. data/lib/quickbooks/service/service_crud.rb +21 -0
  64. data/lib/quickbooks/service/service_crud_json.rb +1 -1
  65. data/lib/quickbooks/service/transfer.rb +16 -0
  66. data/lib/quickbooks/service/upload.rb +5 -1
  67. data/lib/quickbooks/util/logging.rb +4 -0
  68. data/lib/quickbooks/util/multipart.rb +2 -65
  69. data/lib/quickbooks/util/name_entity.rb +2 -1
  70. data/lib/quickbooks/util/query_builder.rb +6 -5
  71. data/lib/quickbooks/version.rb +1 -1
  72. metadata +73 -62
@@ -0,0 +1,9 @@
1
+
2
+ module Quickbooks
3
+ module Model
4
+ class LineEx < BaseModel
5
+
6
+ xml_accessor :name_values, :from => "NameValue", :as => [NameValue]
7
+ end
8
+ end
9
+ end
@@ -1,9 +1,16 @@
1
1
  module Quickbooks
2
2
  module Model
3
3
  class LinkedTransaction < BaseModel
4
+ #== Constants
5
+ DEFAULT_TXN_TYPE = 'BillPaymentCheck'.freeze
6
+
4
7
  xml_accessor :txn_id, :from => 'TxnId'
5
8
  xml_accessor :txn_type, :from => 'TxnType'
6
9
  xml_accessor :txn_line_id, :from => 'TxnLineId'
10
+
11
+ def bill_payment_check_type?
12
+ txn_type.to_s == DEFAULT_TXN_TYPE
13
+ end
7
14
  end
8
15
  end
9
16
  end
@@ -20,11 +20,11 @@ module Quickbooks
20
20
  end
21
21
 
22
22
  def lat_to_f
23
- BigDecimal.new(lat)
23
+ BigDecimal(lat)
24
24
  end
25
25
 
26
26
  def lon_to_f
27
- BigDecimal.new(lon)
27
+ BigDecimal(lon)
28
28
  end
29
29
 
30
30
  def have_lat?
@@ -19,16 +19,40 @@ module Quickbooks
19
19
  PREFERENCE_SECTIONS = {
20
20
  :accounting_info => %w(TrackDepartments DepartmentTerminology ClassTrackingPerTxnLine? ClassTrackingPerTxn? CustomerTerminology),
21
21
  :product_and_services => %w(ForSales? ForPurchase? QuantityWithPriceAndRate? QuantityOnHand?),
22
- :sales_forms => %w(CustomTxnNumbers? AllowDeposit? AllowDiscount? DefaultDiscountAccount? AllowEstimates? EstimateMessage?
23
- ETransactionEnabledStatus? ETransactionAttachPDF? ETransactionPaymentEnabled? IPNSupportEnabled?
24
- AllowServiceDate? AllowShipping? DefaultShippingAccount? DefaultTerms DefaultCustomerMessage),
25
22
  :vendor_and_purchase => %w(TrackingByCustomer? BillableExpenseTracking? DefaultTerms? DefaultMarkup? POCustomField),
26
23
  :time_tracking => %w(UseServices? BillCustomers? ShowBillRateToAll WorkWeekStartDate MarkTimeEntiresBillable?),
27
- :tax => %w(UsingSalesTax?),
24
+ :tax => %w(UsingSalesTax? PartnerTaxEnabled?),
28
25
  :currency => %w(MultiCurrencyEnabled? HomeCurrency),
29
26
  :report => %w(ReportBasis)
30
27
  }
31
28
 
29
+ xml_reader :sales_forms, :from => "SalesFormsPrefs", :as => create_preference_class(*%w(
30
+ AllowDeposit?
31
+ AllowDiscount?
32
+ AllowEstimates?
33
+ AllowServiceDate?
34
+ AllowShipping?
35
+ AutoApplyCredit?
36
+ CustomField?
37
+ CustomTxnNumbers?
38
+ DefaultCustomerMessage
39
+ DefaultDiscountAccount?
40
+ DefaultShippingAccount?
41
+ DefaultTerms
42
+ EmailCopyToCompany?
43
+ EstimateMessage
44
+ ETransactionAttachPDF?
45
+ ETransactionEnabledStatus
46
+ ETransactionPaymentEnabled?
47
+ IPNSupportEnabled?
48
+ SalesEmailBcc
49
+ SalesEmailCc
50
+ UsingPriceLevels?
51
+ UsingProgressInvoicing?
52
+ )) {
53
+ xml_reader :custom_fields, :as => [CustomField], :from => 'CustomField', in: 'CustomField'
54
+ }
55
+
32
56
  PREFERENCE_SECTIONS.each do |section_name, fields|
33
57
  xml_reader section_name, :from => "#{section_name}_prefs".camelize, :as => create_preference_class(*fields)
34
58
  end
@@ -0,0 +1,7 @@
1
+ module Quickbooks
2
+ module Model
3
+ class PurchaseChange < ChangeModel
4
+ XML_NODE = "Purchase"
5
+ end
6
+ end
7
+ end
@@ -12,6 +12,7 @@ module Quickbooks
12
12
  xml_accessor :description, :from => 'Description'
13
13
  xml_accessor :amount, :from => 'Amount', :as => BigDecimal, :to_xml => Proc.new { |val| val.to_f }
14
14
  xml_accessor :detail_type, :from => 'DetailType'
15
+ xml_accessor :received, :from => 'Received', :as => BigDecimal, :to_xml => to_xml_big_decimal
15
16
 
16
17
  #== Various detail types
17
18
  xml_accessor :account_based_expense_line_detail, :from => ACCOUNT_BASED_EXPENSE_LINE_DETAIL, :as => AccountBasedExpenseLineDetail
@@ -16,6 +16,7 @@ module Quickbooks
16
16
  xml_accessor :txn_date, :from => 'TxnDate', :as => Date
17
17
  xml_accessor :custom_fields, :from => 'CustomField', :as => [CustomField]
18
18
  xml_accessor :private_note, :from => 'PrivateNote'
19
+ xml_accessor :memo, :from => 'Memo'
19
20
 
20
21
  xml_accessor :linked_transactions, :from => 'LinkedTxn', :as => [LinkedTransaction]
21
22
  xml_accessor :line_items, :from => 'Line', :as => [PurchaseLineItem]
@@ -23,10 +24,11 @@ module Quickbooks
23
24
  xml_accessor :attachable_ref, :from => 'AttachableRef', :as => BaseReference
24
25
  xml_accessor :vendor_ref, :from => 'VendorRef', :as => BaseReference
25
26
  xml_accessor :ap_account_ref, :from => 'APAccountRef', :as => BaseReference
27
+ xml_accessor :po_email, from: "POEmail", as: EmailAddress
26
28
  xml_accessor :class_ref, :from => 'ClassRef', :as => BaseReference
27
29
  xml_accessor :sales_term_ref, :from => 'SalesTermRef', :as => BaseReference
28
30
 
29
- xml_accessor :total, :from => 'TotalAmt', :as => BigDecimal, :to_xml => Proc.new { |val| val.to_f }
31
+ xml_accessor :total, :from => 'TotalAmt', :as => BigDecimal, :to_xml => to_xml_big_decimal
30
32
  xml_accessor :due_date, :from => 'DueDate', :as => Date
31
33
  xml_accessor :vendor_address, :from => 'VendorAddr', :as => PhysicalAddress
32
34
  xml_accessor :ship_address, :from => 'ShipAddr', :as => PhysicalAddress
@@ -0,0 +1,8 @@
1
+ module Quickbooks
2
+ module Model
3
+ # Refer to: https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/change_data_capture
4
+ class RefundReceiptChange < ChangeModel
5
+ XML_NODE = "RefundReceipt"
6
+ end
7
+ end
8
+ end
@@ -37,16 +37,18 @@ module Quickbooks
37
37
  row_node.elements.map.with_index do |el, i|
38
38
  value = el.attr('value')
39
39
 
40
- if i.zero? # Return the first column as a string, its the label.
41
- value
42
- elsif value.blank?
43
- nil
44
- else
45
- BigDecimal(value)
46
- end
40
+ next nil if value.blank?
41
+
42
+ parse_row_value(value)
47
43
  end
48
44
  end
49
45
 
46
+ def parse_row_value(value)
47
+ BigDecimal(value)
48
+ rescue ArgumentError
49
+ value
50
+ end
51
+
50
52
  end
51
53
  end
52
54
  end
@@ -20,6 +20,7 @@ module Quickbooks
20
20
  xml_accessor :department_ref, :from => 'DepartmentRef', :as => BaseReference
21
21
  xml_accessor :bill_email, :from => 'BillEmail', :as => EmailAddress
22
22
  xml_accessor :bill_address, :from => 'BillAddr', :as => PhysicalAddress
23
+ xml_accessor :delivery_info, :from => 'DeliveryInfo', :as => DeliveryInfo
23
24
  xml_accessor :ship_address, :from => 'ShipAddr', :as => PhysicalAddress
24
25
  xml_accessor :po_number, :from => 'PONumber'
25
26
  xml_accessor :ship_method_ref, :from => 'ShipMethodRef', :as => BaseReference
@@ -54,9 +55,10 @@ module Quickbooks
54
55
  validate :line_item_size
55
56
  validate :document_numbering
56
57
 
57
- def email=(email)
58
- self.bill_email = EmailAddress.new(email)
58
+ def billing_email_address=(email_address_string)
59
+ self.bill_email = EmailAddress.new(email_address_string)
59
60
  end
61
+ alias_method :email=, :billing_email_address= # backward backward compatibility to v0.4.6
60
62
 
61
63
  end
62
64
  end
@@ -16,7 +16,7 @@ module Quickbooks
16
16
  xml_accessor :tax_return_line_ref, :from => "TaxReturnLineRef", :as => BaseReference
17
17
  xml_accessor :special_tax_type, :from => "SpecialTaxType"
18
18
  xml_accessor :display_type, :from => "DisplayType"
19
- xml_accessor :effective_tax_rate, :from => "EffectiveTaxRate"
19
+ xml_accessor :effective_tax_rate, :from => "EffectiveTaxRate", :as => [EffectiveTaxRate]
20
20
 
21
21
  validates_presence_of :name, :rate_value
22
22
 
@@ -43,10 +43,11 @@ module Quickbooks
43
43
  validates_inclusion_of :name_of, :in => NAMEOF_OPTIONS
44
44
  validate :existence_of_employee_ref, :if => Proc.new { |ta| ta.name_of == "Employee" }
45
45
  validate :existence_of_vendor_ref, :if => Proc.new { |ta| ta.name_of == "Vendor" }
46
+ validates :description, length: { maximum: 4000 }
46
47
 
47
48
  def existence_of_employee_ref
48
49
  if employee_ref.nil? || (employee_ref && employee_ref.value == 0)
49
- errors.add(:employee_ref, "VendorRef is required and must be a non-zero value.")
50
+ errors.add(:employee_ref, "EmployeeRef is required and must be a non-zero value.")
50
51
  end
51
52
  end
52
53
 
@@ -0,0 +1,21 @@
1
+ module Quickbooks
2
+ module Model
3
+ class Transfer < BaseModel
4
+ XML_COLLECTION_NODE = "Transfer"
5
+ XML_NODE = "Transfer"
6
+ REST_RESOURCE = 'transfer'
7
+
8
+ xml_accessor :id, :from => 'Id'
9
+ xml_accessor :sync_token, :from => 'SyncToken', :as => Integer
10
+ xml_accessor :meta_data, :from => 'MetaData', :as => MetaData
11
+ xml_accessor :txn_date, :from => 'TxnDate', :as => Date
12
+ xml_accessor :currency_ref, :from => 'CurrencyRef', :as => BaseReference
13
+ xml_accessor :private_note, :from => 'PrivateNote'
14
+ xml_accessor :from_account_ref, :from => 'FromAccountRef', :as => BaseReference
15
+ xml_accessor :to_account_ref, :from => 'ToAccountRef', :as => BaseReference
16
+ xml_accessor :amount, :from => 'Amount', :as => BigDecimal, :to_xml => to_xml_big_decimal
17
+
18
+ reference_setters :currency_ref, :to_account_ref, :from_account_ref
19
+ end
20
+ end
21
+ end
@@ -41,6 +41,7 @@ module Quickbooks
41
41
  xml_accessor :account_number, :from => 'AcctNum'
42
42
  xml_accessor :is_1099?, :from => 'Vendor1099'
43
43
  xml_accessor :currency_ref, :from => 'CurrencyRef', :as => BaseReference
44
+ xml_accessor :bill_rate, from: "BillRate", as: BigDecimal, to_xml: to_xml_big_decimal
44
45
 
45
46
  #== Validations
46
47
  validate :names_cannot_contain_invalid_characters
@@ -3,7 +3,7 @@ module Quickbooks
3
3
  class AccessToken < BaseService
4
4
 
5
5
  RENEW_URL = "https://appcenter.intuit.com/api/v1/connection/reconnect"
6
- DISCONNECT_URL = "https://appcenter.intuit.com/api/v1/connection/disconnect"
6
+ DISCONNECT_URL = "https://developer.api.intuit.com/v2/oauth2/tokens/revoke"
7
7
 
8
8
  # https://developer.intuit.com/docs/0025_quickbooksapi/0053_auth_auth/oauth_management_api#Reconnect
9
9
  def renew
@@ -21,18 +21,19 @@ module Quickbooks
21
21
 
22
22
  # https://developer.intuit.com/docs/0025_quickbooksapi/0053_auth_auth/oauth_management_api#Disconnect
23
23
  def disconnect
24
- result = nil
25
- response = do_http_get(DISCONNECT_URL)
26
- if response
27
- code = response.code.to_i
28
- if code == 200
29
- result = Quickbooks::Model::AccessTokenResponse.from_xml(response.plain_body)
30
- end
31
- end
24
+ conn = Faraday.new
25
+ conn.basic_auth oauth.client.id, oauth.client.secret
26
+ url = "#{DISCONNECT_URL}?minorversion=#{Quickbooks.minorversion}"
27
+ response = conn.post(url, token: oauth.refresh_token || oauth.token)
32
28
 
33
- result
29
+ if response.success?
30
+ Quickbooks::Model::AccessTokenResponse.new(error_code: "0")
31
+ else
32
+ Quickbooks::Model::AccessTokenResponse.new(
33
+ error_code: response.status.to_s, error_message: response.reason_phrase
34
+ )
35
+ end
34
36
  end
35
-
36
37
  end
37
38
  end
38
39
  end
@@ -7,8 +7,11 @@ module Quickbooks
7
7
  attr_accessor :company_id
8
8
  attr_accessor :oauth
9
9
  attr_reader :base_uri
10
- attr_reader :last_response_body
11
10
  attr_reader :last_response_xml
11
+ attr_reader :last_response_intuit_tid
12
+ attr_accessor :before_request
13
+ attr_accessor :around_request
14
+ attr_accessor :after_request
12
15
 
13
16
  XML_NS = %{xmlns="http://schema.intuit.com/finance/v3"}
14
17
  HTTP_CONTENT_TYPE = 'application/xml'
@@ -17,6 +20,8 @@ module Quickbooks
17
20
  BASE_DOMAIN = 'quickbooks.api.intuit.com'
18
21
  SANDBOX_DOMAIN = 'sandbox-quickbooks.api.intuit.com'
19
22
 
23
+ RequestInfo = Struct.new(:url, :headers, :body, :method)
24
+
20
25
  def initialize(attributes = {})
21
26
  domain = Quickbooks.sandbox_mode ? SANDBOX_DOMAIN : BASE_DOMAIN
22
27
  @base_uri = "https://#{domain}/v3/company"
@@ -25,6 +30,7 @@ module Quickbooks
25
30
 
26
31
  def access_token=(token)
27
32
  @oauth = token
33
+ rebuild_connection!
28
34
  end
29
35
 
30
36
  def company_id=(company_id)
@@ -36,6 +42,22 @@ module Quickbooks
36
42
  @company_id = company_id
37
43
  end
38
44
 
45
+ # def oauth_v2?
46
+ # @oauth.is_a? OAuth2::AccessToken
47
+ # end
48
+
49
+ # [OAuth2] The default Faraday connection does not have gzip or multipart support.
50
+ # We need to reset the existing connection and build a new one.
51
+ def rebuild_connection!
52
+ @oauth.client.connection = nil
53
+ @oauth.client.connection.build do |builder|
54
+ builder.use :gzip
55
+ builder.request :multipart
56
+ builder.request :url_encoded
57
+ builder.adapter :net_http
58
+ end
59
+ end
60
+
39
61
  def url_for_resource(resource)
40
62
  "#{url_for_base}/#{resource}"
41
63
  end
@@ -49,15 +71,19 @@ module Quickbooks
49
71
  self.class::HTTP_CONTENT_TYPE == "application/json"
50
72
  end
51
73
 
74
+ def is_pdf?
75
+ self.class::HTTP_CONTENT_TYPE == "application/pdf"
76
+ end
77
+
52
78
  def default_model_query
53
79
  "SELECT * FROM #{self.class.name.split("::").last}"
54
80
  end
55
81
 
56
- def url_for_query(query = nil, start_position = 1, max_results = 20)
82
+ def url_for_query(query = nil, start_position = 1, max_results = 20, options = {})
57
83
  query ||= default_model_query
58
84
  query = "#{query} STARTPOSITION #{start_position} MAXRESULTS #{max_results}"
59
85
 
60
- "#{url_for_base}/query?query=#{URI.encode_www_form_component(query)}"
86
+ "#{url_for_base}/query?query=#{CGI.escape(query)}"
61
87
  end
62
88
 
63
89
  private
@@ -174,6 +200,21 @@ module Quickbooks
174
200
  do_http(:get, url, {}, headers)
175
201
  end
176
202
 
203
+ def do_http_raw_get(url, params = {}, headers = {})
204
+ url = add_query_string_to_url(url, params)
205
+ unless headers.has_key?('Content-Type')
206
+ headers['Content-Type'] = self.class::HTTP_CONTENT_TYPE
207
+ end
208
+ unless headers.has_key?('Accept')
209
+ headers['Accept'] = self.class::HTTP_ACCEPT
210
+ end
211
+ unless headers.has_key?('Accept-Encoding')
212
+ headers['Accept-Encoding'] = HTTP_ACCEPT_ENCODING
213
+ end
214
+ raw_response = oauth_get(url, headers)
215
+ Quickbooks::Service::Responses::OAuthHttpResponse.wrap(raw_response)
216
+ end
217
+
177
218
  def do_http_file_upload(uploadIO, url, metadata = nil)
178
219
  headers = {
179
220
  'Content-Type' => 'multipart/form-data'
@@ -188,6 +229,8 @@ module Quickbooks
188
229
  body['file_metadata_0'] = param_part
189
230
  end
190
231
 
232
+ url = add_query_string_to_url(url, {})
233
+
191
234
  do_http(:upload, url, body, headers)
192
235
  end
193
236
 
@@ -211,31 +254,71 @@ module Quickbooks
211
254
  log_request_body(body)
212
255
  log "REQUEST HEADERS = #{headers.inspect}"
213
256
 
214
- response = case method
257
+ request_info = RequestInfo.new(url, headers, body, method)
258
+ before_request.call(request_info) if before_request
259
+
260
+ raw_response = with_around_request(request_info) do
261
+ case method
215
262
  when :get
216
- @oauth.get(url, headers)
263
+ oauth_get(url, headers)
217
264
  when :post
218
- @oauth.post(url, body, headers)
265
+ oauth_post(url, body, headers)
219
266
  when :upload
220
- @oauth.post_with_multipart(url, body, headers)
267
+ oauth_post_with_multipart(url, body, headers)
221
268
  else
222
269
  raise "Do not know how to perform that HTTP operation"
223
270
  end
224
- check_response(response, :request => body)
271
+ end
272
+
273
+ after_request.call(request_info, raw_response.body) if after_request
274
+
275
+ response = Quickbooks::Service::Responses::OAuthHttpResponse.wrap(raw_response)
276
+ log "------ QUICKBOOKS-RUBY RESPONSE ------"
277
+ log "RESPONSE CODE = #{response.code}"
278
+ log_response_body(response)
279
+ if response.respond_to?(:headers)
280
+ log "RESPONSE HEADERS = #{response.headers}"
281
+ end
282
+ check_response(response, request: body)
283
+ end
284
+
285
+ def oauth_get(url, headers)
286
+ @oauth.get(url, headers: headers, raise_errors: false)
225
287
  end
226
288
 
227
- def add_query_string_to_url(url, params)
289
+ def oauth_post(url, body, headers)
290
+ @oauth.post(url, headers: headers, body: body, raise_errors: false)
291
+ end
292
+
293
+ def oauth_post_with_multipart(url, body, headers)
294
+ @oauth.post_with_multipart(url, headers: headers, body: body, raise_errors: false)
295
+ end
296
+
297
+ def add_query_string_to_url(url, params = {})
298
+ params ||= {}
299
+ params['minorversion'] = Quickbooks.minorversion
228
300
  if params.is_a?(Hash) && !params.empty?
229
- url + "?" + params.collect { |k| "#{k.first}=#{k.last}" }.join("&")
301
+ keyvalues = params.collect { |k| "#{k.first}=#{k.last}" }.join("&")
302
+ delim = url.index("?") != nil ? "&" : "?"
303
+ url + delim + keyvalues
230
304
  else
231
305
  url
232
306
  end
233
307
  end
234
308
 
235
309
  def check_response(response, options = {})
236
- log "------ QUICKBOOKS-RUBY RESPONSE ------"
237
- log "RESPONSE CODE = #{response.code}"
238
- log_response_body(response)
310
+ if is_json?
311
+ parse_json(response.plain_body)
312
+ elsif !is_pdf?
313
+ parse_xml(response.plain_body)
314
+ end
315
+
316
+ @last_response_intuit_tid = if response.respond_to?(:headers) && response.headers
317
+ response.headers['intuit_tid']
318
+ else
319
+ nil
320
+ end
321
+
239
322
  status = response.code.to_i
240
323
  case status
241
324
  when 200
@@ -248,12 +331,23 @@ module Quickbooks
248
331
  when 302
249
332
  raise "Unhandled HTTP Redirect"
250
333
  when 401
251
- raise Quickbooks::AuthorizationFailure
334
+ raise Quickbooks::AuthorizationFailure, parse_intuit_error
252
335
  when 403
253
- raise Quickbooks::Forbidden
336
+ message = parse_intuit_error[:message]
337
+ if message.include?('ThrottleExceeded')
338
+ raise Quickbooks::ThrottleExceeded, message
339
+ end
340
+ raise Quickbooks::Forbidden, message
341
+ when 404
342
+ raise Quickbooks::NotFound
343
+ when 413
344
+ raise Quickbooks::RequestTooLarge
254
345
  when 400, 500
255
346
  parse_and_raise_exception(options)
256
- when 503, 504
347
+ when 429
348
+ message = parse_intuit_error[:message]
349
+ raise Quickbooks::TooManyRequests, message
350
+ when 502, 503, 504
257
351
  raise Quickbooks::ServiceUnavailable
258
352
  else
259
353
  raise "HTTP Error Code: #{status}, Msg: #{response.plain_body}"
@@ -264,10 +358,10 @@ module Quickbooks
264
358
  log "RESPONSE BODY:"
265
359
  if is_json?
266
360
  log ">>>>#{response.plain_body.inspect}"
267
- parse_json(response.plain_body)
361
+ elsif is_pdf?
362
+ log("BODY is a PDF : not dumping")
268
363
  else
269
364
  log(log_xml(response.plain_body))
270
- parse_xml(response.plain_body)
271
365
  end
272
366
  end
273
367
 
@@ -275,8 +369,26 @@ module Quickbooks
275
369
  log "REQUEST BODY:"
276
370
  if is_json?
277
371
  log(body.inspect)
372
+ elsif is_pdf?
373
+ log("BODY is a PDF : not dumping")
278
374
  else
279
- log(log_xml(body))
375
+ #multipart request for uploads arrive here in a Hash with UploadIO vals
376
+ if body.is_a?(Hash)
377
+ body.each do |k,v|
378
+ log('BODY PART:')
379
+ val_content = v.inspect
380
+ if v.is_a?(UploadIO)
381
+ if v.content_type == 'application/xml'
382
+ if v.io.is_a?(StringIO)
383
+ val_content = log_xml(v.io.string)
384
+ end
385
+ end
386
+ end
387
+ log("#{k}: #{val_content}")
388
+ end
389
+ else
390
+ log(log_xml(body))
391
+ end
280
392
  end
281
393
  end
282
394
 
@@ -291,17 +403,22 @@ module Quickbooks
291
403
  else
292
404
  ex.request_xml = options[:request]
293
405
  end
406
+ ex.intuit_tid = err[:intuit_tid]
294
407
  raise ex
295
408
  end
296
409
 
297
410
  def response_is_error?
298
- @last_response_xml.xpath("//xmlns:IntuitResponse/xmlns:Fault")[0] != nil
299
- rescue Nokogiri::XML::XPath::SyntaxError => exception
300
- true
411
+ begin
412
+ @last_response_xml.xpath("//xmlns:IntuitResponse/xmlns:Fault")[0] != nil
413
+ rescue Nokogiri::XML::XPath::SyntaxError => exception
414
+ #puts @last_response_xml.to_xml.to_s
415
+ #puts "WTF: #{exception.inspect}:#{exception.backtrace.join("\n")}"
416
+ true
417
+ end
301
418
  end
302
419
 
303
420
  def parse_intuit_error
304
- error = {:message => "", :detail => "", :type => nil, :code => 0}
421
+ error = {:message => "", :detail => "", :type => nil, :code => 0, :intuit_tid => @last_response_intuit_tid}
305
422
  fault = @last_response_xml.xpath("//xmlns:IntuitResponse/xmlns:Fault")[0]
306
423
  if fault
307
424
  error[:type] = fault.attributes['type'].value
@@ -313,7 +430,7 @@ module Quickbooks
313
430
  error[:code] = code_attr.value
314
431
  end
315
432
  element_attr = error_element.attributes['element']
316
- if code_attr
433
+ if element_attr
317
434
  error[:element] = code_attr.value
318
435
  end
319
436
  error[:message] = error_element.xpath("//xmlns:Message").text
@@ -328,6 +445,13 @@ module Quickbooks
328
445
  error
329
446
  end
330
447
 
448
+ def with_around_request(request_info, &block)
449
+ if around_request
450
+ around_request.call(request_info, &block)
451
+ else
452
+ block.call
453
+ end
454
+ end
331
455
  end
332
456
  end
333
457
  end