class ActiveMerchant::Billing::SagePayGateway

Constants

APPROVED
AVS_CODE
CREDIT_CARDS
CVV_CODE
OPTIONAL_REQUEST_FIELDS
TRANSACTIONS

Public Class Methods

new(options = {}) click to toggle source
Calls superclass method ActiveMerchant::Billing::Gateway::new
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 79
def initialize(options = {})
  requires!(options, :login)
  @protocol_version = options.fetch(:protocol_version, '3.00')
  super
end

Public Instance Methods

authorize(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 103
def authorize(money, payment_method, options = {})
  requires!(options, :order_id)

  post = {}

  add_three_ds_data(post, options)
  add_stored_credentials_data(post, options)
  add_override_protocol_version(options)
  add_amount(post, money, options)
  add_invoice(post, options)
  add_payment_method(post, payment_method, options)
  add_address(post, options)
  add_customer_data(post, options)
  add_optional_data(post, options)

  commit(:authorization, post)
end
capture(money, identification, options = {}) click to toggle source

You can only capture a transaction once, even if you didn’t capture the full amount the first time.

# File lib/active_merchant/billing/gateways/sage_pay.rb, line 122
def capture(money, identification, options = {})
  post = {}

  add_override_protocol_version(options)
  add_reference(post, identification)
  add_release_amount(post, money, options)

  commit(:capture, post)
end
credit(money, identification, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 156
def credit(money, identification, options = {})
  ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE
  refund(money, identification, options)
end
purchase(money, payment_method, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 85
def purchase(money, payment_method, options = {})
  requires!(options, :order_id)

  post = {}

  add_override_protocol_version(options)
  add_three_ds_data(post, options)
  add_stored_credentials_data(post, options)
  add_amount(post, money, options)
  add_invoice(post, options)
  add_payment_method(post, payment_method, options)
  add_address(post, options)
  add_customer_data(post, options)
  add_optional_data(post, options)

  commit((past_purchase_reference?(payment_method) ? :repeat : :purchase), post)
end
refund(money, identification, options = {}) click to toggle source

Refunding requires a new order_id to passed in, as well as a description

# File lib/active_merchant/billing/gateways/sage_pay.rb, line 143
def refund(money, identification, options = {})
  requires!(options, :order_id, :description)

  post = {}

  add_override_protocol_version(options)
  add_related_reference(post, identification)
  add_amount(post, money, options)
  add_invoice(post, options)

  commit(:credit, post)
end
scrub(transcript) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 188
def scrub(transcript)
  transcript.
    gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
    gsub(%r((&?CardNumber=)\d+(&?)), '\1[FILTERED]\2').
    gsub(%r((&?CV2=)\d+(&?)), '\1[FILTERED]\2')
end
store(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 161
def store(credit_card, options = {})
  post = {}
  add_override_protocol_version(options)
  add_credit_card(post, credit_card)
  add_currency(post, 0, options)

  commit(:store, post)
end
supports_scrubbing() click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 184
def supports_scrubbing
  true
end
unstore(token, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 170
def unstore(token, options = {})
  post = {}
  add_override_protocol_version(options)
  add_token(post, token)
  commit(:unstore, post)
end
verify(credit_card, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 177
def verify(credit_card, options = {})
  MultiResponse.run(:use_first_response) do |r|
    r.process { authorize(100, credit_card, options) }
    r.process(:ignore_result) { void(r.authorization, options) }
  end
end
void(identification, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 132
def void(identification, options = {})
  post = {}

  add_override_protocol_version(options)
  add_reference(post, identification)
  action = abort_or_void_from(identification)

  commit(action, post)
end

Private Instance Methods

abort_or_void_from(identification) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 440
def abort_or_void_from(identification)
  original_transaction = identification.split(';').last
  original_transaction == 'authorization' ? :abort : :void
end
add_address(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 307
def add_address(post, options)
  if billing_address = options[:billing_address] || options[:address]
    first_name, last_name = split_names(billing_address[:name])
    add_pair(post, :BillingSurname, truncate(last_name, 20))
    add_pair(post, :BillingFirstnames, truncate(first_name, 20))
    add_pair(post, :BillingAddress1, truncate(billing_address[:address1], 100))
    add_pair(post, :BillingAddress2, truncate(billing_address[:address2], 100))
    add_pair(post, :BillingCity, truncate(billing_address[:city], 40))
    add_pair(post, :BillingState, truncate(billing_address[:state], 2)) if usa?(billing_address[:country])
    add_pair(post, :BillingCountry, truncate(billing_address[:country], 2))
    add_pair(post, :BillingPhone, sanitize_phone(billing_address[:phone]))
    add_pair(post, :BillingPostCode, truncate(billing_address[:zip], 10))
  end

  if shipping_address = options[:shipping_address] || billing_address
    first_name, last_name = split_names(shipping_address[:name])
    add_pair(post, :DeliverySurname, truncate(last_name, 20))
    add_pair(post, :DeliveryFirstnames, truncate(first_name, 20))
    add_pair(post, :DeliveryAddress1, truncate(shipping_address[:address1], 100))
    add_pair(post, :DeliveryAddress2, truncate(shipping_address[:address2], 100))
    add_pair(post, :DeliveryCity, truncate(shipping_address[:city], 40))
    add_pair(post, :DeliveryState, truncate(shipping_address[:state], 2)) if usa?(shipping_address[:country])
    add_pair(post, :DeliveryCountry, truncate(shipping_address[:country], 2))
    add_pair(post, :DeliveryPhone, sanitize_phone(shipping_address[:phone]))
    add_pair(post, :DeliveryPostCode, truncate(shipping_address[:zip], 10))
  end
end
add_amount(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 278
def add_amount(post, money, options)
  currency = options[:currency] || currency(money)
  add_pair(post, :Amount, localized_amount(money, currency), required: true)
  add_pair(post, :Currency, currency, required: true)
end
add_browser_info(post, browser_info) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 211
def add_browser_info(post, browser_info)
  add_pair(post, :BrowserAcceptHeader, browser_info[:accept_header])
  add_pair(post, :BrowserColorDepth, browser_info[:depth])
  add_pair(post, :BrowserJavascriptEnabled, format_boolean(browser_info[:java]))
  add_pair(post, :BrowserJavaEnabled, format_boolean(browser_info[:java]))
  add_pair(post, :BrowserLanguage, browser_info[:language])
  add_pair(post, :BrowserScreenHeight, browser_info[:height])
  add_pair(post, :BrowserScreenWidth, browser_info[:width])
  add_pair(post, :BrowserTZ, browser_info[:timezone])
  add_pair(post, :BrowserUserAgent, browser_info[:user_agent])
  add_pair(post, :ChallengeWindowSize, browser_info[:browser_size])
end
add_credit_card(post, credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 352
def add_credit_card(post, credit_card)
  add_pair(post, :CardHolder, truncate(credit_card.name, 50), required: true)
  add_pair(post, :CardNumber, credit_card.number, required: true)

  add_pair(post, :ExpiryDate, format_date(credit_card.month, credit_card.year), required: true)
  add_pair(post, :CardType, map_card_type(credit_card))

  add_pair(post, :CV2, credit_card.verification_value)
end
add_currency(post, money, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 284
def add_currency(post, money, options)
  currency = options[:currency] || currency(money)
  add_pair(post, :Currency, currency, required: true)
end
add_customer_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 294
def add_customer_data(post, options)
  add_pair(post, :CustomerEMail, truncate(options[:email], 255)) unless options[:email].blank?
  add_pair(post, :ClientIPAddress, options[:ip])
end
add_invoice(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 335
def add_invoice(post, options)
  add_pair(post, :VendorTxCode, sanitize_order_id(options[:order_id]), required: true)
  add_pair(post, :Description, truncate(options[:description] || options[:order_id], 100))
end
add_optional_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 299
def add_optional_data(post, options)
  add_pair(post, :CreateToken, 1) unless options[:store].blank?

  OPTIONAL_REQUEST_FIELDS.each do |gateway_option, sagepay_field|
    add_pair(post, sagepay_field, options[gateway_option])
  end
end
add_override_protocol_version(options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 197
def add_override_protocol_version(options)
  @protocol_version = options[:protocol_version] if options[:protocol_version]
end
add_pair(post, key, value, options = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 497
def add_pair(post, key, value, options = {})
  post[key] = value if !value.blank? || options[:required]
end
add_payment_method(post, payment_method, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 340
def add_payment_method(post, payment_method, options)
  if payment_method.is_a?(String)
    if past_purchase_reference?(payment_method)
      add_related_reference(post, payment_method)
    else
      add_token_details(post, payment_method, options)
    end
  else
    add_credit_card(post, payment_method)
  end
end
add_reference(post, identification) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 260
def add_reference(post, identification)
  order_id, transaction_id, authorization, security_key = identification.split(';')

  add_pair(post, :VendorTxCode, order_id)
  add_pair(post, :VPSTxId, transaction_id)
  add_pair(post, :TxAuthNo, authorization)
  add_pair(post, :SecurityKey, security_key)
end
add_release_amount(post, money, options) click to toggle source

doesn’t actually use the currency – dodgy!

# File lib/active_merchant/billing/gateways/sage_pay.rb, line 290
def add_release_amount(post, money, options)
  add_pair(post, :ReleaseAmount, amount(money), required: true)
end
add_stored_credentials_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 224
def add_stored_credentials_data(post, options)
  return unless @protocol_version == '4.00'
  return unless stored_credential = options[:stored_credential]

  initiator = stored_credential[:initiator] == 'cardholder' ? 'CIT' : 'MIT'
  cof_usage = if stored_credential[:initial_transaction] && initiator == 'CIT'
                'FIRST'
              elsif !stored_credential[:initial_transaction] && initiator == 'MIT'
                'SUBSEQUENT'
              end

  add_pair(post, :COFUsage, cof_usage) if cof_usage
  add_pair(post, :InitiatedTYPE, initiator)
  add_pair(post, :SchemeTraceID, stored_credential[:network_transaction_id]) if stored_credential[:network_transaction_id]

  reasoning = stored_credential[:reason_type] == 'installment' ? 'instalment' : stored_credential[:reason_type]
  add_pair(post, :MITType, reasoning.upcase)

  if %w(instalment recurring).any?(reasoning)
    add_pair(post, :RecurringExpiry, options[:recurring_expiry])
    add_pair(post, :RecurringFrequency, options[:recurring_frequency])
    add_pair(post, :PurchaseInstalData, options[:installment_data])
  end
end
add_three_ds_data(post, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 201
def add_three_ds_data(post, options)
  return unless @protocol_version == '4.00'
  return unless three_ds_2_options = options[:three_ds_2]

  add_pair(post, :ThreeDSNotificationURL, three_ds_2_options[:notification_url])
  return unless three_ds_2_options[:browser_info]

  add_browser_info(post, three_ds_2_options[:browser_info])
end
add_token(post, token) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 368
def add_token(post, token)
  add_pair(post, :Token, token)
end
add_token_details(post, token, options) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 362
def add_token_details(post, token, options)
  add_token(post, token)
  add_pair(post, :StoreToken, options[:customer])
  add_pair(post, :CV2, options[:verification_value])
end
authorization_from(response, params, action) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 427
def authorization_from(response, params, action)
  case action
  when :store
    response['Token']
  else
    [params[:VendorTxCode],
     response['VPSTxId'] || params[:VPSTxId],
     response['TxAuthNo'],
     response['SecurityKey'] || params[:SecurityKey],
     action].join(';')
  end
end
build_simulator_url(action) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 459
def build_simulator_url(action)
  endpoint = %i[purchase authorization].include?(action) ? 'VSPDirectGateway.asp' : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
  "#{self.simulator_url}/#{endpoint}"
end
build_url(action) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 449
def build_url(action)
  endpoint =
    case action
    when :purchase, :authorization then 'vspdirect-register'
    when :store then 'directtoken'
    else TRANSACTIONS[action].downcase
    end
  "#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp"
end
commit(action, parameters) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 410
def commit(action, parameters)
  response = parse(ssl_post(url_for(action), post_data(action, parameters)))

  Response.new(
    response['Status'] == APPROVED,
    message_from(response),
    response,
    test: test?,
    authorization: authorization_from(response, parameters, action),
    avs_result: {
      street_match: AVS_CODE[response['AddressResult']],
      postal_match: AVS_CODE[response['PostCodeResult']]
    },
    cvv_result: CVV_CODE[response['CV2Result']]
  )
end
format_boolean(value) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 480
def format_boolean(value)
  return if value.nil?

  value ? '1' : '0'
end
format_date(month, year) click to toggle source

MMYY format

# File lib/active_merchant/billing/gateways/sage_pay.rb, line 401
def format_date(month, year)
  return nil if year.blank? || month.blank?

  year  = sprintf('%.4i', year)
  month = sprintf('%.2i', month)

  "#{month}#{year[-2..-1]}"
end
map_card_type(credit_card) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 388
def map_card_type(credit_card)
  raise ArgumentError, 'The credit card type must be provided' if card_brand(credit_card).blank?

  card_type = card_brand(credit_card).to_sym

  if card_type == :visa && credit_card.electron?
    CREDIT_CARDS[:electron]
  else
    CREDIT_CARDS[card_type]
  end
end
message_from(response) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 464
def message_from(response)
  response['Status'] == APPROVED ? 'Success' : (response['StatusDetail'] || 'Unspecified error') # simonr 20080207 can't actually get non-nil blanks, so this is shorter
end
parse(body) click to toggle source

SagePay returns data in the following format Key1=value1 Key2=value2

# File lib/active_merchant/billing/gateways/sage_pay.rb, line 489
def parse(body)
  result = {}
  body.to_s.each_line do |pair|
    result[$1] = $2 if pair.strip =~ /\A([^=]+)=(.+)\Z/im
  end
  result
end
past_purchase_reference?(payment_method) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 501
def past_purchase_reference?(payment_method)
  return false unless payment_method.is_a?(String)

  %w(purchase repeat).include?(payment_method.split(';').last)
end
post_data(action, parameters = {}) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 468
def post_data(action, parameters = {})
  parameters.update(
    Vendor: @options[:login],
    TxType: TRANSACTIONS[action],
    VPSProtocol: @protocol_version
  )

  parameters.update(ReferrerID: application_id) if application_id && (application_id != Gateway.application_id)

  parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
end
sanitize_order_id(order_id) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 372
def sanitize_order_id(order_id)
  cleansed = order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '')
  truncate(cleansed, 40)
end
sanitize_phone(phone) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 377
def sanitize_phone(phone)
  return nil unless phone

  cleansed = phone.to_s.gsub(/[^0-9+]/, '')
  truncate(cleansed, 20)
end
truncate(value, max_size) click to toggle source
Calls superclass method
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 249
def truncate(value, max_size)
  return nil unless value
  return value.to_s if CGI.escape(value.to_s).length <= max_size

  if value.size > max_size
    truncate(super(value, max_size), max_size)
  else
    truncate(value.to_s.chop, max_size)
  end
end
url_for(action) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 445
def url_for(action)
  simulate ? build_simulator_url(action) : build_url(action)
end
usa?(country) click to toggle source
# File lib/active_merchant/billing/gateways/sage_pay.rb, line 384
def usa?(country)
  truncate(country, 2) == 'US'
end