class ActiveMerchant::Billing::CommerceHubGateway

Constants

ENDPOINTS
SCHEDULED_REASON_TYPES
STANDARD_ERROR_CODE_MAPPING

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 25
def initialize(options = {})
  requires!(options, :api_key, :api_secret, :merchant_id, :terminal_id)
  super
end

Public Instance Methods

authorize(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 41
def authorize(money, payment, options = {})
  post = {}
  options[:capture_flag] = false
  options[:create_token] = false

  add_transaction_details(post, options, 'sale')
  build_purchase_and_auth_request(post, money, payment, options)

  commit('sale', post, options)
end
capture(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 52
def capture(money, authorization, options = {})
  post = {}
  options[:capture_flag] = true
  add_invoice(post, money, options)
  add_transaction_details(post, options, 'capture')
  add_reference_transaction_details(post, authorization, options, :capture)
  add_dynamic_descriptors(post, options)

  commit('sale', post, options)
end
credit(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 72
def credit(money, payment_method, options = {})
  post = {}
  add_invoice(post, money, options)
  add_transaction_interaction(post, options)
  add_payment(post, payment_method, options)

  commit('refund', post, options)
end
purchase(money, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 30
def purchase(money, payment, options = {})
  post = {}
  options[:capture_flag] = true
  options[:create_token] = false

  add_transaction_details(post, options, 'sale')
  build_purchase_and_auth_request(post, money, payment, options)

  commit('sale', post, options)
end
refund(money, authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 63
def refund(money, authorization, options = {})
  post = {}
  add_invoice(post, money, options) if money
  add_transaction_details(post, options)
  add_reference_transaction_details(post, authorization, options, :refund)

  commit('refund', post, options)
end
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 111
def scrub(transcript)
  transcript.
    gsub(%r((Authorization: )[a-zA-Z0-9+./=]+), '\1[FILTERED]').
    gsub(%r((Api-Key: )\w+), '\1[FILTERED]').
    gsub(%r(("apiKey\\?":\\?")\w+), '\1[FILTERED]').
    gsub(%r(("cardData\\?":\\?")\d+), '\1[FILTERED]').
    gsub(%r(("securityCode\\?":\\?")\d+), '\1[FILTERED]').
    gsub(%r(("cavv\\?":\\?")\w+), '\1[FILTERED]')
end
store(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 89
def store(credit_card, options = {})
  post = {}
  add_payment(post, credit_card, options)
  add_billing_address(post, credit_card, options)
  add_transaction_details(post, options)
  add_transaction_interaction(post, options)

  commit('vault', post, options)
end
supports_scrubbing?() click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 107
def supports_scrubbing?
  true
end
verify(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 99
def verify(credit_card, options = {})
  post = {}
  add_payment(post, credit_card, options)
  add_billing_address(post, credit_card, options)

  commit('verify', post, options)
end
void(authorization, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 81
def void(authorization, options = {})
  post = {}
  add_transaction_details(post, options)
  add_reference_transaction_details(post, authorization, options, :void)

  commit('void', post, options)
end

Private Instance Methods

add_billing_address(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 171
def add_billing_address(post, payment, options)
  return unless billing = options[:billing_address]

  billing_address = {}
  name_from_address(billing_address, billing) || name_from_payment(billing_address, payment)
  address = {}
  address[:street] = billing[:address1] if billing[:address1]
  address[:houseNumberOrName] = billing[:address2] if billing[:address2]
  address[:recipientNameOrAddress] = billing[:name] if billing[:name]
  address[:city] = billing[:city] if billing[:city]
  address[:stateOrProvince] = billing[:state] if billing[:state]
  address[:postalCode] = billing[:zip] if billing[:zip]
  address[:country] = billing[:country] if billing[:country]

  billing_address[:address] = address unless address.empty?
  if billing[:phone_number]
    billing_address[:phone] = {}
    billing_address[:phone][:phoneNumber] = billing[:phone_number]
  end
  post[:billingAddress] = billing_address
end
add_credit_card(source, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 281
def add_credit_card(source, payment, options)
  source[:sourceType] = 'PaymentCard'
  source[:card] = {}
  source[:card][:cardData] = payment.number
  source[:card][:expirationMonth] = format(payment.month, :two_digits) if payment.month
  source[:card][:expirationYear] = format(payment.year, :four_digits) if payment.year
  if payment.verification_value
    source[:card][:securityCode] = payment.verification_value
    source[:card][:securityCodeIndicator] = 'PROVIDED'
  end
end
add_decrypted_wallet(source, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 304
def add_decrypted_wallet(source, payment, options)
  source[:sourceType] = 'DecryptedWallet'
  source[:card] = {}
  source[:card][:cardData] = payment.number
  source[:card][:expirationMonth] = format(payment.month, :two_digits)
  source[:card][:expirationYear] = format(payment.year, :four_digits)
  source[:cavv] = payment.payment_cryptogram
  source[:walletType] = payment.source.to_s.upcase
end
add_dynamic_descriptors(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 241
def add_dynamic_descriptors(post, options)
  dynamic_descriptors_fields = %i[mcc merchant_name customer_service_number service_entitlement dynamic_descriptors_address]
  return unless dynamic_descriptors_fields.any? { |key| options.include?(key) }

  dynamic_descriptors = {}
  dynamic_descriptors[:mcc] = options[:mcc] if options[:mcc]
  dynamic_descriptors[:merchantName] = options[:merchant_name] if options[:merchant_name]
  dynamic_descriptors[:customerServiceNumber] = options[:customer_service_number] if options[:customer_service_number]
  dynamic_descriptors[:serviceEntitlement] = options[:service_entitlement] if options[:service_entitlement]
  dynamic_descriptors[:address] = options[:dynamic_descriptors_address] if options[:dynamic_descriptors_address]

  post[:dynamicDescriptors] = dynamic_descriptors
end
add_invoice(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 264
def add_invoice(post, money, options)
  post[:amount] = {
    total: amount(money).to_f,
    currency: options[:currency] || self.default_currency
  }
end
add_merchant_details(post) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 355
def add_merchant_details(post)
  post[:merchantDetails] = {}
  post[:merchantDetails][:terminalId] = @options[:terminal_id]
  post[:merchantDetails][:merchantId] = @options[:merchant_id]
end
add_payment(post, payment, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 314
def add_payment(post, payment, options = {})
  source = {}
  case payment
  when NetworkTokenizationCreditCard
    add_decrypted_wallet(source, payment, options)
  when CreditCard
    if options[:encryption_data].present?
      source[:sourceType] = 'PaymentCard'
      source[:encryptionData] = options[:encryption_data]
    else
      add_credit_card(source, payment, options)
    end
  when String
    add_payment_token(source, payment, options)
  end
  post[:source] = source
end
add_payment_token(source, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 293
def add_payment_token(source, payment, options)
  source[:sourceType] = 'PaymentToken'
  source[:tokenData] = payment
  source[:tokenSource] = options[:token_source] if options[:token_source]
  if options[:card_expiration_month] || options[:card_expiration_year]
    source[:card] = {}
    source[:card][:expirationMonth] = options[:card_expiration_month] if options[:card_expiration_month]
    source[:card][:expirationYear] = options[:card_expiration_year] if options[:card_expiration_year]
  end
end
add_reference_transaction_details(post, authorization, options, action = nil) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 255
def add_reference_transaction_details(post, authorization, options, action = nil)
  reference_details = {}
  _merchant_reference, transaction_id = authorization.include?('|') ? authorization.split('|') : [nil, authorization]

  reference_details[:referenceTransactionId] = transaction_id
  reference_details[:referenceTransactionType] = (options[:reference_transaction_type] || 'CHARGES') unless action == :capture
  post[:referenceTransactionDetails] = reference_details.compact
end
add_shipping_address(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 209
def add_shipping_address(post, options)
  return unless shipping = options[:shipping_address]

  shipping_address = {}
  address = {}
  address[:street] = shipping[:address1] if shipping[:address1]
  address[:houseNumberOrName] = shipping[:address2] if shipping[:address2]
  address[:recipientNameOrAddress] = shipping[:name] if shipping[:name]
  address[:city] = shipping[:city] if shipping[:city]
  address[:stateOrProvince] = shipping[:state] if shipping[:state]
  address[:postalCode] = shipping[:zip] if shipping[:zip]
  address[:country] = shipping[:country] if shipping[:country]

  shipping_address[:address] = address unless address.empty?
  if shipping[:phone_number]
    shipping_address[:phone] = {}
    shipping_address[:phone][:phoneNumber] = shipping[:phone_number]
  end
  post[:shippingAddress] = shipping_address
end
add_stored_credentials(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 271
def add_stored_credentials(post, options)
  return unless stored_credential = options[:stored_credential]

  post[:storedCredentials] = {}
  post[:storedCredentials][:sequence] = stored_credential[:initial_transaction] ? 'FIRST' : 'SUBSEQUENT'
  post[:storedCredentials][:initiator] = stored_credential[:initiator] == 'merchant' ? 'MERCHANT' : 'CARD_HOLDER'
  post[:storedCredentials][:scheduled] = SCHEDULED_REASON_TYPES.include?(stored_credential[:reason_type])
  post[:storedCredentials][:schemeReferenceTransactionId] = options[:scheme_reference_transaction_id] || stored_credential[:network_transaction_id]
end
add_three_d_secure(post, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 123
def add_three_d_secure(post, payment, options)
  return unless three_d_secure = options[:three_d_secure]

  post[:additionalData3DS] = {
    dsTransactionId: three_d_secure[:ds_transaction_id],
    authenticationStatus: three_d_secure[:authentication_response_status],
    serviceProviderTransactionId: three_d_secure[:three_ds_server_trans_id],
    acsTransactionId: three_d_secure[:acs_transaction_id],
    mpiData: {
      cavv: three_d_secure[:cavv],
      eci: three_d_secure[:eci],
      xid: three_d_secure[:xid]
    }.compact,
    versionData: { recommendedVersion: three_d_secure[:version] }
  }.compact
end
add_transaction_details(post, options, action = nil) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 150
def add_transaction_details(post, options, action = nil)
  details = {
    captureFlag: options[:capture_flag],
    createToken: options[:create_token],
    physicalGoodsIndicator: [true, 'true'].include?(options[:physical_goods_indicator])
  }

  if options[:order_id].present? && action == 'sale'
    details[:merchantOrderId] = options[:order_id]
    details[:merchantTransactionId] = options[:order_id]
  end

  if action != 'capture'
    details[:merchantInvoiceNumber] = options[:merchant_invoice_number] || rand.to_s[2..13]
    details[:primaryTransactionType] = options[:primary_transaction_type]
    details[:accountVerification] = options[:account_verification]
  end

  post[:transactionDetails] = details.compact
end
add_transaction_interaction(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 140
def add_transaction_interaction(post, options)
  post[:transactionInteraction] = {}
  post[:transactionInteraction][:origin] = options[:origin] || 'ECOM'
  post[:transactionInteraction][:eciIndicator] = options[:eci_indicator] || 'CHANNEL_ENCRYPTED'
  post[:transactionInteraction][:posConditionCode] = options[:pos_condition_code] || 'CARD_NOT_PRESENT_ECOM'
  post[:transactionInteraction][:posEntryMode] = (options[:pos_entry_mode] || 'MANUAL') unless options[:encryption_data].present?
  post[:transactionInteraction][:additionalPosInformation] = {}
  post[:transactionInteraction][:additionalPosInformation][:dataEntrySource] = options[:data_entry_source] || 'UNSPECIFIED'
end
authorization_from(action, response, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 411
def authorization_from(action, response, options)
  case action
  when 'vault'
    response.dig('paymentTokens', 0, 'tokenData')
  when 'sale'
    [options[:order_id] || '', response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')].join('|')
  else
    response.dig('gatewayResponse', 'transactionProcessingDetails', 'transactionId')
  end
end
build_purchase_and_auth_request(post, money, payment, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 230
def build_purchase_and_auth_request(post, money, payment, options)
  add_three_d_secure(post, payment, options)
  add_invoice(post, money, options)
  add_payment(post, payment, options)
  add_stored_credentials(post, options)
  add_transaction_interaction(post, options)
  add_billing_address(post, payment, options)
  add_shipping_address(post, options)
  add_dynamic_descriptors(post, options)
end
commit(action, parameters, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 361
def commit(action, parameters, options)
  url = (test? ? test_url : live_url) + ENDPOINTS[action]
  add_merchant_details(parameters)
  response = parse(ssl_post(url, parameters.to_json, headers(parameters.to_json, options)))

  Response.new(
    success_from(response, action),
    message_from(response, action),
    response,
    authorization: authorization_from(action, response, options),
    test: test?,
    error_code: error_code_from(response, action),
    avs_result: AVSResult.new(code: get_avs_cvv(response, 'avs')),
    cvv_result: CVVResult.new(get_avs_cvv(response, 'cvv'))
  )
end
error_code_from(response, action) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 422
def error_code_from(response, action)
  response.dig('error', 0, 'code') unless success_from(response, action)
end
get_avs_cvv(response, type = 'avs') click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 378
def get_avs_cvv(response, type = 'avs')
  response.dig(
    'paymentReceipt',
    'processorResponseDetails',
    'bankAssociationDetails',
    'avsSecurityCodeResponse',
    'association',
    type == 'avs' ? 'avsCode' : 'securityCodeResponse'
  )
end
handle_response(response) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 389
def handle_response(response)
  case response.code.to_i
  when 200...300, 400, 401, 429
    response.body
  else
    raise ResponseError.new(response)
  end
end
headers(request, options) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 336
def headers(request, options)
  time = DateTime.now.strftime('%Q').to_s
  client_request_id = options[:client_request_id] || rand.to_s[2..8]
  raw_signature = @options[:api_key] + client_request_id.to_s + time + request
  hmac = OpenSSL::HMAC.digest('sha256', @options[:api_secret], raw_signature)
  signature = Base64.strict_encode64(hmac.to_s).to_s
  custom_headers = options.fetch(:headers_identifiers, {})
  {
    'Client-Request-Id' => client_request_id,
    'Api-Key' => @options[:api_key],
    'Timestamp' => time,
    'Accept-Language' => 'application/json',
    'Auth-Token-Type' => 'HMAC',
    'Content-Type' => 'application/json',
    'Accept' => 'application/json',
    'Authorization' => signature
  }.merge!(custom_headers)
end
message_from(response, action = nil) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 404
def message_from(response, action = nil)
  return response.dig('error', 0, 'message') if response['error'].present?
  return response.dig('gatewayResponse', 'transactionState') if action == 'verify'

  response.dig('paymentReceipt', 'processorResponseDetails', 'responseMessage') || response.dig('gatewayResponse', 'transactionType')
end
name_from_address(billing_address, billing) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 200
def name_from_address(billing_address, billing)
  return unless address = billing

  first_name, last_name = split_names(address[:name]) if address[:name]

  billing_address[:firstName] = first_name if first_name
  billing_address[:lastName] = last_name if last_name
end
name_from_payment(billing_address, payment) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 193
def name_from_payment(billing_address, payment)
  return unless payment.respond_to?(:first_name) && payment.respond_to?(:last_name)

  billing_address[:firstName] = payment.first_name if payment.first_name
  billing_address[:lastName] = payment.last_name if payment.last_name
end
parse(body) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 332
def parse(body)
  JSON.parse(body)
end
success_from(response, action = nil) click to toggle source
# File lib/active_merchant/billing/gateways/commerce_hub.rb, line 398
def success_from(response, action = nil)
  return message_from(response, action) == 'VERIFIED' if action == 'verify'

  (response.dig('paymentReceipt', 'processorResponseDetails', 'responseCode') || response.dig('paymentTokens', 0, 'tokenResponseCode')) == '000'
end