module ActiveMerchant #:nodoc: module Billing #:nodoc: class CardprocessGateway < Gateway self.test_url = 'https://test.vr-pay-ecommerce.de/v1/payments' self.live_url = 'https://vr-pay-ecommerce.de/v1/payments' self.supported_countries = %w[ BE BG CZ DK DE EE IE ES FR HR IT CY LV LT LU MT HU NL AT PL PT RO SI SK FI SE GB IS LI NO CH ME MK AL RS TR BA ] self.default_currency = 'EUR' self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :jcb] self.homepage_url = 'https://vr-pay-ecommerce.docs.oppwa.com/' self.display_name = 'CardProcess VR-Pay' self.money_format = :dollars STANDARD_ERROR_CODE_MAPPING = {} # Creates a new CardProcess Gateway # # The gateway requires a valid login, password, and entity ID # to be passed in the +options+ hash. # # === Options # # * :user_id -- The CardProcess user ID # * :password -- The CardProcess password # * :entity_id -- The CardProcess channel or entity ID for any transactions def initialize(options={}) requires!(options, :user_id, :password, :entity_id) super # This variable exists purely to allow remote tests to force error codes; # the lack of a setter despite its usage is intentional. @test_options = {} end def purchase(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) commit('DB', post) end def authorize(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) commit('PA', post) end def capture(money, authorization, options = {}) post = { id: authorization } add_invoice(post, money, options) commit('CP', post) end def refund(money, authorization, options = {}) post = { id: authorization } add_invoice(post, money, options) commit('RF', post) end def credit(money, payment, options = {}) post = {} add_invoice(post, money, options) add_payment(post, payment) add_address(post, payment, options) add_customer_data(post, options) commit('CD', post) end def void(authorization, _options = {}) post = { id: authorization } commit('RV', post) end def verify(credit_card, options = {}) MultiResponse.run do |r| r.process { authorize(100, credit_card, options) } r.process { void(r.authorization, options) } end end def supports_scrubbing? true end def scrub(transcript) transcript .gsub(%r{(authentication\.[^=]+=)[^&]+}, '\1[FILTERED]') .gsub(%r{(card\.number=)\d+}, '\1[FILTERED]') .gsub(%r{(cvv=)\d{3,4}}, '\1[FILTERED]\2') end private def add_customer_data(post, options) post['customer.ip'] = options[:ip] if options[:ip] end def add_address(post, _card, options) if (address = options[:billing_address] || options[:address]) post[:billing] = hashify_address(address) end if (shipping = options[:shipping_address]) post[:shipping] = hashify_address(shipping) end end def add_invoice(post, money, options) return if money.nil? post[:amount] = amount(money) post[:currency] = (options[:currency] || currency(money)) post[:merchantInvoiceId] = options[:merchant_invoice_id] if options[:merchant_invoice_id] post[:merchantTransactionId] = options[:merchant_transaction_id] if options[:merchant_transaction_id] post[:transactionCategory] = options[:transaction_category] if options[:transaction_category] end def add_payment(post, payment) return if payment.is_a?(String) post[:paymentBrand] = payment.brand.upcase if payment.brand post[:card] ||= {} post[:card][:number] = payment.number post[:card][:holder] = payment.name post[:card][:expiryMonth] = sprintf('%02d', payment.month) post[:card][:expiryYear] = sprintf('%02d', payment.year) post[:card][:cvv] = payment.verification_value end def parse(body) JSON.parse(body) end def commit(action, parameters) url = (test? ? test_url : live_url) if (id = parameters.delete(:id)) url += "/#{id}" end begin raw_response = ssl_post(url, post_data(action, parameters.merge(@test_options))) rescue ResponseError => e raw_response = e.response.body end response = parse(raw_response) Response.new( success_from(response), message_from(response), response, authorization: authorization_from(response), avs_result: AVSResult.new(code: response['result']['avsResponse']), cvv_result: CVVResult.new(response['result']['cvvResponse']), test: test?, error_code: error_code_from(response) ) end def success_from(response) !(response['result']['code'] =~ /^(000\.000\.|000\.100\.1|000\.[36])/).nil? end def message_from(response) response['result']['description'] end def authorization_from(response) response['id'] end def post_data(action, parameters = {}) post = parameters.clone post[:authentication] ||= {} post[:authentication][:userId] = @options[:user_id] post[:authentication][:password] = @options[:password] post[:authentication][:entityId] = @options[:entity_id] post[:paymentType] = action dot_flatten_hash(post).map {|key, value| "#{key}=#{CGI.escape(value.to_s)}"}.join("&") end def error_code_from(response) unless success_from(response) case response['result']['code'] when '100.100.101' STANDARD_ERROR_CODE[:incorrect_number] when '100.100.303' STANDARD_ERROR_CODE[:expired_card] when /100\.100\.(201|301|305)/ STANDARD_ERROR_CODE[:invalid_expiry_date] when /100.100.60[01]/ STANDARD_ERROR_CODE[:invalid_cvc] when '800.100.151' STANDARD_ERROR_CODE[:invalid_number] when '800.100.153' STANDARD_ERROR_CODE[:incorrect_cvc] when /800.800.(102|302)/ STANDARD_ERROR_CODE[:incorrect_address] when '800.800.202' STANDARD_ERROR_CODE[:invalid_zip] when '800.100.166' STANDARD_ERROR_CODE[:incorrect_pin] when '800.100.171' STANDARD_ERROR_CODE[:pickup_card] when /^(200|700)\./ STANDARD_ERROR_CODE[:config_error] when /^(800\.[17]00|800\.800\.[123])/ STANDARD_ERROR_CODE[:card_declined] when /^(900\.[1234]00)/ STANDARD_ERROR_CODE[:processing_error] else STANDARD_ERROR_CODE[:processing_error] end end end def hashify_address(address) hash = {} hash[:street1] = address[:address1] if address[:address1] hash[:street2] = address[:address2] if address[:address2] hash[:city] = address[:city] if address[:city] hash[:state] = address[:state] if address[:state] hash[:postcode] = address[:zip] if address[:zip] hash[:country] = address[:country] if address[:country] hash end def dot_flatten_hash(hash, prefix = '') h = {} hash.each_pair do |k, v| if v.is_a?(Hash) h.merge!(dot_flatten_hash(v, prefix + k.to_s + '.')) else h[prefix + k.to_s] = v end end h end end end end