lib/active_merchant/billing/gateways/wirecard.rb in activemerchant-1.43.3 vs lib/active_merchant/billing/gateways/wirecard.rb in activemerchant-1.44.0

- old
+ new

@@ -42,22 +42,38 @@ def initialize(options = {}) requires!(options, :login, :password, :signature) super end - def authorize(money, creditcard, options = {}) - options[:credit_card] = creditcard + # Authorization - the second parameter may be a CreditCard or + # a String which represents a GuWID reference to an earlier + # transaction. If a GuWID is given, rather than a CreditCard, + # then then the :recurring option will be forced to "Repeated" + def authorize(money, payment_method, options = {}) + if payment_method.respond_to?(:number) + options[:credit_card] = payment_method + else + options[:preauthorization] = payment_method + end commit(:preauthorization, money, options) end def capture(money, authorization, options = {}) options[:preauthorization] = authorization commit(:capture, money, options) end - def purchase(money, creditcard, options = {}) - options[:credit_card] = creditcard + # Purchase - the second parameter may be a CreditCard or + # a String which represents a GuWID reference to an earlier + # transaction. If a GuWID is given, rather than a CreditCard, + # then then the :recurring option will be forced to "Repeated" + def purchase(money, payment_method, options = {}) + if payment_method.respond_to?(:number) + options[:credit_card] = payment_method + else + options[:preauthorization] = payment_method + end commit(:purchase, money, options) end def void(identification, options = {}) options[:preauthorization] = identification @@ -67,10 +83,50 @@ def refund(money, identification, options = {}) options[:preauthorization] = identification commit(:bookback, money, options) end + # Store card - Wirecard supports the notion of "Recurring + # Transactions" by allowing the merchant to provide a reference + # to an earlier transaction (the GuWID) rather than a credit + # card. A reusable reference (GuWID) can be obtained by sending + # a purchase or authorization transaction with the element + # "RECURRING_TRANSACTION/Type" set to "Initial". Subsequent + # transactions can then use the GuWID in place of a credit + # card by setting "RECURRING_TRANSACTION/Type" to "Repeated". + # + # This implementation of card store utilizes a Wirecard + # "Authorization Check" (a Preauthorization that is automatically + # reversed). It defaults to a check amount of "100" (i.e. + # $1.00) but this can be overriden (see below). + # + # IMPORTANT: In order to reuse the stored reference, the + # +authorization+ from the response should be saved by + # your application code. + # + # ==== Options specific to +store+ + # + # * <tt>:amount</tt> -- The amount, in cents, that should be + # "validated" by the Authorization Check. This amount will + # be reserved and then reversed. Default is 100. + # + # Note: This is not the only way to achieve a card store + # operation at Wirecard. Any +purchase+ or +authorize+ + # can be sent with +options[:recurring] = 'Initial'+ to make + # the returned authorization/GuWID usable in later transactions + # with +options[:recurring] = 'Repeated'+. + def store(creditcard, options = {}) + options[:credit_card] = creditcard + options[:recurring] = 'Initial' + money = options.delete(:amount) || 100 + # Amex does not support authorization_check + if creditcard.brand == 'american_express' + commit(:preauthorization, money, options) + else + commit(:authorization_check, money, options) + end + end private def clean_description(description) description.to_s.slice(0,32).encode("US-ASCII", invalid: :replace, undef: :replace, replace: '?') end @@ -88,10 +144,17 @@ options[:shipping_address] = options[:shipping_address] || {} # Include Email in address-hash from options-hash options[:billing_address][:email] = options[:email] if options[:email] end + # If a GuWID (string-based reference) is passed rather than a + # credit card, then the :recurring type needs to be forced to + # "Repeated" + def setup_recurring_flag(options) + options[:recurring] = 'Repeated' if options[:preauthorization].present? + end + # Contact WireCard, make the XML request, and parse the # reply into a Response object def commit(action, money, options) request = build_request(action, money, options) @@ -105,12 +168,12 @@ authorization = response[:GuWID] Response.new(success, message, response, :test => test?, :authorization => authorization, - :avs_result => { :code => response[:avsCode] }, - :cvv_result => response[:cvCode] + :avs_result => { :code => avs_code(response, options) }, + :cvv_result => response[:CVCResponseCode] ) rescue ResponseError => e if e.response.code == "401" return Response.new(false, "Invalid Login") else @@ -144,14 +207,22 @@ xml.tag! "FNC_CC_#{options[:action].to_s.upcase}" do xml.tag! 'FunctionID', clean_description(options[:description]) xml.tag! 'CC_TRANSACTION' do xml.tag! 'TransactionID', options[:order_id] + xml.tag! 'CommerceType', options[:commerce_type] if options[:commerce_type] case options[:action] - when :preauthorization, :purchase + when :preauthorization, :purchase, :authorization_check + setup_recurring_flag(options) add_invoice(xml, money, options) - add_creditcard(xml, options[:credit_card]) + + if options[:credit_card] + add_creditcard(xml, options[:credit_card]) + else + xml.tag! 'GuWID', options[:preauthorization] + end + add_address(xml, options[:billing_address]) when :capture, :bookback xml.tag! 'GuWID', options[:preauthorization] add_amount(xml, money) when :reversal @@ -225,19 +296,25 @@ xml = REXML::Document.new(xml) if root = REXML::XPath.first(xml, "#{basepath}/W_JOB") parse_response(response, root) elsif root = REXML::XPath.first(xml, "//ERROR") - parse_error(response, root) + parse_error_only_response(response, root) else response[:Message] = "No valid XML response message received. \ Propably wrong credentials supplied with HTTP header." end response end + def parse_error_only_response(response, root) + error_code = REXML::XPath.first(root, "Number") + response[:ErrorCode] = error_code.text if error_code + response[:Message] = parse_error(root) + end + # Parse the <ProcessingStatus> Element which contains all important information def parse_response(response, root) status = nil root.elements.to_a.each do |node| @@ -251,11 +328,18 @@ if info = status.elements['Info'] message << info.text end status.elements.to_a.each do |node| - response[node.name.to_sym] = (node.text || '').strip + if (node.elements.size == 0) + response[node.name.to_sym] = (node.text || '').strip + else + node.elements.each do |childnode| + name = "#{node.name}_#{childnode.name}" + response[name.to_sym] = (childnode.text || '').strip + end + end end error_code = REXML::XPath.first(status, "ERROR/Number") response['ErrorCode'] = error_code.text if error_code end @@ -290,18 +374,40 @@ errors << error end # Convert all messages to a single string string = '' errors.each do |error| - string << error[:Message] + string << error[:Message] if error[:Message] error[:Advice].each_with_index do |advice, index| string << ' (' if index == 0 string << "#{index+1}. #{advice}" string << ' and ' if index < error[:Advice].size - 1 string << ')' if index == error[:Advice].size - 1 end end string + end + + # Amex have different AVS response codes + AMEX_TRANSLATED_AVS_CODES = { + "A" => "B", # CSC and Address Matched + "F" => "D", # All Data Matched + "N" => "I", # CSC Match + "U" => "U", # Data Not Checked + "Y" => "D", # All Data Matched + "Z" => "P", # CSC and Postcode Matched + "F" => "D" # Street address and zip code match + } + + # Amex have different AVS response codes to visa etc + def avs_code(response, options) + if response.has_key?(:AVS_ProviderResultCode) + if options[:credit_card].present? && ActiveMerchant::Billing::CreditCard.brand?(options[:credit_card].number) == "american_express" + AMEX_TRANSLATED_AVS_CODES[response[:AVS_ProviderResultCode]] + else + response[:AVS_ProviderResultCode] + end + end end # Encode login and password in Base64 to supply as HTTP header # (for http basic authentication) def encoded_credentials