require 'net/https'
require 'uri'
require File.dirname(__FILE__) + '/certificate_helper'
module EWS # :nodoc:
# A Transporter is responsible for communicating with the E-xact Web Service in
# whichever dialect is chosen by the user. The available options are:
# :json REST with JSON payload
# :rest REST with XML payload (default)
# :soap SOAP
#
# The Transporter will connect to the service, using SSL if required, and will
# encode Reqests to send to the service, and decode Responses received from the
# service.
#
# Once configured to connect to a particular service, it can be used repeatedly
# to send as many transactions as required.
class Transporter
include CertificateHelper
# Initialize a Transporter.
#
# You can specify the URL you would like the Transporter to connect to, although it defaults
# to https://api.e-xact.com, the location of our transaction processing web service.
#
# You can also specify a hash of options as follows:
# :transport_type the transport_type for this transporter (defaults to :rest)
# :server_cert the path to the server's certificate file (defaults to E-xact's Server Cert)
# :issuer_cert the path to the issuer's certificate file (defaults to E-xact's Issuer's Cert)
#
# The default certificates are those required to connect to https://api.e-xact.com and the
# default transport_type is :rest. The default transport_type can be overridden on a per-transaction
# basis, if you choose to do so, by specifying it as a parameter to the submit method.
def initialize(url = "https://api.e-xact.com", options = {})
@url = URI.parse(url.gsub(/\/$/,''))
@transport_type = options[:transport_type] || :rest
configure_certificates(options)
end
# Submit a transaction request to the server
#
# transaction:: the Request object to encode for transmission to the server
# transport_type:: (optional) the transport type to use for this transaction only. If it is not specified, the Transporter's transport type will be used
def submit(transaction, transport_type = nil)
raise ArgumentError, "Request not supplied" if transaction.nil?
return false unless transaction.valid?
transport_type ||= @transport_type
raise ArgumentError, "Transport type #{transport_type} is not supported" unless @@transport_types.include? transport_type
transport_details = @@transport_types[transport_type]
request = build_http_request(transaction, transport_type, transport_details[:suffix])
request.basic_auth(transaction.gateway_id, transaction.password)
request.add_field "Accept", transport_details[:content_type]
request.add_field "User-Agent", "exact4r v1.6"
request.add_field "Content-type", "#{transport_details[:content_type]}; charset=UTF-8"
response = get_connection.request(request)
case response
when Net::HTTPSuccess then EWS::Transaction::Mapping.send "#{transport_type}_to_response", response.body
else
r = ::EWS::Transaction::Response.new
if(transport_type == :soap)
# we may have a SOAP Fault
r = EWS::Transaction::Mapping.send "#{transport_type}_to_response", response.body
end
# SOAP Fault may already have populated the error_number etc.
unless r.error_number
# populate the error number and description
r.error_number = response.code.to_i
r.error_description = response.message
end
r
end
end
private
def build_http_request(transaction, transport_type, request_suffix)
req = nil
if !transaction.is_find_transaction? or transport_type == :soap
req = Net::HTTP::Post.new(@url.path + "/transaction.#{request_suffix}")
if transport_type == :soap
# add the SOAPAction header
soapaction = (transaction.is_find_transaction?) ? "TransactionInfo" : "SendAndCommit"
req.add_field "soapaction", "http://secure2.e-xact.com/vplug-in/transaction/rpc-enc/#{soapaction}"
end
req.body = EWS::Transaction::Mapping.send "request_to_#{transport_type.to_s}", transaction
else
param_str = ""
escaping_regex = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
params = [:authorization_num, :reference_no].collect do |attr_name|
value = transaction.send(attr_name)
value.blank? ? nil : "#{attr_name}=" + URI.escape(value, escaping_regex)
end.compact
param_str = "?"+params.join('&') unless params.empty?
req = Net::HTTP::Get.new(@url.path + "/transaction/#{transaction.transaction_tag}.#{request_suffix}"+param_str)
end
req
end
def get_connection
# re-use the connection if it's available
return @connection unless @connection.nil?
@connection = Net::HTTP.new(@url.host, @url.port)
@connection.set_debug_output $stdout if $DEBUG
if @url.scheme == 'https'
@connection.use_ssl = true
@connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
@connection.verify_callback = method(:validate_certificate)
@connection.ca_file = self.issuer_cert_file
end
@connection
end
# what transport types we support, and their corresponding suffixes
@@transport_types = {
:rest => {:suffix => "xml", :content_type => "application/xml"},
:json => {:suffix => "json", :content_type => "application/json"},
:soap => {:suffix => "xmlsoap", :content_type => "application/xml"}
}
end
end