quickbooks-ruby-oauth2 0.6.1

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 (169) hide show
  1. checksums.yaml +7 -0
  2. data/lib/quickbooks/faraday/middleware/gzip.rb +72 -0
  3. data/lib/quickbooks/model/access_token_response.rb +29 -0
  4. data/lib/quickbooks/model/account.rb +78 -0
  5. data/lib/quickbooks/model/account_based_expense_line_detail.rb +15 -0
  6. data/lib/quickbooks/model/attachable.rb +33 -0
  7. data/lib/quickbooks/model/attachable_ref.rb +22 -0
  8. data/lib/quickbooks/model/base_model.rb +120 -0
  9. data/lib/quickbooks/model/base_model_json.rb +16 -0
  10. data/lib/quickbooks/model/base_reference.rb +23 -0
  11. data/lib/quickbooks/model/batch_request.rb +45 -0
  12. data/lib/quickbooks/model/batch_response.rb +33 -0
  13. data/lib/quickbooks/model/bill.rb +45 -0
  14. data/lib/quickbooks/model/bill_line_item.rb +42 -0
  15. data/lib/quickbooks/model/bill_payment.rb +40 -0
  16. data/lib/quickbooks/model/bill_payment_check.rb +12 -0
  17. data/lib/quickbooks/model/bill_payment_credit_card.rb +10 -0
  18. data/lib/quickbooks/model/bill_payment_line_item.rb +21 -0
  19. data/lib/quickbooks/model/budget.rb +27 -0
  20. data/lib/quickbooks/model/budget_line_item.rb +15 -0
  21. data/lib/quickbooks/model/change_data_capture.rb +42 -0
  22. data/lib/quickbooks/model/change_model.rb +10 -0
  23. data/lib/quickbooks/model/check_payment.rb +11 -0
  24. data/lib/quickbooks/model/class.rb +31 -0
  25. data/lib/quickbooks/model/company_info.rb +69 -0
  26. data/lib/quickbooks/model/credit_card_payment.rb +17 -0
  27. data/lib/quickbooks/model/credit_memo.rb +59 -0
  28. data/lib/quickbooks/model/credit_memo_change.rb +8 -0
  29. data/lib/quickbooks/model/custom_field.rb +13 -0
  30. data/lib/quickbooks/model/customer.rb +76 -0
  31. data/lib/quickbooks/model/customer_change.rb +8 -0
  32. data/lib/quickbooks/model/definition.rb +46 -0
  33. data/lib/quickbooks/model/delivery_info.rb +8 -0
  34. data/lib/quickbooks/model/department.rb +31 -0
  35. data/lib/quickbooks/model/deposit.rb +44 -0
  36. data/lib/quickbooks/model/deposit_line_detail.rb +17 -0
  37. data/lib/quickbooks/model/deposit_line_item.rb +35 -0
  38. data/lib/quickbooks/model/description_line_detail.rb +7 -0
  39. data/lib/quickbooks/model/discount_line_detail.rb +15 -0
  40. data/lib/quickbooks/model/discount_override.rb +13 -0
  41. data/lib/quickbooks/model/document_numbering.rb +18 -0
  42. data/lib/quickbooks/model/effective_tax_rate.rb +15 -0
  43. data/lib/quickbooks/model/email_address.rb +19 -0
  44. data/lib/quickbooks/model/employee.rb +47 -0
  45. data/lib/quickbooks/model/entity.rb +16 -0
  46. data/lib/quickbooks/model/entity_ref.rb +8 -0
  47. data/lib/quickbooks/model/estimate.rb +76 -0
  48. data/lib/quickbooks/model/exchange_rate.rb +23 -0
  49. data/lib/quickbooks/model/fault.rb +16 -0
  50. data/lib/quickbooks/model/global_tax_calculation.rb +13 -0
  51. data/lib/quickbooks/model/group_line_detail.rb +13 -0
  52. data/lib/quickbooks/model/has_line_items.rb +14 -0
  53. data/lib/quickbooks/model/invoice.rb +107 -0
  54. data/lib/quickbooks/model/invoice_change.rb +9 -0
  55. data/lib/quickbooks/model/invoice_group_line_detail.rb +14 -0
  56. data/lib/quickbooks/model/invoice_line_item.rb +88 -0
  57. data/lib/quickbooks/model/item.rb +87 -0
  58. data/lib/quickbooks/model/item_based_expense_line_detail.rb +20 -0
  59. data/lib/quickbooks/model/item_change.rb +8 -0
  60. data/lib/quickbooks/model/item_group_detail.rb +9 -0
  61. data/lib/quickbooks/model/item_group_line.rb +20 -0
  62. data/lib/quickbooks/model/journal_entry.rb +36 -0
  63. data/lib/quickbooks/model/journal_entry_line_detail.rb +27 -0
  64. data/lib/quickbooks/model/line.rb +95 -0
  65. data/lib/quickbooks/model/line_ex.rb +9 -0
  66. data/lib/quickbooks/model/linked_transaction.rb +9 -0
  67. data/lib/quickbooks/model/markup_info.rb +12 -0
  68. data/lib/quickbooks/model/meta_data.rb +24 -0
  69. data/lib/quickbooks/model/name_value.rb +8 -0
  70. data/lib/quickbooks/model/other_contact_info.rb +8 -0
  71. data/lib/quickbooks/model/payment.rb +43 -0
  72. data/lib/quickbooks/model/payment_change.rb +8 -0
  73. data/lib/quickbooks/model/payment_line_detail.rb +12 -0
  74. data/lib/quickbooks/model/payment_method.rb +25 -0
  75. data/lib/quickbooks/model/physical_address.rb +40 -0
  76. data/lib/quickbooks/model/preferences.rb +49 -0
  77. data/lib/quickbooks/model/purchase.rb +57 -0
  78. data/lib/quickbooks/model/purchase_line_item.rb +44 -0
  79. data/lib/quickbooks/model/purchase_order.rb +50 -0
  80. data/lib/quickbooks/model/purchase_tax_rate_list.rb +11 -0
  81. data/lib/quickbooks/model/refund_receipt.rb +62 -0
  82. data/lib/quickbooks/model/refund_receipt_change.rb +8 -0
  83. data/lib/quickbooks/model/report.rb +52 -0
  84. data/lib/quickbooks/model/sales_item_line_detail.rb +16 -0
  85. data/lib/quickbooks/model/sales_receipt.rb +65 -0
  86. data/lib/quickbooks/model/sales_tax_rate_list.rb +11 -0
  87. data/lib/quickbooks/model/sub_total_line_detail.rb +13 -0
  88. data/lib/quickbooks/model/tax_agency.rb +18 -0
  89. data/lib/quickbooks/model/tax_code.rb +21 -0
  90. data/lib/quickbooks/model/tax_line.rb +15 -0
  91. data/lib/quickbooks/model/tax_line_detail.rb +16 -0
  92. data/lib/quickbooks/model/tax_rate.rb +28 -0
  93. data/lib/quickbooks/model/tax_rate_detail.rb +15 -0
  94. data/lib/quickbooks/model/tax_rate_detail_line.rb +23 -0
  95. data/lib/quickbooks/model/tax_service.rb +52 -0
  96. data/lib/quickbooks/model/telephone_number.rb +13 -0
  97. data/lib/quickbooks/model/term.rb +28 -0
  98. data/lib/quickbooks/model/time_activity.rb +79 -0
  99. data/lib/quickbooks/model/transaction_tax_detail.rb +12 -0
  100. data/lib/quickbooks/model/transfer.rb +21 -0
  101. data/lib/quickbooks/model/upload.rb +35 -0
  102. data/lib/quickbooks/model/validator.rb +10 -0
  103. data/lib/quickbooks/model/vendor.rb +53 -0
  104. data/lib/quickbooks/model/vendor_change.rb +8 -0
  105. data/lib/quickbooks/model/vendor_credit.rb +40 -0
  106. data/lib/quickbooks/model/web_site_address.rb +16 -0
  107. data/lib/quickbooks/service/access_token.rb +38 -0
  108. data/lib/quickbooks/service/account.rb +17 -0
  109. data/lib/quickbooks/service/attachable.rb +12 -0
  110. data/lib/quickbooks/service/base_service.rb +445 -0
  111. data/lib/quickbooks/service/base_service_json.rb +39 -0
  112. data/lib/quickbooks/service/batch.rb +11 -0
  113. data/lib/quickbooks/service/bill.rb +16 -0
  114. data/lib/quickbooks/service/bill_payment.rb +16 -0
  115. data/lib/quickbooks/service/budget.rb +12 -0
  116. data/lib/quickbooks/service/change_data_capture.rb +24 -0
  117. data/lib/quickbooks/service/change_service.rb +28 -0
  118. data/lib/quickbooks/service/class.rb +23 -0
  119. data/lib/quickbooks/service/company_info.rb +12 -0
  120. data/lib/quickbooks/service/credit_memo.rb +16 -0
  121. data/lib/quickbooks/service/credit_memo_change.rb +16 -0
  122. data/lib/quickbooks/service/customer.rb +18 -0
  123. data/lib/quickbooks/service/customer_change.rb +16 -0
  124. data/lib/quickbooks/service/department.rb +23 -0
  125. data/lib/quickbooks/service/deposit.rb +16 -0
  126. data/lib/quickbooks/service/employee.rb +24 -0
  127. data/lib/quickbooks/service/estimate.rb +22 -0
  128. data/lib/quickbooks/service/exchange_rate.rb +25 -0
  129. data/lib/quickbooks/service/invoice.rb +45 -0
  130. data/lib/quickbooks/service/invoice_change.rb +16 -0
  131. data/lib/quickbooks/service/item.rb +32 -0
  132. data/lib/quickbooks/service/item_change.rb +16 -0
  133. data/lib/quickbooks/service/journal_entry.rb +16 -0
  134. data/lib/quickbooks/service/payment.rb +29 -0
  135. data/lib/quickbooks/service/payment_change.rb +16 -0
  136. data/lib/quickbooks/service/payment_method.rb +30 -0
  137. data/lib/quickbooks/service/preferences.rb +12 -0
  138. data/lib/quickbooks/service/purchase.rb +16 -0
  139. data/lib/quickbooks/service/purchase_order.rb +16 -0
  140. data/lib/quickbooks/service/refund_receipt.rb +16 -0
  141. data/lib/quickbooks/service/refund_receipt_change.rb +16 -0
  142. data/lib/quickbooks/service/reports.rb +33 -0
  143. data/lib/quickbooks/service/responses/methods.rb +17 -0
  144. data/lib/quickbooks/service/responses/oauth1_http_response.rb +42 -0
  145. data/lib/quickbooks/service/responses/oauth2_http_response.rb +43 -0
  146. data/lib/quickbooks/service/responses/oauth_http_response.rb +21 -0
  147. data/lib/quickbooks/service/sales_receipt.rb +46 -0
  148. data/lib/quickbooks/service/service_crud.rb +75 -0
  149. data/lib/quickbooks/service/service_crud_json.rb +31 -0
  150. data/lib/quickbooks/service/tax_agency.rb +12 -0
  151. data/lib/quickbooks/service/tax_code.rb +12 -0
  152. data/lib/quickbooks/service/tax_rate.rb +12 -0
  153. data/lib/quickbooks/service/tax_service.rb +11 -0
  154. data/lib/quickbooks/service/term.rb +17 -0
  155. data/lib/quickbooks/service/time_activity.rb +16 -0
  156. data/lib/quickbooks/service/transfer.rb +16 -0
  157. data/lib/quickbooks/service/upload.rb +39 -0
  158. data/lib/quickbooks/service/vendor.rb +24 -0
  159. data/lib/quickbooks/service/vendor_change.rb +16 -0
  160. data/lib/quickbooks/service/vendor_credit.rb +16 -0
  161. data/lib/quickbooks/util/collection.rb +18 -0
  162. data/lib/quickbooks/util/http_encoding_helper.rb +54 -0
  163. data/lib/quickbooks/util/logging.rb +19 -0
  164. data/lib/quickbooks/util/multipart.rb +85 -0
  165. data/lib/quickbooks/util/name_entity.rb +85 -0
  166. data/lib/quickbooks/util/query_builder.rb +35 -0
  167. data/lib/quickbooks/version.rb +5 -0
  168. data/lib/quickbooks-ruby.rb +255 -0
  169. metadata +378 -0
