lib/active_merchant/billing/gateways/sage_pay.rb in activemerchant-1.28.0 vs lib/active_merchant/billing/gateways/sage_pay.rb in activemerchant-1.29.0
- old
+ new
@@ -1,28 +1,28 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
- class SagePayGateway < Gateway
+ class SagePayGateway < Gateway
cattr_accessor :simulate
self.simulate = false
class_attribute :simulator_url
self.test_url = 'https://test.sagepay.com/gateway/service'
self.live_url = 'https://live.sagepay.com/gateway/service'
self.simulator_url = 'https://test.sagepay.com/Simulator'
-
+
APPROVED = 'OK'
-
+
TRANSACTIONS = {
:purchase => 'PAYMENT',
:credit => 'REFUND',
:authorization => 'DEFERRED',
:capture => 'RELEASE',
:void => 'VOID',
:abort => 'ABORT'
}
-
+
CREDIT_CARDS = {
:visa => "VISA",
:master => "MC",
:delta => "DELTA",
:solo => "SOLO",
@@ -33,119 +33,114 @@
:diners_club => "DC",
:jcb => "JCB"
}
ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/
-
+
AVS_CVV_CODE = {
- "NOTPROVIDED" => nil,
+ "NOTPROVIDED" => nil,
"NOTCHECKED" => 'X',
"MATCHED" => 'Y',
"NOTMATCHED" => 'N'
}
-
+
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club]
self.supported_countries = ['GB']
self.default_currency = 'GBP'
-
+
self.homepage_url = 'http://www.sagepay.com'
self.display_name = 'SagePay'
def initialize(options = {})
requires!(options, :login)
- @options = options
super
end
-
- def test?
- @options[:test] || super
- end
-
+
def purchase(money, credit_card, options = {})
requires!(options, :order_id)
-
+
post = {}
-
+
add_amount(post, money, options)
add_invoice(post, options)
add_credit_card(post, credit_card)
add_address(post, options)
add_customer_data(post, options)
commit(:purchase, post)
end
-
+
def authorize(money, credit_card, options = {})
requires!(options, :order_id)
-
+
post = {}
-
+
add_amount(post, money, options)
add_invoice(post, options)
add_credit_card(post, credit_card)
add_address(post, options)
add_customer_data(post, options)
commit(:authorization, post)
end
-
+
# You can only capture a transaction once, even if you didn't capture the full amount the first time.
def capture(money, identification, options = {})
post = {}
-
+
add_reference(post, identification)
add_release_amount(post, money, options)
-
+
commit(:capture, post)
end
-
+
def void(identification, options = {})
post = {}
-
+
add_reference(post, identification)
action = abort_or_void_from(identification)
commit(action, post)
end
# Refunding requires a new order_id to passed in, as well as a description
def refund(money, identification, options = {})
requires!(options, :order_id, :description)
-
+
post = {}
-
+
add_credit_reference(post, identification)
add_amount(post, money, options)
add_invoice(post, options)
-
+
commit(:credit, post)
end
def credit(money, identification, options = {})
deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
-
+
private
def add_reference(post, identification)
- order_id, transaction_id, authorization, security_key = identification.split(';')
-
+ 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
-
+
def add_credit_reference(post, identification)
- order_id, transaction_id, authorization, security_key = identification.split(';')
-
+ order_id, transaction_id, authorization, security_key = identification.split(';')
+
add_pair(post, :RelatedVendorTxCode, order_id)
add_pair(post, :RelatedVPSTxId, transaction_id)
add_pair(post, :RelatedTxAuthNo, authorization)
add_pair(post, :RelatedSecurityKey, security_key)
end
-
+
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
@@ -171,11 +166,11 @@
add_pair(post, :BillingCity, billing_address[:city])
add_pair(post, :BillingState, billing_address[:state]) if billing_address[:country] == 'US'
add_pair(post, :BillingCountry, billing_address[:country])
add_pair(post, :BillingPostCode, billing_address[:zip])
end
-
+
if shipping_address = options[:shipping_address] || billing_address
first_name, last_name = parse_first_and_last_name(shipping_address[:name])
add_pair(post, :DeliverySurname, last_name)
add_pair(post, :DeliveryFirstnames, first_name)
add_pair(post, :DeliveryAddress1, shipping_address[:address1])
@@ -193,63 +188,63 @@
end
def add_credit_card(post, credit_card)
add_pair(post, :CardHolder, credit_card.name, :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)
-
+
if requires_start_date_or_issue_number?(credit_card)
add_pair(post, :StartDate, format_date(credit_card.start_month, credit_card.start_year))
add_pair(post, :IssueNumber, credit_card.issue_number)
end
add_pair(post, :CardType, map_card_type(credit_card))
-
+
add_pair(post, :CV2, credit_card.verification_value)
end
-
+
def sanitize_order_id(order_id)
order_id.to_s.gsub(/[^-a-zA-Z0-9._]/, '')
end
-
+
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
-
+
# Check if it is an electron card
- if card_type == :visa && credit_card.number =~ ELECTRON
+ if card_type == :visa && credit_card.number =~ ELECTRON
CREDIT_CARDS[:electron]
- else
+ else
CREDIT_CARDS[card_type]
end
end
-
+
# MMYY format
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
-
+
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 => {
+ :avs_result => {
:street_match => AVS_CVV_CODE[ response["AddressResult"] ],
:postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ],
},
:cvv_result => AVS_CVV_CODE[ response["CV2Result"] ]
)
end
-
+
def authorization_from(response, params, action)
[ params[:VendorTxCode],
response["VPSTxId"],
response["TxAuthNo"],
response["SecurityKey"],
@@ -262,16 +257,16 @@
end
def url_for(action)
simulate ? build_simulator_url(action) : build_url(action)
end
-
+
def build_url(action)
endpoint = [ :purchase, :authorization ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase
"#{test? ? self.test_url : self.live_url}/#{endpoint}.vsp"
end
-
+
def build_simulator_url(action)
endpoint = [ :purchase, :authorization ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
"#{self.simulator_url}/#{endpoint}"
end
@@ -283,14 +278,14 @@
parameters.update(
:Vendor => @options[:login],
:TxType => TRANSACTIONS[action],
:VPSProtocol => "2.23"
)
-
+
parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
end
-
+
# SagePay returns data in the following format
# Key1=value1
# Key2=value2
def parse(body)
result = {}
@@ -304,15 +299,15 @@
post[key] = value if !value.blank? || options[:required]
end
def parse_first_and_last_name(value)
name = value.to_s.split(' ')
-
+
last_name = name.pop || ''
first_name = name.join(' ')
[ first_name[0,20], last_name[0,20] ]
end
-
+
def localized_amount(money, currency)
amount = amount(money)
CURRENCIES_WITHOUT_FRACTIONS.include?(currency.to_s) ? amount.split('.').first : amount
end
end