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