class ActiveMerchant::Billing::QuickbooksGateway
Constants
- BASE
- ENDPOINT
- FRAUD_WARNING_CODES
- REFRESH_URI
- STANDARD_ERROR_CODE_MAPPING
developer.intuit.com/docs/0150_payments/0300_developer_guides/error_handling
- VOID_ENDPOINT
Public Class Methods
new(options = {})
click to toggle source
Calls superclass method
ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 50 def initialize(options = {}) # Quickbooks is deprecating OAuth 1.0 on December 17, 2019. # OAuth 2.0 requires a client_id, client_secret, access_token, and refresh_token # To maintain backwards compatibility, check for the presence of a refresh_token (only specified for OAuth 2.0) # When present, validate that all OAuth 2.0 options are present if options[:refresh_token] requires!(options, :client_id, :client_secret, :access_token, :refresh_token) else requires!(options, :consumer_key, :consumer_secret, :access_token, :token_secret, :realm) end @options = options super end
Public Instance Methods
capture(money, authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 84 def capture(money, authorization, options = {}) post = {} authorization, = split_authorization(authorization) post[:amount] = localized_amount(money, currency(money)) add_context(post, options) response = commit(capture_uri(authorization), post) check_token_response(response, capture_uri(authorization), post, options) end
purchase(money, payment, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 64 def purchase(money, payment, options = {}) post = {} add_amount(post, money, options) add_charge_data(post, payment, options) post[:capture] = 'true' response = commit(ENDPOINT, post) check_token_response(response, ENDPOINT, post, options) end
refresh()
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 115 def refresh response = refresh_access_token response_object(response) end
refund(money, authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 94 def refund(money, authorization, options = {}) post = {} post[:amount] = localized_amount(money, currency(money)) add_context(post, options) authorization, = split_authorization(authorization) response = commit(refund_uri(authorization), post) check_token_response(response, refund_uri(authorization), post, options) end
scrub(transcript)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 124 def scrub(transcript) transcript. gsub(%r((realm=\")\w+), '\1[FILTERED]'). gsub(%r((oauth_consumer_key=\")\w+), '\1[FILTERED]'). gsub(%r((oauth_nonce=\")\w+), '\1[FILTERED]'). gsub(%r((oauth_signature=\")[a-zA-Z%0-9]+), '\1[FILTERED]'). gsub(%r((oauth_token=\")\w+), '\1[FILTERED]'). gsub(%r((number\\\":\\\")\d+), '\1[FILTERED]'). gsub(%r((cvc\\\":\\\")\d+), '\1[FILTERED]'). gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r((access_token\\?":\\?")[\w\-\.]+)i, '\1[FILTERED]'). gsub(%r((refresh_token\\?":\\?")\w+), '\1[FILTERED]'). gsub(%r((refresh_token=)\w+), '\1[FILTERED]'). gsub(%r((Authorization: Bearer )[\w\-\.]+)i, '\1[FILTERED]\2') end
supports_scrubbing?()
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 120 def supports_scrubbing? true end
verify(credit_card, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 111 def verify(credit_card, options = {}) authorize(1.00, credit_card, options) end
void(authorization, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 104 def void(authorization, options = {}) _, request_id = split_authorization(authorization) response = commit(void_uri(request_id)) check_token_response(response, void_uri(request_id), {}, options) end
Private Instance Methods
add_address(post, options)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 147 def add_address(post, options) return unless post[:card]&.kind_of?(Hash) card_address = {} if address = options[:billing_address] || options[:address] card_address[:streetAddress] = address[:address1] card_address[:city] = address[:city] region = address[:state] || address[:region] card_address[:region] = region if region.present? card_address[:country] = address[:country] if address[:country].present? card_address[:postalCode] = address[:zip] if address[:zip] end post[:card][:address] = card_address end
add_amount(post, money, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 162 def add_amount(post, money, options = {}) currency = options[:currency] || currency(money) post[:amount] = localized_amount(money, currency) post[:currency] = currency.upcase end
add_charge_data(post, payment, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 142 def add_charge_data(post, payment, options = {}) add_payment(post, payment, options) add_address(post, options) end
add_context(post, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 185 def add_context(post, options = {}) post[:context] = { mobile: options.fetch(:mobile, false), isEcommerce: options.fetch(:ecommerce, true) } end
add_creditcard(post, creditcard, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 173 def add_creditcard(post, creditcard, options = {}) card = {} card[:number] = creditcard.number card[:expMonth] = '%02d' % creditcard.month card[:expYear] = creditcard.year card[:cvc] = creditcard.verification_value if creditcard.verification_value? card[:name] = creditcard.name if creditcard.name card[:commercialCardCode] = options[:card_code] if options[:card_code] post[:card] = card end
add_payment(post, payment, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 168 def add_payment(post, payment, options = {}) add_creditcard(post, payment, options) add_context(post, options) end
capture_uri(authorization)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 369 def capture_uri(authorization) "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/capture" end
check_token_response(response, endpoint, body = {}, options = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 286 def check_token_response(response, endpoint, body = {}, options = {}) return response unless @options[:refresh_token] return response unless options[:allow_refresh] return response unless response.params['code'] == 'AuthenticationFailed' refresh_access_token commit(endpoint, body) end
commit(uri, body = {}, method = :post)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 196 def commit(uri, body = {}, method = :post) endpoint = gateway_url + uri # The QuickBooks API returns HTTP 4xx on failed transactions, which causes a # ResponseError raise, so we have to inspect the response and discern between # a legitimate HTTP error and an actual gateway transactional error. headers = {} response = begin headers = headers(method, endpoint) method == :post ? ssl_post(endpoint, post_data(body), headers) : ssl_request(:get, endpoint, nil, headers) rescue ResponseError => e extract_response_body_or_raise(e) end response_object(response, headers) end
cvv_code_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 316 def cvv_code_from(response) if response['errors'].present? FRAUD_WARNING_CODES.include?(response['errors'].first['code']) ? 'I' : '' else success?(response) ? 'M' : '' end end
errors_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 334 def errors_from(response) if %w[AuthenticationFailed AuthorizationFailed].include?(response['code']) response['code'] else response['errors'].present? ? STANDARD_ERROR_CODE_MAPPING[response['errors'].first['code']] : '' end end
extract_response_body_or_raise(response_error)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 355 def extract_response_body_or_raise(response_error) begin parse(response_error.response.body) rescue JSON::ParserError raise response_error end response_error.response.body end
fraud_review_status_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 351 def fraud_review_status_from(response) response['errors'] && FRAUD_WARNING_CODES.include?(response['errors'].first['code']) end
gateway_url()
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 232 def gateway_url test? ? test_url : live_url end
headers(method, uri)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 240 def headers(method, uri) return oauth_v2_headers if @options[:refresh_token] raise ArgumentError, "Invalid HTTP method: #{method}. Valid methods are :post and :get" unless %i[post get].include?(method) request_uri = URI.parse(uri) # Following the guidelines from http://nouncer.com/oauth/authentication.html oauth_parameters = { oauth_nonce: generate_unique_id, oauth_timestamp: Time.now.to_i.to_s, oauth_signature_method: 'HMAC-SHA1', oauth_version: '1.0', oauth_consumer_key: @options[:consumer_key], oauth_token: @options[:access_token] } # prepare components for signature oauth_signature_base_string = [method.to_s.upcase, request_uri.to_s, oauth_parameters.to_param].map { |v| CGI.escape(v) }.join('&') oauth_signing_key = [@options[:consumer_secret], @options[:token_secret]].map { |v| CGI.escape(v) }.join('&') hmac_signature = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), oauth_signing_key, oauth_signature_base_string) # append signature to required OAuth parameters oauth_parameters[:oauth_signature] = CGI.escape(Base64.encode64(hmac_signature).chomp.delete("\n")) # prepare Authorization header string oauth_parameters = oauth_parameters.sort_by { |k, _| k }.to_h oauth_headers = ["OAuth realm=\"#{@options[:realm]}\""] oauth_headers += oauth_parameters.map { |k, v| "#{k}=\"#{v}\"" } { 'Content-type' => 'application/json', 'Request-Id' => generate_unique_id, 'Authorization' => oauth_headers.join(', ') } end
message_from(response)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 330 def message_from(response) response['errors'].present? ? response['errors'].map { |error_hash| error_hash['message'] }.join(' ') : response['status'] end
oauth_v2_headers()
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 277 def oauth_v2_headers { 'Content-Type' => 'application/json', 'Request-Id' => generate_unique_id, 'Accept' => 'application/json', 'Authorization' => "Bearer #{@options[:access_token]}" } end
parse(body)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 192 def parse(body) JSON.parse(body) end
post_data(data = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 236 def post_data(data = {}) data.to_json end
refresh_access_token()
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 295 def refresh_access_token post = {} post[:grant_type] = 'refresh_token' post[:refresh_token] = @options[:refresh_token] data = post.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&') basic_auth = Base64.strict_encode64("#{@options[:client_id]}:#{@options[:client_secret]}") headers = { 'Content-Type' => 'application/x-www-form-urlencoded', 'Accept' => 'application/json', 'Authorization' => "Basic #{basic_auth}" } response = ssl_post(REFRESH_URI, data, headers) json_response = JSON.parse(response) @options[:access_token] = json_response['access_token'] if json_response['access_token'] @options[:refresh_token] = json_response['refresh_token'] if json_response['refresh_token'] response end
refund_uri(authorization)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 365 def refund_uri(authorization) "#{ENDPOINT}/#{CGI.escape(authorization.to_s)}/refunds" end
response_object(raw_response, headers = {})
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 213 def response_object(raw_response, headers = {}) parsed_response = parse(raw_response) # Include access_token and refresh_token in params for OAuth 2.0 parsed_response['access_token'] = @options[:access_token] if @options[:refresh_token] parsed_response['refresh_token'] = @options[:refresh_token] if @options[:refresh_token] Response.new( success?(parsed_response), message_from(parsed_response), parsed_response, authorization: authorization_from(parsed_response, headers), test: test?, cvv_result: cvv_code_from(parsed_response), error_code: errors_from(parsed_response), fraud_review: fraud_review_status_from(parsed_response) ) end
success?(response)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 324 def success?(response) return FRAUD_WARNING_CODES.concat(['0']).include?(response['errors'].first['code']) if response['errors'] !%w[DECLINED CANCELLED].include?(response['status']) && !%w[AuthenticationFailed AuthorizationFailed].include?(response['code']) end
void_uri(request_id)
click to toggle source
# File lib/active_merchant/billing/gateways/quickbooks.rb, line 373 def void_uri(request_id) "#{VOID_ENDPOINT}/#{CGI.escape(request_id.to_s)}/void" end