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", "UK" ] 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