require 'rubygems' require 'nokogiri' module ActiveMerchant #:nodoc: module Billing #:nodoc: class CheckoutGateway < Gateway self.default_currency = 'USD' self.money_format = :cents self.supported_countries = ['AD', 'AT', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GI', 'GL', 'GR', 'HR', 'HU', 'IE', 'IS', 'IL', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'SE', 'SI', 'SM', 'SK', 'SJ', 'TR', 'VA'] self.supported_cardtypes = [:visa, :master, :american_express, :diners_club] self.homepage_url = 'https://www.checkout.com/' self.display_name = 'Checkout.com' self.live_url = 'https://api.checkout.com/Process/gateway.aspx' ACTIONS = { 'purchase' => '1', 'authorize' => '4', 'capture' => '5', 'refund' => '2', 'void_purchase' => '3', 'void_authorize' => '9', 'void_capture' => '7' } def initialize(options = {}) requires!(options, :merchant_id, :password) super end def purchase(amount, payment_method, options) commit('purchase', amount, options) do |xml| add_credentials(xml, options) add_invoice(xml, amount, options) add_track_id(xml, options[:order_id] || generate_unique_id) add_payment_method(xml, payment_method) add_billing_info(xml, options) add_shipping_info(xml, options) add_user_defined_fields(xml, options) add_other_fields(xml, options) end end def authorize(amount, payment_method, options) commit('authorize', amount, options) do |xml| add_credentials(xml, options) add_invoice(xml, amount, options) add_track_id(xml, options[:order_id] || generate_unique_id) add_payment_method(xml, payment_method) add_billing_info(xml, options) add_shipping_info(xml, options) add_user_defined_fields(xml, options) add_other_fields(xml, options) end end def capture(amount, authorization, options = {}) commit('capture', amount, options) do |xml| add_credentials(xml, options) add_reference(xml, authorization) add_invoice(xml, amount, options) add_user_defined_fields(xml, options) add_other_fields(xml, options) end end def void(authorization, options = {}) _, _, orig_action, amount, currency = split_authorization(authorization) commit("void_#{orig_action}") do |xml| add_credentials(xml, options) add_invoice(xml, amount.to_i, options.merge(currency: currency)) add_reference(xml, authorization) end end def refund(amount, authorization, options = {}) commit('refund') do |xml| add_credentials(xml, options) add_invoice(xml, amount.to_i, options) add_reference(xml, authorization) end end def verify(credit_card, options={}) MultiResponse.run(:use_first_response) do |r| r.process { authorize(100, credit_card, options) } r.process(:ignore_result) { void(r.authorization, options) } end end private def add_credentials(xml, options) xml.merchantid_ @options[:merchant_id] xml.password_ @options[:password] end def add_invoice(xml, amount, options) xml.bill_amount_ amount(amount) xml.bill_currencycode_ options[:currency] || currency(amount) end def add_payment_method(xml, payment_method) xml.bill_cardholder_ payment_method.name xml.bill_cc_ payment_method.number xml.bill_expmonth_ format(payment_method.month, :two_digits) xml.bill_expyear_ format(payment_method.year, :four_digits) if payment_method.verification_value? xml.bill_cvv2_ payment_method.verification_value end end def add_billing_info(xml, options) if options[:billing_address] xml.bill_address_ options[:billing_address][:address1] xml.bill_city_ options[:billing_address][:city] xml.bill_state_ options[:billing_address][:state] xml.bill_postal_ options[:billing_address][:zip] xml.bill_country_ options[:billing_address][:country] xml.bill_phone_ options[:billing_address][:phone] end end def add_shipping_info(xml, options) if options[:shipping_address] xml.ship_address_ options[:shipping_address][:address1] xml.ship_address2_ options[:shipping_address][:address2] xml.ship_city_ options[:shipping_address][:city] xml.ship_state_ options[:shipping_address][:state] xml.ship_postal_ options[:shipping_address][:zip] xml.ship_country_ options[:shipping_address][:country] xml.ship_phone_ options[:shipping_address][:phone] end end def add_user_defined_fields(xml, options) xml.udf1_ options[:udf1] xml.udf2_ options[:udf2] xml.udf3_ options[:udf3] xml.udf4_ options[:udf4] xml.udf5_ options[:udf5] end def add_other_fields(xml, options) xml.bill_email_ options[:email] xml.bill_customerip_ options[:ip] xml.merchantcustomerid_ options[:customer] xml.descriptor_name options[:descriptor_name] xml.descriptor_city options[:descriptor_city] end def add_reference(xml, authorization) transid, trackid, _, _, _ = split_authorization(authorization) xml.transid transid add_track_id(xml, trackid) end def add_track_id(xml, trackid) xml.trackid(trackid) if trackid end def commit(action, amount=nil, options={}, &builder) response = parse_xml(ssl_post(live_url, build_xml(action, &builder))) Response.new( (response[:responsecode] == '0'), (response[:result] || response[:error_text] || 'Unknown Response'), response, authorization: authorization_from(response, action, amount, options), test: test? ) end def build_xml(action) Nokogiri::XML::Builder.new do |xml| xml.request do xml.action_ ACTIONS[action] yield xml end end.to_xml end def parse_xml(xml) response = {} Nokogiri::XML(CGI.unescapeHTML(xml)).xpath('//response').children.each do |node| if node.text? next elsif node.elements.size == 0 response[node.name.downcase.to_sym] = node.text else node.elements.each do |childnode| name = "#{node.name.downcase}_#{childnode.name.downcase}" response[name.to_sym] = childnode.text end end end response end def authorization_from(response, action, amount, options) currency = options[:currency] || currency(amount) [response[:tranid], response[:trackid], action, amount, currency].join('|') end def split_authorization(authorization) transid, trackid, action, amount, currency = authorization.split('|') [transid, trackid, action, amount, currency] end end end end