lib/active_merchant/billing/gateways/blue_snap.rb in activemerchant-1.90.0 vs lib/active_merchant/billing/gateways/blue_snap.rb in activemerchant-1.91.0
- old
+ new
@@ -3,11 +3,11 @@
module ActiveMerchant
module Billing
class BlueSnapGateway < Gateway
self.test_url = 'https://sandbox.bluesnap.com/services/2'
self.live_url = 'https://ws.bluesnap.com/services/2'
- self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE)
+ self.supported_countries = %w(US CA GB AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE AR BO BR BZ CL CO CR DO EC GF GP GT HN HT MF MQ MX NI PA PE PR PY SV UY VE)
self.default_currency = 'USD'
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :diners_club, :maestro]
self.homepage_url = 'https://home.bluesnap.com/'
@@ -57,18 +57,31 @@
'line1: N, zip: N, name: U' => 'N',
'line1: N, zip: N, name: M' => 'K',
'line1: N, zip: N, name: N' => 'N',
}
+ BANK_ACCOUNT_TYPE_MAPPING = {
+ 'personal_checking' => 'CONSUMER_CHECKING',
+ 'personal_savings' => 'CONSUMER_SAVINGS',
+ 'business_checking' => 'CORPORATE_CHECKING',
+ 'business_savings' => 'CORPORATE_SAVINGS'
+ }
+
def initialize(options={})
requires!(options, :api_username, :api_password)
super
end
def purchase(money, payment_method, options={})
- commit(:purchase) do |doc|
- add_auth_purchase(doc, money, payment_method, options)
+ payment_method_details = PaymentMethodDetails.new(payment_method)
+
+ commit(:purchase, :post, payment_method_details) do |doc|
+ if payment_method_details.alt_transaction?
+ add_alt_transaction_purchase(doc, money, payment_method_details, options)
+ else
+ add_auth_purchase(doc, money, payment_method, options)
+ end
end
end
def authorize(money, payment_method, options={})
commit(:authorize) do |doc|
@@ -100,22 +113,37 @@
def verify(payment_method, options={})
authorize(0, payment_method, options)
end
- def store(credit_card, options = {})
- commit(:store) do |doc|
- add_personal_info(doc, credit_card, options)
+ def store(payment_method, options = {})
+ payment_method_details = PaymentMethodDetails.new(payment_method)
+
+ commit(:store, :post, payment_method_details) do |doc|
+ add_personal_info(doc, payment_method, options)
+ add_echeck_company(doc, payment_method) if payment_method_details.check?
doc.send('payment-sources') do
- doc.send('credit-card-info') do
- add_credit_card(doc, credit_card)
- end
+ payment_method_details.check? ? store_echeck(doc, payment_method) : store_credit_card(doc, payment_method)
end
add_order(doc, options)
end
end
+ def store_credit_card(doc, payment_method)
+ doc.send('credit-card-info') do
+ add_credit_card(doc, payment_method)
+ end
+ end
+
+ def store_echeck(doc, payment_method)
+ doc.send('ecp-info') do
+ doc.send('ecp') do
+ add_echeck(doc, payment_method)
+ end
+ end
+ end
+
def verify_credentials
begin
ssl_get(url.to_s, headers)
rescue ResponseError => e
return false if e.response.code.to_i == 401
@@ -130,23 +158,23 @@
def scrub(transcript)
transcript.
gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]').
gsub(%r((<card-number>).+(</card-number>)), '\1[FILTERED]\2').
- gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2')
+ gsub(%r((<security-code>).+(</security-code>)), '\1[FILTERED]\2').
+ gsub(%r((<(?:public-)?account-number>).+(</(?:public-)?account-number>)), '\1[FILTERED]\2').
+ gsub(%r((<(?:public-)?routing-number>).+(</(?:public-)?routing-number>)), '\1[FILTERED]\2')
end
private
def add_auth_purchase(doc, money, payment_method, options)
doc.send('recurring-transaction', options[:recurring] ? 'RECURRING' : 'ECOMMERCE')
add_order(doc, options)
- doc.send('storeCard', options[:store_card] || false)
+ doc.send('store-card', options[:store_card] || false)
add_amount(doc, money, options)
- doc.send('transaction-fraud-info') do
- doc.send('shopper-ip-address', options[:ip]) if options[:ip]
- end
+ add_fraud_info(doc, options)
if payment_method.is_a?(String)
doc.send('vaulted-shopper-id', payment_method)
else
doc.send('card-holder-info') do
@@ -159,13 +187,14 @@
def add_amount(doc, money, options)
doc.amount(amount(money))
doc.currency(options[:currency] || currency(money))
end
- def add_personal_info(doc, credit_card, options)
- doc.send('first-name', credit_card.first_name)
- doc.send('last-name', credit_card.last_name)
+ def add_personal_info(doc, payment_method, options)
+ doc.send('first-name', payment_method.first_name)
+ doc.send('last-name', payment_method.last_name)
+ doc.send('personal-identification-number', options[:personal_identification_number]) if options[:personal_identification_number]
doc.email(options[:email]) if options[:email]
add_address(doc, options)
end
def add_credit_card(doc, card)
@@ -189,10 +218,11 @@
def add_order(doc, options)
doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
add_description(doc, options[:description]) if options[:description]
+ add_level_3_data(doc, options)
end
def add_address(doc, options)
address = options[:billing_address]
return unless address
@@ -202,14 +232,96 @@
doc.address(address[:address]) if address[:address]
doc.city(address[:city]) if address[:city]
doc.zip(address[:zip]) if address[:zip]
end
+ def add_level_3_data(doc, options)
+ return unless options[:customer_reference_number]
+ doc.send('level-3-data') do
+ send_when_present(doc, :customer_reference_number, options)
+ send_when_present(doc, :sales_tax_amount, options)
+ send_when_present(doc, :freight_amount, options)
+ send_when_present(doc, :duty_amount, options)
+ send_when_present(doc, :destination_zip_code, options)
+ send_when_present(doc, :destination_country_code, options)
+ send_when_present(doc, :ship_from_zip_code, options)
+ send_when_present(doc, :discount_amount, options)
+ send_when_present(doc, :tax_amount, options)
+ send_when_present(doc, :tax_rate, options)
+ add_level_3_data_items(doc, options[:level_3_data_items]) if options[:level_3_data_items]
+ end
+ end
+
+ def send_when_present(doc, options_key, options, xml_element_name = nil)
+ return unless options[options_key]
+ xml_element_name ||= options_key.to_s
+
+ doc.send(xml_element_name.dasherize, options[options_key])
+ end
+
+ def add_level_3_data_items(doc, items)
+ items.each do |item|
+ doc.send('level-3-data-item') do
+ item.each do |key, value|
+ key = key.to_s.dasherize
+ doc.send(key, value)
+ end
+ end
+ end
+ end
+
def add_authorization(doc, authorization)
doc.send('transaction-id', authorization)
end
+ def add_fraud_info(doc, options)
+ doc.send('transaction-fraud-info') do
+ doc.send('shopper-ip-address', options[:ip]) if options[:ip]
+ end
+ end
+
+ def add_alt_transaction_purchase(doc, money, payment_method_details, options)
+ doc.send('merchant-transaction-id', truncate(options[:order_id], 50)) if options[:order_id]
+ doc.send('soft-descriptor', options[:soft_descriptor]) if options[:soft_descriptor]
+ add_amount(doc, money, options)
+
+ vaulted_shopper_id = payment_method_details.vaulted_shopper_id
+ doc.send('vaulted-shopper-id', vaulted_shopper_id) if vaulted_shopper_id
+
+ if payment_method_details.check?
+ add_echeck_transaction(doc, payment_method_details.payment_method, options, vaulted_shopper_id.present?)
+ end
+
+ add_fraud_info(doc, options)
+ add_description(doc, options)
+ end
+
+ def add_echeck_transaction(doc, check, options, vaulted_shopper)
+ unless vaulted_shopper
+ doc.send('payer-info') do
+ add_personal_info(doc, check, options)
+ add_echeck_company(doc, check)
+ end
+ end
+
+ doc.send('ecp-transaction') do
+ add_echeck(doc, check) unless vaulted_shopper
+ end
+
+ doc.send('authorized-by-shopper', options[:authorized_by_shopper])
+ end
+
+ def add_echeck_company(doc, check)
+ doc.send('company-name', truncate(check.name, 50)) if check.account_holder_type = 'business'
+ end
+
+ def add_echeck(doc, check)
+ doc.send('account-number', check.account_number)
+ doc.send('routing-number', check.routing_number)
+ doc.send('account-type', BANK_ACCOUNT_TYPE_MAPPING["#{check.account_holder_type}_#{check.account_type}"])
+ end
+
def parse(response)
return bad_authentication_response if response.code.to_i == 401
return forbidden_response(response.body) if response.code.to_i == 403
parsed = {}
@@ -234,37 +346,37 @@
else
parsed[node.name.downcase] = node.text
end
end
- def api_request(action, request, verb)
- ssl_request(verb, url(action), request, headers)
+ def api_request(action, request, verb, payment_method_details)
+ ssl_request(verb, url(action, payment_method_details), request, headers)
rescue ResponseError => e
e.response
end
- def commit(action, verb = :post)
- request = build_xml_request(action) { |doc| yield(doc) }
- response = api_request(action, request, verb)
+ def commit(action, verb = :post, payment_method_details = PaymentMethodDetails.new())
+ request = build_xml_request(action, payment_method_details) { |doc| yield(doc) }
+ response = api_request(action, request, verb, payment_method_details)
parsed = parse(response)
succeeded = success_from(action, response)
Response.new(
succeeded,
message_from(succeeded, parsed),
parsed,
- authorization: authorization_from(action, parsed),
+ authorization: authorization_from(action, parsed, payment_method_details),
avs_result: avs_result(parsed),
cvv_result: cvv_result(parsed),
error_code: error_code_from(parsed),
test: test?
)
end
- def url(action = nil)
+ def url(action = nil, payment_method_details = PaymentMethodDetails.new())
base = test? ? test_url : live_url
- resource = action == :store ? 'vaulted-shoppers' : 'transactions'
+ resource = action == :store ? 'vaulted-shoppers' : payment_method_details.resource_url
"#{base}/#{resource}"
end
def cvv_result(parsed)
CVVResult.new(CVC_CODE_TRANSLATOR[parsed['cvv-response-code']])
@@ -285,17 +397,19 @@
def message_from(succeeded, parsed_response)
return 'Success' if succeeded
parsed_response['description']
end
- def authorization_from(action, parsed_response)
- action == :store ? vaulted_shopper_id(parsed_response) : parsed_response['transaction-id']
+ def authorization_from(action, parsed_response, payment_method_details)
+ action == :store ? vaulted_shopper_id(parsed_response, payment_method_details) : parsed_response['transaction-id']
end
- def vaulted_shopper_id(parsed_response)
+ def vaulted_shopper_id(parsed_response, payment_method_details)
return nil unless parsed_response['content-location-header']
- parsed_response['content-location-header'].split('/').last
+ vaulted_shopper_id = parsed_response['content-location-header'].split('/').last
+ vaulted_shopper_id += "|#{payment_method_details.payment_method_type}" if payment_method_details.alt_transaction?
+ vaulted_shopper_id
end
def error_code_from(parsed_response)
parsed_response['code']
end
@@ -304,25 +418,25 @@
{
xmlns: 'http://ws.plimus.com'
}
end
- def root_element(action)
- action == :store ? 'vaulted-shopper' : 'card-transaction'
+ def root_element(action, payment_method_details)
+ action == :store ? 'vaulted-shopper' : payment_method_details.root_element
end
def headers
{
'Content-Type' => 'application/xml',
'Authorization' => ('Basic ' + Base64.strict_encode64("#{@options[:api_username]}:#{@options[:api_password]}").strip),
}
end
- def build_xml_request(action)
+ def build_xml_request(action, payment_method_details)
builder = Nokogiri::XML::Builder.new
- builder.__send__(root_element(action), root_attributes) do |doc|
- doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action]
+ builder.__send__(root_element(action, payment_method_details), root_attributes) do |doc|
+ doc.send('card-transaction-type', TRANSACTIONS[action]) if TRANSACTIONS[action] && !payment_method_details.alt_transaction?
yield(doc)
end
builder.doc.root.to_xml
end
@@ -339,9 +453,50 @@
{ 'description' => 'Unable to authenticate. Please check your credentials.' }
end
def forbidden_response(body)
{ 'description' => body }
+ end
+ end
+
+ class PaymentMethodDetails
+ attr_reader :payment_method, :vaulted_shopper_id, :payment_method_type
+
+ def initialize(payment_method = nil)
+ @payment_method = payment_method
+ @payment_method_type = nil
+ parse(payment_method)
+ end
+
+ def check?
+ @payment_method.is_a?(Check) || @payment_method_type == 'check'
+ end
+
+ def alt_transaction?
+ check?
+ end
+
+ def root_element
+ alt_transaction? ? 'alt-transaction' : 'card-transaction'
+ end
+
+ def resource_url
+ alt_transaction? ? 'alt-transactions' : 'transactions'
+ end
+
+ private
+
+ def parse(payment_method)
+ return unless payment_method
+
+ if payment_method.is_a?(String)
+ @vaulted_shopper_id, payment_method_type = payment_method.split('|')
+ @payment_method_type = payment_method_type if payment_method_type.present?
+ elsif payment_method.is_a?(Check)
+ @payment_method_type = payment_method.type
+ else
+ @payment_method_type = 'credit_card'
+ end
end
end
end
end