@@ -0,0 +1,40 @@
1
+ # === Business Rules
2
+ # * The VendorRef attribute must be specified.
3
+ # * At lease one Line with Line.Amount must be specified.
4
+
5
+ module Quickbooks
6
+ module Model
7
+ class VendorCredit < BaseModel
8
+ include GlobalTaxCalculation
9
+ include HasLineItems
10
+
11
+ #== Constants
12
+ REST_RESOURCE = 'vendorcredit'
13
+ XML_COLLECTION_NODE = "VendorCredit"
14
+ XML_NODE = "VendorCredit"
15
+
16
+ xml_accessor :id, :from => 'Id'
17
+ xml_accessor :sync_token, :from => 'SyncToken', :as => Integer
18
+ xml_accessor :meta_data, :from => 'MetaData', :as => MetaData
19
+ xml_accessor :doc_number, :from => 'DocNumber'
20
+ xml_accessor :txn_date, :from => 'TxnDate', :as => Date
21
+ xml_accessor :private_note, :from => 'PrivateNote'
22
+
23
+ xml_accessor :line_items, :from => 'Line', :as => [PurchaseLineItem]
24
+ xml_accessor :department_ref, :from => 'DepartmentRef', :as => BaseReference
25
+ xml_accessor :ap_account_ref, :from => 'APAccountRef', :as => BaseReference
26
+ xml_accessor :vendor_ref, :from => 'VendorRef', :as => BaseReference
27
+ xml_accessor :total, :from => 'TotalAmt', :as => BigDecimal, :to_xml => Proc.new { |val| val.to_f }
28
+
29
+ xml_accessor :currency_ref, :from => 'CurrencyRef', :as => BaseReference
30
+ xml_accessor :exchange_rate, :from => 'ExchangeRate', :as => BigDecimal, :to_xml => to_xml_big_decimal
31
+
32
+ reference_setters
33
+
34
+ #== This adds aliases for backwards compatability to old attributes names
35
+ alias_method :total_amount, :total
36
+ alias_method :total_amount=, :total=
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module Quickbooks
2
+ module Model
3
+ class WebSiteAddress < BaseModel
4
+ xml_accessor :uri, :from => 'URI'
5
+
6
+ def initialize(uri = nil)
7
+ self.uri = uri if uri
8
+ end
9
+
10
+ def to_xml(options = {})
11
+ return "" if uri.to_s.empty?
12
+ super(options)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ module Quickbooks
2
+ module Service
3
+ class AccessToken < BaseService
4
+
5
+ RENEW_URL = "https://appcenter.intuit.com/api/v1/connection/reconnect"
6
+ DISCONNECT_URL = "https://appcenter.intuit.com/api/v1/connection/disconnect"
7
+
8
+ # https://developer.intuit.com/docs/0025_quickbooksapi/0053_auth_auth/oauth_management_api#Reconnect
9
+ def renew
10
+ result = nil
11
+ response = do_http_get(RENEW_URL)
12
+ if response
13
+ code = response.code.to_i
14
+ if code == 200
15
+ result = Quickbooks::Model::AccessTokenResponse.from_xml(response.plain_body)
16
+ end
17
+ end
18
+
19
+ result
20
+ end
21
+
22
+ # https://developer.intuit.com/docs/0025_quickbooksapi/0053_auth_auth/oauth_management_api#Disconnect
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
32
+
33
+ result
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ module Quickbooks
2
+ module Service
3
+ class Account < BaseService
4
+
5
+ def delete(account)
6
+ account.active = false
7
+ update(account, :sparse => true)
8
+ end
9
+
10
+ private
11
+
12
+ def model
13
+ Quickbooks::Model::Account
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Quickbooks
2
+ module Service
3
+ class Attachable < BaseService
4
+
5
+ private
6
+
7
+ def model
8
+ Quickbooks::Model::Attachable
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,445 @@
1
+ module Quickbooks
2
+ module Service
3
+ class BaseService
4
+ include Quickbooks::Util::Logging
5
+ include ServiceCrud
6
+
7
+ attr_accessor :company_id
8
+ attr_accessor :oauth
9
+ attr_reader :base_uri
10
+ attr_reader :last_response_body
11
+ attr_reader :last_response_xml
12
+
13
+ XML_NS = %{xmlns="http://schema.intuit.com/finance/v3"}
14
+ HTTP_CONTENT_TYPE = 'application/xml'
15
+ HTTP_ACCEPT = 'application/xml'
16
+ HTTP_ACCEPT_ENCODING = 'gzip, deflate'
17
+ BASE_DOMAIN = 'quickbooks.api.intuit.com'
18
+ SANDBOX_DOMAIN = 'sandbox-quickbooks.api.intuit.com'
19
+
20
+ def initialize(attributes = {})
21
+ domain = Quickbooks.sandbox_mode ? SANDBOX_DOMAIN : BASE_DOMAIN
22
+ @base_uri = "https://#{domain}/v3/company"
23
+ attributes.each {|key, value| public_send("#{key}=", value) }
24
+ end
25
+
26
+ def access_token=(token)
27
+ @oauth = token
28
+ rebuild_connection!
29
+ end
30
+
31
+ def company_id=(company_id)
32
+ @company_id = company_id
33
+ end
34
+
35
+ # realm & company are synonymous
36
+ def realm_id=(company_id)
37
+ @company_id = company_id
38
+ end
39
+
40
+ def oauth_v1?
41
+ @oauth.is_a? OAuth::AccessToken
42
+ end
43
+
44
+ def oauth_v2?
45
+ @oauth.is_a? OAuth2::AccessToken
46
+ end
47
+
48
+ # [OAuth2] The default Faraday connection does not have gzip or multipart support.
49
+ # We need to reset the existing connection and build a new one.
50
+ def rebuild_connection!
51
+ return unless oauth_v2?
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
+
61
+ def url_for_resource(resource)
62
+ "#{url_for_base}/#{resource}"
63
+ end
64
+
65
+ def url_for_base
66
+ raise MissingRealmError.new unless @company_id
67
+ "#{@base_uri}/#{@company_id}"
68
+ end
69
+
70
+ def is_json?
71
+ self.class::HTTP_CONTENT_TYPE == "application/json"
72
+ end
73
+
74
+ def is_pdf?
75
+ self.class::HTTP_CONTENT_TYPE == "application/pdf"
76
+ end
77
+
78
+ def default_model_query
79
+ "SELECT * FROM #{self.class.name.split("::").last}"
80
+ end
81
+
82
+ def url_for_query(query = nil, start_position = 1, max_results = 20, options = {})
83
+ query ||= default_model_query
84
+ query = "#{query} STARTPOSITION #{start_position} MAXRESULTS #{max_results}"
85
+
86
+ "#{url_for_base}/query?query=#{CGI.escape(query)}"
87
+ end
88
+
89
+ private
90
+
91
+ def parse_xml(xml)
92
+ @last_response_xml = Nokogiri::XML(xml)
93
+ end
94
+
95
+ def valid_xml_document(xml)
96
+ %Q{<?xml version="1.0" encoding="utf-8"?>\n#{xml.strip}}
97
+ end
98
+
99
+ # A single object response is the same as a collection response except
100
+ # it just has a single main element
101
+ def fetch_object(model, url, params = {})
102
+ raise ArgumentError, "missing model to instantiate" if model.nil?
103
+ response = do_http_get(url, params)
104
+ collection = parse_collection(response, model)
105
+ if collection.is_a?(Quickbooks::Collection)
106
+ collection.entries.first
107
+ else
108
+ nil
109
+ end
110
+ end
111
+
112
+ def fetch_collection(query, model, options = {})
113
+ page = options.fetch(:page, 1)
114
+ per_page = options.fetch(:per_page, 20)
115
+
116
+ start_position = ((page - 1) * per_page) + 1 # page=2, per_page=10 then we want to start at 11
117
+ max_results = per_page
118
+
119
+ response = do_http_get(url_for_query(query, start_position, max_results))
120
+
121
+ parse_collection(response, model)
122
+ end
123
+
124
+ def parse_collection(response, model)
125
+ if response
126
+ collection = Quickbooks::Collection.new
127
+ xml = @last_response_xml
128
+ begin
129
+ results = []
130
+
131
+ query_response = xml.xpath("//xmlns:IntuitResponse/xmlns:QueryResponse")[0]
132
+ if query_response
133
+
134
+ start_pos_attr = query_response.attributes['startPosition']
135
+ if start_pos_attr
136
+ collection.start_position = start_pos_attr.value.to_i
137
+ end
138
+
139
+ max_results_attr = query_response.attributes['maxResults']
140
+ if max_results_attr
141
+ collection.max_results = max_results_attr.value.to_i
142
+ end
143
+
144
+ total_count_attr = query_response.attributes['totalCount']
145
+ if total_count_attr
146
+ collection.total_count = total_count_attr.value.to_i
147
+ end
148
+ end
149
+
150
+ path_to_nodes = "//xmlns:IntuitResponse//xmlns:#{model::XML_NODE}"
151
+ collection.count = xml.xpath(path_to_nodes).count
152
+ if collection.count > 0
153
+ xml.xpath(path_to_nodes).each do |xa|
154
+ results << model.from_xml(xa)
155
+ end
156
+ end
157
+
158
+ collection.entries = results
159
+ rescue => ex
160
+ raise Quickbooks::IntuitRequestException.new("Error parsing XML: #{ex.message}")
161
+ end
162
+ collection
163
+ else
164
+ nil
165
+ end
166
+ end
167
+
168
+ # Given an IntuitResponse which is expected to wrap a single
169
+ # Entity node, e.g.
170
+ # <IntuitResponse xmlns="http://schema.intuit.com/finance/v3" time="2013-11-16T10:26:42.762-08:00">
171
+ # <Customer domain="QBO" sparse="false">
172
+ # <Id>1</Id>
173
+ # ...
174
+ # </Customer>
175
+ # </IntuitResponse>
176
+ def parse_singular_entity_response(model, xml, node_xpath_prefix = nil)
177
+ xmldoc = Nokogiri(xml)
178
+ prefix = node_xpath_prefix || model::XML_NODE
179
+ xmldoc.xpath("//xmlns:IntuitResponse/xmlns:#{prefix}")[0]
180
+ end
181
+
182
+ # A successful delete request returns a XML packet like:
183
+ # <IntuitResponse xmlns="http://schema.intuit.com/finance/v3" time="2013-04-23T08:30:33.626-07:00">
184
+ # <Payment domain="QBO" status="Deleted">
185
+ # <Id>8748</Id>
186
+ # </Payment>
187
+ # </IntuitResponse>
188
+ def parse_singular_entity_response_for_delete(model, xml)
189
+ xmldoc = Nokogiri(xml)
190
+ xmldoc.xpath("//xmlns:IntuitResponse/xmlns:#{model::XML_NODE}[@status='Deleted']").length == 1
191
+ end
192
+
193
+ def do_http_post(url, body = "", params = {}, headers = {}) # throws IntuitRequestException
194
+ url = add_query_string_to_url(url, params)
195
+ do_http(:post, url, body, headers)
196
+ end
197
+
198
+ def do_http_get(url, params = {}, headers = {}) # throws IntuitRequestException
199
+ url = add_query_string_to_url(url, params)
200
+ do_http(:get, url, {}, headers)
201
+ end
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
+
218
+ def do_http_file_upload(uploadIO, url, metadata = nil)
219
+ headers = {
220
+ 'Content-Type' => 'multipart/form-data'
221
+ }
222
+ body = {}
223
+ body['file_content_0'] = uploadIO
224
+
225
+ if metadata
226
+ standalone_prefix = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
227
+ meta_data_xml = "#{standalone_prefix}\n#{metadata.to_xml_ns.to_s}"
228
+ param_part = UploadIO.new(StringIO.new(meta_data_xml), "application/xml")
229
+ body['file_metadata_0'] = param_part
230
+ end
231
+
232
+ do_http(:upload, url, body, headers)
233
+ end
234
+
235
+ def do_http(method, url, body, headers) # throws IntuitRequestException
236
+ if @oauth.nil?
237
+ raise "OAuth client has not been initialized. Initialize with setter access_token="
238
+ end
239
+ unless headers.has_key?('Content-Type')
240
+ headers['Content-Type'] = self.class::HTTP_CONTENT_TYPE
241
+ end
242
+ unless headers.has_key?('Accept')
243
+ headers['Accept'] = self.class::HTTP_ACCEPT
244
+ end
245
+ unless headers.has_key?('Accept-Encoding')
246
+ headers['Accept-Encoding'] = HTTP_ACCEPT_ENCODING
247
+ end
248
+
249
+ log "------ QUICKBOOKS-RUBY REQUEST ------"
250
+ log "METHOD = #{method}"
251
+ log "RESOURCE = #{url}"
252
+ log_request_body(body)
253
+ log "REQUEST HEADERS = #{headers.inspect}"
254
+
255
+ raw_response = case method
256
+ when :get
257
+ oauth_get(url, headers)
258
+ when :post
259
+ oauth_post(url, body, headers)
260
+ when :upload
261
+ oauth_post_with_multipart(url, body, headers)
262
+ else
263
+ raise "Do not know how to perform that HTTP operation"
264
+ end
265
+ response = Quickbooks::Service::Responses::OAuthHttpResponse.wrap(raw_response)
266
+ check_response(response, :request => body)
267
+ end
268
+
269
+ def oauth_get(url, headers)
270
+ if oauth_v1?
271
+ @oauth.get(url, headers)
272
+ elsif oauth_v2?
273
+ @oauth.get(url, headers: headers, raise_errors: false)
274
+ else
275
+ raise InvalidOauthAccessTokenObject.new(@oauth)
276
+ end
277
+ end
278
+
279
+ def oauth_post(url, body, headers)
280
+ if oauth_v1?
281
+ @oauth.post(url, body, headers)
282
+ elsif oauth_v2?
283
+ @oauth.post(url, headers: headers, body: body, raise_errors: false)
284
+ else
285
+ raise InvalidOauthAccessTokenObject.new(@oauth)
286
+ end
287
+ end
288
+
289
+ def oauth_post_with_multipart(url, body, headers)
290
+ raw_response = if oauth_v1?
291
+ oauth.post_with_multipart(url, body, headers)
292
+ elsif oauth_v2?
293
+ oauth.post_with_multipart(url, headers: headers, body: body, raise_errors: false)
294
+ else
295
+ raise InvalidOauthAccessTokenObject.new(@oauth)
296
+ end
297
+ response = Quickbooks::Service::Responses::OAuthHttpResponse.wrap(raw_response)
298
+ check_response(response, :request => body)
299
+ end
300
+
301
+ def add_query_string_to_url(url, params)
302
+ if params.is_a?(Hash) && !params.empty?
303
+ url + "?" + params.collect { |k| "#{k.first}=#{k.last}" }.join("&")
304
+ else
305
+ url
306
+ end
307
+ end
308
+
309
+ def check_response(response, options = {})
310
+ log "------ QUICKBOOKS-RUBY RESPONSE ------"
311
+ log "RESPONSE CODE = #{response.code}"
312
+ if response.respond_to?(:headers)
313
+ log "RESPONSE HEADERS = #{response.headers}"
314
+ end
315
+ log_response_body(response)
316
+ status = response.code.to_i
317
+ case status
318
+ when 200
319
+ # even HTTP 200 can contain an error, so we always have to peek for an Error
320
+ if response_is_error?
321
+ parse_and_raise_exception(options)
322
+ else
323
+ response
324
+ end
325
+ when 302
326
+ raise "Unhandled HTTP Redirect"
327
+ when 401
328
+ raise Quickbooks::AuthorizationFailure
329
+ when 403
330
+ message = parse_intuit_error[:message]
331
+ if message.include?('ThrottleExceeded')
332
+ raise Quickbooks::ThrottleExceeded, message
333
+ end
334
+ raise Quickbooks::Forbidden, message
335
+ when 404
336
+ raise Quickbooks::NotFound
337
+ when 413
338
+ raise Quickbooks::RequestTooLarge
339
+ when 400, 500
340
+ parse_and_raise_exception(options)
341
+ when 429
342
+ message = parse_intuit_error[:message]
343
+ raise Quickbooks::TooManyRequests, message
344
+ when 503, 504
345
+ raise Quickbooks::ServiceUnavailable
346
+ else
347
+ raise "HTTP Error Code: #{status}, Msg: #{response.plain_body}"
348
+ end
349
+ end
350
+
351
+ def log_response_body(response)
352
+ log "RESPONSE BODY:"
353
+ if is_json?
354
+ log ">>>>#{response.plain_body.inspect}"
355
+ parse_json(response.plain_body)
356
+ elsif is_pdf?
357
+ log("BODY is a PDF : not dumping")
358
+ else
359
+ log(log_xml(response.plain_body))
360
+ parse_xml(response.plain_body)
361
+ end
362
+ end
363
+
364
+ def log_request_body(body)
365
+ log "REQUEST BODY:"
366
+ if is_json?
367
+ log(body.inspect)
368
+ elsif is_pdf?
369
+ log("BODY is a PDF : not dumping")
370
+ else
371
+ #multipart request for uploads arrive here in a Hash with UploadIO vals
372
+ if body.is_a?(Hash)
373
+ body.each do |k,v|
374
+ log('BODY PART:')
375
+ val_content = v.inspect
376
+ if v.is_a?(UploadIO)
377
+ if v.content_type == 'application/xml'
378
+ if v.io.is_a?(StringIO)
379
+ val_content = log_xml(v.io.string)
380
+ end
381
+ end
382
+ end
383
+ log("#{k}: #{val_content}")
384
+ end
385
+ else
386
+ log(log_xml(body))
387
+ end
388
+ end
389
+ end
390
+
391
+ def parse_and_raise_exception(options = {})
392
+ err = parse_intuit_error
393
+ ex = Quickbooks::IntuitRequestException.new("#{err[:message]}:\n\t#{err[:detail]}")
394
+ ex.code = err[:code]
395
+ ex.detail = err[:detail]
396
+ ex.type = err[:type]
397
+ if is_json?
398
+ ex.request_json = options[:request]
399
+ else
400
+ ex.request_xml = options[:request]
401
+ end
402
+ raise ex
403
+ end
404
+
405
+ def response_is_error?
406
+ begin
407
+ @last_response_xml.xpath("//xmlns:IntuitResponse/xmlns:Fault")[0] != nil
408
+ rescue Nokogiri::XML::XPath::SyntaxError => exception
409
+ #puts @last_response_xml.to_xml.to_s
410
+ #puts "WTF: #{exception.inspect}:#{exception.backtrace.join("\n")}"
411
+ true
412
+ end
413
+ end
414
+
415
+ def parse_intuit_error
416
+ error = {:message => "", :detail => "", :type => nil, :code => 0}
417
+ fault = @last_response_xml.xpath("//xmlns:IntuitResponse/xmlns:Fault")[0]
418
+ if fault
419
+ error[:type] = fault.attributes['type'].value
420
+
421
+ error_element = fault.xpath("//xmlns:Error")[0]
422
+ if error_element
423
+ code_attr = error_element.attributes['code']
424
+ if code_attr
425
+ error[:code] = code_attr.value
426
+ end
427
+ element_attr = error_element.attributes['element']
428
+ if code_attr
429
+ error[:element] = code_attr.value
430
+ end
431
+ error[:message] = error_element.xpath("//xmlns:Message").text
432
+ error[:detail] = error_element.xpath("//xmlns:Detail").text
433
+ end
434
+ end
435
+
436
+ error
437
+ rescue Nokogiri::XML::XPath::SyntaxError => exception
438
+ error[:detail] = @last_response_xml.to_s
439
+
440
+ error
441
+ end
442
+
443
+ end
444
+ end
445
+ end
@@ -0,0 +1,39 @@
1
+ module Quickbooks
2
+ module Service
3
+ class BaseServiceJSON < BaseService
4
+ include ServiceCrudJSON
5
+ HTTP_CONTENT_TYPE = 'application/json'
6
+ HTTP_ACCEPT = 'application/json'
7
+ attr_reader :last_response_json
8
+
9
+ private
10
+
11
+ def parse_json(json)
12
+ @last_response_json = json
13
+ end
14
+
15
+ def response_is_error?
16
+ @last_response_json['Fault'].present?
17
+ end
18
+
19
+ def parse_intuit_error
20
+ error = {:message => "", :detail => "", :type => nil, :code => 0}
21
+ resp = JSON.parse(@last_response_json)
22
+ fault = resp['Fault']
23
+ if fault.present?
24
+ error[:type] = fault['type'] if fault.has_key?('type')
25
+ if fault_error = fault['Error'].first
26
+ error[:message] = fault_error['Message']
27
+ error[:detail] = fault_error['Detail']
28
+ error[:code] = fault_error['code']
29
+ end
30
+ end
31
+ error
32
+ rescue Exception => exception
33
+ error[:detail] = @last_response_json.to_s
34
+ error
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,11 @@
1
+ module Quickbooks
2
+ module Service
3
+ class Batch < BaseService
4
+
5
+ def make_request(entity, options = {})
6
+ response = do_http_post(url_for_resource('batch'), valid_xml_document(entity.to_xml_ns), options[:query])
7
+ Quickbooks::Model::BatchResponse.from_xml(response.plain_body)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module Quickbooks
2
+ module Service
3
+ class Bill < BaseService
4
+
5
+ def delete(bill)
6
+ delete_by_query_string(bill)
7
+ end
8
+
9
+ private
10
+
11
+ def model
12
+ Quickbooks::Model::Bill
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Quickbooks
2
+ module Service
3
+ class BillPayment < BaseService
4
+
5
+ def delete(bill_payment)
6
+ delete_by_query_string(bill_payment)
7
+ end
8
+
9
+ private
10
+
11
+ def model
12
+ Quickbooks::Model::BillPayment
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module Quickbooks
2
+ module Service
3
+ class Budget < BaseService
4
+
5
+ private
6
+
7
+ def model
8
+ Quickbooks::Model::Budget
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ module Quickbooks
2
+ module Service
3
+ class ChangeDataCapture < BaseService
4
+
5
+ def url_for_query(entity_list, query=nil)
6
+ q = entity_list.join(",")
7
+ q = "#{q}&#{query}" if query.present?
8
+ return "#{url_for_base}/cdc?entities=#{q}"
9
+ end
10
+
11
+ def since(entity_list, timestamp)
12
+ do_http_get(url_for_query(entity_list, "changedSince=#{URI.encode_www_form_component(timestamp.iso8601)}"))
13
+ model.new(:xml => @last_response_xml)
14
+ end
15
+
16
+ private
17
+
18
+ def model
19
+ Quickbooks::Model::ChangeDataCapture
20
+ end
21
+
22
+ end
23
+ end
24
+ end