quickbooks-ruby 0.4.0 → 1.0.16

Sign up to get free protection for your applications and to get access to all the features.
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