class ActiveMerchant::Billing::Shift4Gateway

Constants

DISALLOWED_ENTRY_MODE_ACTIONS
RECURRING_TYPE_TRANSACTIONS
SUCCESS_TRANSACTION_STATUS
TRANSACTIONS_WITHOUT_RESPONSE_CODE
URL_POSTFIX_MAPPING

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/shift4.rb, line 24
def initialize(options = {})
  requires!(options, :client_guid, :auth_token)
  @client_guid = options[:client_guid]
  @auth_token = options[:auth_token]
  @access_token = options[:access_token]
  super
end

Public Instance Methods

authorize(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 48
def authorize(money, payment_method, options = {})
  post = {}
  action = 'authorization'

  payment_method = get_card_token(payment_method) if payment_method.is_a?(String)
  add_datetime(post, options)
  add_invoice(post, money, options)
  add_clerk(post, options)
  add_transaction(post, options)
  add_card(action, post, payment_method, options)
  add_card_present(post, options)
  add_customer(post, payment_method, options)

  commit(action, post, options)
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 64
def capture(money, authorization, options = {})
  post = {}
  action = 'capture'
  options[:invoice] = get_invoice(authorization)

  add_datetime(post, options)
  add_invoice(post, money, options)
  add_clerk(post, options)
  add_transaction(post, options)
  add_card(action, post, get_card_token(authorization), options)

  commit(action, post, options)
end
credit(money, payment_method, options = {})
Alias for: refund
purchase(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 32
def purchase(money, payment_method, options = {})
  post = {}
  action = 'sale'

  payment_method = get_card_token(payment_method) if payment_method.is_a?(String)
  add_datetime(post, options)
  add_invoice(post, money, options)
  add_clerk(post, options)
  add_transaction(post, options)
  add_card(action, post, payment_method, options)
  add_card_present(post, options)
  add_customer(post, payment_method, options)

  commit(action, post, options)
end
refund(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 78
def refund(money, payment_method, options = {})
  post = {}
  action = 'refund'

  add_datetime(post, options)
  add_invoice(post, money, options)
  add_clerk(post, options)
  add_transaction(post, options)
  card_token = payment_method.is_a?(CreditCard) ? get_card_token(payment_method) : payment_method
  add_card(action, post, card_token, options)
  add_card_present(post, options)

  commit(action, post, options)
end
Also aliased as: credit
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 128
def scrub(transcript)
  transcript.
    gsub(%r(("Number\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("expirationDate\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("FirstName\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("LastName\\?"\s*:\s*\\?")[^"]*)i, '\1[FILTERED]').
    gsub(%r(("securityCode\\?":{\\?"\w+\\?":\d+,\\?"value\\?":\\?")\d*)i, '\1[FILTERED]')
end
setup_access_token() click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 137
def setup_access_token
  post = {}
  add_credentials(post, options)
  add_datetime(post, options)

  response = commit('accesstoken', post, request_headers('accesstoken', options))
  raise OAuthResponseError.new(response, response.params.fetch('result', [{}]).first.dig('error', 'longText')) unless response.success?

  response.params['result'].first['credential']['accessToken']
end
store(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 113
def store(credit_card, options = {})
  post = {}
  action = 'add'

  add_datetime(post, options)
  add_card(action, post, credit_card, options)
  add_customer(post, credit_card, options)

  commit(action, post, options)
end
supports_scrubbing?() click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 124
def supports_scrubbing?
  true
end
verify(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 100
def verify(credit_card, options = {})
  post = {}
  action = 'verify'
  post[:transaction] = {}

  add_datetime(post, options)
  add_card(action, post, credit_card, options)
  add_customer(post, credit_card, options)
  add_card_on_file(post[:transaction], options)

  commit(action, post, options)
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 95
def void(authorization, options = {})
  options[:invoice] = get_invoice(authorization)
  commit('invoice', {}, options)
end

Private Instance Methods

add_card(action, post, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 181
def add_card(action, post, payment_method, options)
  post[:card] = {}
  post[:card][:entryMode] = options[:entry_mode] || 'M' unless DISALLOWED_ENTRY_MODE_ACTIONS.include?(action)
  if payment_method.is_a?(CreditCard)
    post[:card][:expirationDate] = "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}"
    post[:card][:number] = payment_method.number
    post[:card][:securityCode] = {}
    post[:card][:securityCode][:indicator] = 1
    post[:card][:securityCode][:value] = payment_method.verification_value
  else
    post[:card] = {} if post[:card].nil?
    post[:card][:token] = {}
    post[:card][:token][:value] = payment_method
    post[:card][:expirationDate] = options[:expiration_date] if options[:expiration_date]
  end
end
add_card_on_file(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 225
def add_card_on_file(post, options)
  return unless options[:stored_credential] || options[:usage_indicator] || options[:indicator] || options[:scheduled_indicator] || options[:transaction_id]

  stored_credential = options[:stored_credential] || {}
  post[:cardOnFile] = {}
  post[:cardOnFile][:usageIndicator] = options[:usage_indicator] || (stored_credential[:initial_transaction] ? '01' : '02')
  post[:cardOnFile][:indicator] = options[:indicator] || '01'
  post[:cardOnFile][:scheduledIndicator] = options[:scheduled_indicator] || (RECURRING_TYPE_TRANSACTIONS.include?(stored_credential[:reason_type]) ? '01' : '02')
  post[:cardOnFile][:transactionId] = options[:transaction_id] || stored_credential[:network_transaction_id] if options[:transaction_id] || stored_credential[:network_transaction_id]
end
add_card_present(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 198
def add_card_present(post, options)
  post[:card] = {} unless post[:card].present?

  post[:card][:present] = options[:card_present] || 'N'
end
add_clerk(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 156
def add_clerk(post, options)
  post[:clerk] = {}
  post[:clerk][:numericId] = options[:clerk_id] || '1'
end
add_credentials(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 150
def add_credentials(post, options)
  post[:credential] = {}
  post[:credential][:clientGuid] = @client_guid
  post[:credential][:authToken] = @auth_token
end
add_customer(post, card, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 204
def add_customer(post, card, options)
  address = options[:billing_address] || {}

  post[:customer] = {}
  post[:customer][:addressLine1] = address[:address1] if address[:address1]
  post[:customer][:postalCode] = address[:zip] if address[:zip] && !address[:zip]&.to_s&.empty?
  post[:customer][:firstName] = card.first_name if card.is_a?(CreditCard) && card.first_name
  post[:customer][:lastName] = card.last_name if card.is_a?(CreditCard) && card.last_name
  post[:customer][:emailAddress] = options[:email] if options[:email]
  post[:customer][:ipAddress] = options[:ip] if options[:ip]
end
add_datetime(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 167
def add_datetime(post, options)
  post[:dateTime] = options[:date_time] || current_date_time(options)
end
add_invoice(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 161
def add_invoice(post, money, options)
  post[:amount] = {}
  post[:amount][:total] = amount(money.to_f)
  post[:amount][:tax] = options[:tax].to_f || 0.0
end
add_purchase_card(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 216
def add_purchase_card(post, options)
  return unless options[:customer_reference] || options[:destination_postal_code] || options[:product_descriptors]

  post[:purchaseCard] = {}
  post[:purchaseCard][:customerReference] = options[:customer_reference] if options[:customer_reference]
  post[:purchaseCard][:destinationPostalCode] = options[:destination_postal_code] if options[:destination_postal_code]
  post[:purchaseCard][:productDescriptors] = options[:product_descriptors] if options[:product_descriptors]
end
add_transaction(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 171
def add_transaction(post, options)
  post[:transaction] = {}
  post[:transaction][:invoice] = options[:invoice] || Time.new.to_i.to_s[1..3] + rand.to_s[2..7]
  post[:transaction][:notes] = options[:notes] if options[:notes].present?
  post[:transaction][:vendorReference] = options[:order_id]

  add_purchase_card(post[:transaction], options)
  add_card_on_file(post[:transaction], options)
end
authorization_from(action, response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 293
def authorization_from(action, response)
  return unless success_from(action, response)

  authorization = response.dig('result', 0, 'card', 'token', 'value').to_s
  invoice = response.dig('result', 0, 'transaction', 'invoice')
  authorization += "|#{invoice}" if invoice
  authorization
end
avs_result_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 289
def avs_result_from(response)
  AVSResult.new(code: response['result'].first&.dig('transaction', 'avs', 'result')) if response['result'].first&.dig('transaction', 'avs')
end
commit(action, parameters, option) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 236
def commit(action, parameters, option)
  url_postfix = URL_POSTFIX_MAPPING[action] || 'transactions'
  url = (test? ? "#{test_url}#{url_postfix}/#{action}" : "#{live_url}#{url_postfix}/#{action}")
  if action == 'invoice'
    response = parse(ssl_request(:delete, url, parameters.to_json, request_headers(action, option)))
  else
    response = parse(ssl_post(url, parameters.to_json, request_headers(action, option)))
  end

  Response.new(
    success_from(action, response),
    message_from(action, response),
    response,
    authorization: authorization_from(action, response),
    avs_result: avs_result_from(response),
    test: test?,
    error_code: error_code_from(action, response)
  )
end
current_date_time(options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 333
def current_date_time(options = {})
  time_zone = options[:merchant_time_zone] || 'Pacific Time (US & Canada)'
  time = Time.now.in_time_zone(time_zone)
  offset = Time.now.in_time_zone(time_zone).formatted_offset

  time.strftime('%Y-%m-%dT%H:%M:%S.%3N') + offset
end
error(response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 328
def error(response)
  server_error = { 'longText' => response['error'] } if response['error']
  server_error || response['result'].first['error']
end
error_code_from(action, response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 275
def error_code_from(action, response)
  code = response['result'].first&.dig('transaction', 'responseCode')
  primary_code = response['result'].first['error'].present?
  return unless code == 'D' || primary_code == true || success_from(action, response)

  if response['result'].first&.dig('transaction', 'hostResponse')
    response['result'].first&.dig('transaction', 'hostResponse', 'reasonCode')
  elsif response['result'].first['error']
    response['result'].first&.dig('error', 'primaryCode')
  else
    response['result'].first&.dig('transaction', 'responseCode')
  end
end
get_card_token(authorization) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 302
def get_card_token(authorization)
  authorization.is_a?(CreditCard) ? authorization : authorization.split('|')[0]
end
get_invoice(authorization) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 306
def get_invoice(authorization)
  authorization.is_a?(CreditCard) ? authorization : authorization.split('|')[1]
end
handle_response(response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 256
def handle_response(response)
  case response.code.to_i
  when 200...300, 400, 401, 500
    response.body
  else
    raise ResponseError.new(response)
  end
end
message_from(action, response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 271
def message_from(action, response)
  success_from(action, response) ? 'Transaction successful' : (error(response)&.dig('longText') || response['result'].first&.dig('transaction', 'hostResponse', 'reasonDescription') || 'Transaction declined')
end
parse(body) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 265
def parse(body)
  return {} if body == ''

  JSON.parse(body)
end
request_headers(action, options) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 310
def request_headers(action, options)
  headers = {
    'Content-Type' => 'application/json'
  }
  headers['AccessToken'] = @access_token
  headers['Invoice'] = options[:invoice] if action != 'capture' && options[:invoice].present?
  headers['InterfaceVersion'] = '1'
  headers['InterfaceName'] = 'Spreedly'
  headers['CompanyName'] = 'Spreedly'
  headers
end
success_from(action, response) click to toggle source
# File lib/active_merchant/billing/gateways/shift4.rb, line 322
def success_from(action, response)
  success = error(response).nil?
  success &&= SUCCESS_TRANSACTION_STATUS.include?(response['result'].first['transaction']['responseCode']) unless TRANSACTIONS_WITHOUT_RESPONSE_CODE.include?(action)
  success
end