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