module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class CertoDirectGateway < Gateway
class_attribute :gateway_url
self.gateway_url = "https://secure.certodirect.com/gateway/process/v2"
self.supported_countries = [
"BE", "BG", "CZ", "DK", "DE", "EE", "IE", "EL", "ES", "FR",
"IT", "CY", "LV", "LT", "LU", "HU", "MT", "NL", "AT", "PL",
"PT", "RO", "SI", "SK", "FI", "SE", "GB"
]
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'http://www.certodirect.com/'
self.display_name = 'CertoDirect'
# Creates a new CertoDirectGateway
#
# The gateway requires that a valid login and password be passed
# in the +options+ hash.
#
# ==== Options
#
# * :login -- The CertoDirect Shop ID (REQUIRED)
# * :password -- The CertoDirect Shop Password. (REQUIRED)
# * :test -- +true+ or +false+. If true, perform transactions against the test server.
# Otherwise, perform transactions against the production server.
def initialize(options = {})
requires!(options, :login, :password)
@options = options
super
end
# Perform a purchase, which is essentially an authorization and capture in a single operation.
#
# ==== Parameters
#
# * money -- The amount to be purchased as an Integer value in cents.
# * credit_card -- The CreditCard details for the transaction.
# * options -- A hash of optional parameters.
def purchase(money, credit_card, options = {})
requires!(options, :email, :currency, :ip, :description)
commit(build_sale_request(money, credit_card, options))
end
# Refund a transaction.
#
# This transaction indicates to the gateway that
# money should flow from the merchant to the customer.
#
# ==== Parameters
#
# * money -- The amount to be credited to the customer as an Integer value in cents.
# * identification -- The ID of the original order against which the refund is being issued.
# * options -- A hash of parameters.
def refund(money, identification, options = {})
requires!(options, :reason)
commit(build_refund_request(money, identification, options))
end
# Performs an authorization, which reserves the funds on the customer's credit card, but does not
# charge the card.
#
# ==== Parameters
#
# * money -- The amount to be authorized as an Integer value in cents.
# * credit_card -- The CreditCard details for the transaction.
# * options -- A hash of optional parameters.
def authorize(money, credit_card, options = {})
requires!(options, :email, :currency, :ip, :description)
commit(build_authorize_request(money, credit_card, options))
end
# Captures the funds from an authorized transaction.
#
# ==== Parameters
#
# * money -- The amount to be captured as an Integer value in cents.
# * identification -- The authorization returned from the previous authorize request.
def capture(money, identification, options = {})
commit(build_capture_request(money, identification))
end
# Void a previous transaction
#
# ==== Parameters
#
# * money -- The amount to be captured as an Integer value in cents.
# * identification - The authorization returned from the previous authorize request.
def void(money, identification, options = {})
commit(build_void_request(money, identification))
end
# Create a recurring payment.
#
# ==== Parameters
#
# * options -- A hash of parameters.
#
# ==== Options
#
def recurring(identification, options={})
commit(build_recurring_request(identification, options))
end
private
def commit(request_xml)
begin
response = Hash.from_xml(ssl_post(gateway_url, request_xml, headers))
Response.new(success?(response),
message(response),
response,
:test => test?,
:authorization => authorization(response))
rescue ResponseError => e
raise e unless e.response.code == '403'
response = Hash.from_xml(e.response.body)['response']
Response.new(false, message(response), {}, :test => test?)
end
end
def build_sale_request(money, credit_card, options)
build_request_xml('Sale') do |xml|
add_order(xml, money, credit_card, options)
end
end
def build_authorize_request(money, credit_card, options)
build_request_xml('Authorize') do |xml|
add_order(xml, money, credit_card, options)
end
end
def build_refund_request(money, identification, options)
build_request_xml('Refund') do |xml|
add_reference_info(xml, money, identification, options)
xml.tag! 'reason', options[:reason]
end
end
def build_capture_request(money, identification)
build_request_xml('Capture') do |xml|
add_reference_info(xml, money, identification, options)
end
end
def build_void_request(money, identification)
build_request_xml('Void') do |xml|
add_reference_info(xml, money, identification, options)
end
end
def build_recurring_request(identification, options)
build_request_xml('Sale') do |xml|
xml.tag! 'order' do |xml|
xml.tag!('test', 'true') if test?
xml.tag! 'initial_order_id', identification, :type => 'integer'
add_order_details(xml, options[:amount], options) if has_any_order_details_key?(options)
add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address]
add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address]
end
end
end
def build_request_xml(type, &block)
xml = Builder::XmlMarkup.new(:indent => 2)
xml.tag! 'transaction' do
xml.tag! 'type', type
yield(xml)
end
xml.target!
end
def add_order(xml, money, credit_card, options)
xml.tag! 'order' do
xml.tag!('test', 'true') if test?
xml.tag!('return_url', options[:return_url]) if options[:return_url]
xml.tag!('cancel_url', options[:cancel_url]) if options[:cancel_url]
xml.tag! 'payment_method_type', 'CreditCard'
xml.tag! 'payment_method' do
xml.tag! 'number', credit_card.number
xml.tag! 'exp_month', "%02i" % credit_card.month
xml.tag! 'exp_year', credit_card.year
xml.tag! 'holder', credit_card.name
xml.tag! 'verification_value', credit_card.verification_value
end
add_order_details(xml, money, options)
add_address(xml, 'billing_address', options[:billing_address]) if options[:billing_address]
add_address(xml, 'shipping_address', options[:shipping_address]) if options[:shipping_address]
end
end
def add_order_details(xml, money, options)
xml.tag! 'details' do
xml.tag!('amount', localized_amount(money, options[:currency]), :type => 'decimal') if money
xml.tag!('currency', options[:currency]) if options[:currency]
xml.tag!('email', options[:email]) if options[:email]
xml.tag!('ip', options[:ip]) if options[:ip]
xml.tag!('shipping', options[:shipping], :type => 'decimal') if options[:shipping]
xml.tag!('description', options[:description]) if options[:description]
end
end
def add_reference_info(xml, money, identification, options)
xml.tag! 'order_id', identification, :type => 'integer'
xml.tag! 'amount', localized_amount(money, options[:currency]), :type => 'decimal'
end
def add_address(xml, address_type, address)
xml.tag! address_type do
xml.tag! 'address', address[:address1]
xml.tag! 'city', address[:city]
xml.tag! 'country', address[:country]
xml.tag! 'first_name', address[:first_name]
xml.tag! 'last_name', address[:last_name]
xml.tag! 'state', address[:state]
xml.tag! 'phone', address[:phone]
xml.tag! 'zip', address[:zip]
end
end
def has_any_order_details_key?(options)
[ :currency, :amount, :email, :ip, :shipping, :description ].any? do |key|
options.has_key?(key)
end
end
def success?(response)
%w(completed forwarding).include?(state(response)) and
status(response) == 'success'
end
def error?(response)
response['errors']
end
def state(response)
response["transaction"].try(:[], "state")
end
def status(response)
response['transaction'].try(:[], 'response').try(:[], 'status')
end
def authorization(response)
error?(response) ? nil : response["transaction"]["order"]['id'].to_s
end
def message(response)
return response['errors'].join('; ') if error?(response)
if state(response) == 'completed'
response["transaction"]["response"]["message"]
else
response['transaction']['message']
end
end
def headers
{ 'authorization' => basic_auth,
'Accept' => 'application/xml',
'Content-Type' => 'application/xml' }
end
def basic_auth
'Basic ' + ["#{@options[:login]}:#{@options[:password]}"].pack('m').delete("\r\n")
end
end
end
end