module ActiveMerchant #:nodoc: module Billing #:nodoc: # For more information on the Iridium Gateway please download the # documentation from their Merchant Management System. # # The login and password are not the username and password you use to # login to the Iridium Merchant Management System. Instead, you will # use the API username and password you were issued separately. class IridiumGateway < Gateway self.live_url = self.test_url = 'https://gw1.iridiumcorp.net/' # The countries the gateway supports merchants from as 2 digit ISO country codes self.supported_countries = ['GB', 'ES'] self.default_currency = 'EUR' self.money_format = :cents # The card types supported by the payment gateway self.supported_cardtypes = [:visa, :master, :american_express, :discover, :maestro, :jcb, :solo, :diners_club] # The homepage URL of the gateway self.homepage_url = 'http://www.iridiumcorp.co.uk/' # The name of the gateway self.display_name = 'Iridium' CURRENCY_CODES = { "AED" => '784', "AFN" => '971', "ALL" => '008', "AMD" => '051', "ANG" => '532', "AOA" => '973', "ARS" => '032', "AUD" => '036', "AWG" => '533', "AZN" => '944', "BAM" => '977', "BBD" => '052', "BDT" => '050', "BGN" => '975', "BHD" => '048', "BIF" => '108', "BMD" => '060', "BND" => '096', "BOB" => '068', "BOV" => '984', "BRL" => '986', "BSD" => '044', "BTN" => '064', "BWP" => '072', "BYR" => '974', "BZD" => '084', "CAD" => '124', "CDF" => '976', "CHE" => '947', "CHF" => '756', "CHW" => '948', "CLF" => '990', "CLP" => '152', "CNY" => '156', "COP" => '170', "COU" => '970', "CRC" => '188', "CUP" => '192', "CVE" => '132', "CYP" => '196', "CZK" => '203', "DJF" => '262', "DKK" => '208', "DOP" => '214', "DZD" => '012', "EEK" => '233', "EGP" => '818', "ERN" => '232', "ETB" => '230', "EUR" => '978', "FJD" => '242', "FKP" => '238', "GBP" => '826', "GEL" => '981', "GHS" => '288', "GIP" => '292', "GMD" => '270', "GNF" => '324', "GTQ" => '320', "GYD" => '328', "HKD" => '344', "HNL" => '340', "HRK" => '191', "HTG" => '332', "HUF" => '348', "IDR" => '360', "ILS" => '376', "INR" => '356', "IQD" => '368', "IRR" => '364', "ISK" => '352', "JMD" => '388', "JOD" => '400', "JPY" => '392', "KES" => '404', "KGS" => '417', "KHR" => '116', "KMF" => '174', "KPW" => '408', "KRW" => '410', "KWD" => '414', "KYD" => '136', "KZT" => '398', "LAK" => '418', "LBP" => '422', "LKR" => '144', "LRD" => '430', "LSL" => '426', "LTL" => '440', "LVL" => '428', "LYD" => '434', "MAD" => '504', "MDL" => '498', "MGA" => '969', "MKD" => '807', "MMK" => '104', "MNT" => '496', "MOP" => '446', "MRO" => '478', "MTL" => '470', "MUR" => '480', "MVR" => '462', "MWK" => '454', "MXN" => '484', "MXV" => '979', "MYR" => '458', "MZN" => '943', "NAD" => '516', "NGN" => '566', "NIO" => '558', "NOK" => '578', "NPR" => '524', "NZD" => '554', "OMR" => '512', "PAB" => '590', "PEN" => '604', "PGK" => '598', "PHP" => '608', "PKR" => '586', "PLN" => '985', "PYG" => '600', "QAR" => '634', "ROL" => '642', "RON" => '946', "RSD" => '941', "RUB" => '643', "RWF" => '646', "SAR" => '682', "SBD" => '090', "SCR" => '690', "SDG" => '938', "SEK" => '752', "SGD" => '702', "SHP" => '654', "SKK" => '703', "SLL" => '694', "SOS" => '706', "SRD" => '968', "STD" => '678', "SYP" => '760', "SZL" => '748', "THB" => '764', "TJS" => '972', "TMM" => '795', "TND" => '788', "TOP" => '776', "TRY" => '949', "TTD" => '780', "TWD" => '901', "TZS" => '834', "UAH" => '980', "UGX" => '800', "USD" => '840', "USN" => '997', "USS" => '998', "UYU" => '858', "UZS" => '860', "VEB" => '862', "VND" => '704', "VUV" => '548', "WST" => '882', "XAF" => '950', "XAG" => '961', "XAU" => '959', "XBA" => '955', "XBB" => '956', "XBC" => '957', "XBD" => '958', "XCD" => '951', "XDR" => '960', "XOF" => '952', "XPD" => '964', "XPF" => '953', "XPT" => '962', "XTS" => '963', "XXX" => '999', "YER" => '886', "ZAR" => '710', "ZMK" => '894', "ZWD" => '716', } AVS_CODE = { "PASSED" => "Y", "FAILED" => "N", "PARTIAL" => "X", "NOT_CHECKED" => "X", "UNKNOWN" => "X" } CVV_CODE = { "PASSED" => "M", "FAILED" => "N", "PARTIAL" => "I", "NOT_CHECKED" => "P", "UNKNOWN" => "U" } def initialize(options = {}) requires!(options, :login, :password) super end def authorize(money, payment_source, options = {}) setup_address_hash(options) if payment_source.respond_to?(:number) commit(build_purchase_request('PREAUTH', money, payment_source, options), options) else commit(build_reference_request('PREAUTH', money, payment_source, options), options) end end def purchase(money, payment_source, options = {}) setup_address_hash(options) if payment_source.respond_to?(:number) commit(build_purchase_request('SALE', money, payment_source, options), options) else commit(build_reference_request('SALE', money, payment_source, options), options) end end def capture(money, authorization, options = {}) commit(build_reference_request('COLLECTION', money, authorization, options), options) end def credit(money, authorization, options={}) ActiveMerchant.deprecated CREDIT_DEPRECATION_MESSAGE refund(money, authorization, options) end def refund(money, authorization, options={}) commit(build_reference_request('REFUND', money, authorization, options), options) end def void(authorization, options={}) commit(build_reference_request('VOID', nil, authorization, options), options) end def supports_scrubbing true end def scrub(transcript) transcript. gsub(%r((Authorization: Basic )\w+), '\1[FILTERED]'). gsub(%r(()\d+()), '\1[FILTERED]\2'). gsub(%r(()\d+()), '\1[FILTERED]\2') end private def build_purchase_request(type, money, creditcard, options) options.merge!(:action => 'CardDetailsTransaction') build_request(options) do |xml| add_purchase_data(xml, type, money, options) add_creditcard(xml, creditcard) add_customerdetails(xml, creditcard, options[:billing_address], options) end end def build_reference_request(type, money, authorization, options) options.merge!(:action => 'CrossReferenceTransaction') order_id, cross_reference, _ = authorization.split(";") build_request(options) do |xml| if money details = {'CurrencyCode' => currency_code(options[:currency] || default_currency), 'Amount' => amount(money)} else details = {'CurrencyCode' => currency_code(default_currency), 'Amount' => '0'} end xml.tag! 'TransactionDetails', details do xml.tag! 'MessageDetails', {'TransactionType' => type, 'CrossReference' => cross_reference} xml.tag! 'OrderID', (options[:order_id] || order_id) end end end def build_request(options) requires!(options, :action) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8') xml.tag! 'soap:Envelope', { 'xmlns:soap' => 'http://schemas.xmlsoap.org/soap/envelope/', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'} do xml.tag! 'soap:Body' do xml.tag! options[:action], {'xmlns' => "https://www.thepaymentgateway.net/"} do xml.tag! 'PaymentMessage' do add_merchant_data(xml, options) yield(xml) end end end end xml.target! end def setup_address_hash(options) options[:billing_address] = options[:billing_address] || options[:address] || {} options[:shipping_address] = options[:shipping_address] || {} end def add_purchase_data(xml, type, money, options) requires!(options, :order_id) xml.tag! 'TransactionDetails', {'Amount' => amount(money), 'CurrencyCode' => currency_code(options[:currency] || currency(money))} do xml.tag! 'MessageDetails', {'TransactionType' => type} xml.tag! 'OrderID', options[:order_id] xml.tag! 'TransactionControl' do xml.tag! 'ThreeDSecureOverridePolicy', 'FALSE' xml.tag! 'EchoAVSCheckResult', 'TRUE' xml.tag! 'EchoCV2CheckResult', 'TRUE' end end end def add_customerdetails(xml, creditcard, address, options, shipTo = false) xml.tag! 'CustomerDetails' do if address unless address[:country].blank? country_code = Country.find(address[:country]).code(:numeric) end xml.tag! 'BillingAddress' do xml.tag! 'Address1', address[:address1] xml.tag! 'Address2', address[:address2] xml.tag! 'City', address[:city] xml.tag! 'State', address[:state] xml.tag! 'PostCode', address[:zip] xml.tag! 'CountryCode', country_code if country_code end xml.tag! 'PhoneNumber', address[:phone] end xml.tag! 'EmailAddress', options[:email] xml.tag! 'CustomerIPAddress', options[:ip] || "127.0.0.1" end end def add_creditcard(xml, creditcard) xml.tag! 'CardDetails' do xml.tag! 'CardName', creditcard.name xml.tag! 'CV2', creditcard.verification_value if creditcard.verification_value xml.tag! 'CardNumber', creditcard.number xml.tag! 'ExpiryDate', { 'Month' => creditcard.month.to_s.rjust(2, "0"), 'Year' => creditcard.year.to_s[/\d\d$/] } end end def add_merchant_data(xml, options) xml.tag! 'MerchantAuthentication', {"MerchantID" => @options[:login], "Password" => @options[:password]} end def commit(request, options) requires!(options, :action) response = parse(ssl_post(test? ? self.test_url : self.live_url, request, {"SOAPAction" => "https://www.thepaymentgateway.net/" + options[:action], "Content-Type" => "text/xml; charset=utf-8" })) success = response[:transaction_result][:status_code] == "0" message = response[:transaction_result][:message] authorization = success ? [ options[:order_id], response[:transaction_output_data][:cross_reference], response[:transaction_output_data][:auth_code] ].compact.join(";") : nil Response.new(success, message, response, :test => test?, :authorization => authorization, :avs_result => { :street_match => AVS_CODE[ response[:transaction_output_data][:address_numeric_check_result] ], :postal_match => AVS_CODE[ response[:transaction_output_data][:post_code_check_result] ], }, :cvv_result => CVV_CODE[ response[:transaction_output_data][:cv2_check_result] ] ) end def parse(xml) reply = {} xml = REXML::Document.new(xml) if (root = REXML::XPath.first(xml, "//CardDetailsTransactionResponse")) or (root = REXML::XPath.first(xml, "//CrossReferenceTransactionResponse")) root.elements.to_a.each do |node| case node.name when 'Message' reply[:message] = reply(node.text) else parse_element(reply, node) end end elsif root = REXML::XPath.first(xml, "//soap:Fault") parse_element(reply, root) reply[:message] = "#{reply[:faultcode]}: #{reply[:faultstring]}" end reply end def parse_element(reply, node) case node.name when "CrossReferenceTransactionResult" reply[:transaction_result] = {} node.attributes.each do |a,b| reply[:transaction_result][a.underscore.to_sym] = b end node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? when "CardDetailsTransactionResult" reply[:transaction_result] = {} node.attributes.each do |a,b| reply[:transaction_result][a.underscore.to_sym] = b end node.elements.each{|e| parse_element(reply[:transaction_result], e) } if node.has_elements? when "TransactionOutputData" reply[:transaction_output_data] = {} node.attributes.each{|a,b| reply[:transaction_output_data][a.underscore.to_sym] = b } node.elements.each{|e| parse_element(reply[:transaction_output_data], e) } if node.has_elements? when "CustomVariables" reply[:custom_variables] = {} node.attributes.each{|a,b| reply[:custom_variables][a.underscore.to_sym] = b } node.elements.each{|e| parse_element(reply[:custom_variables], e) } if node.has_elements? when "GatewayEntryPoints" reply[:gateway_entry_points] = {} node.attributes.each{|a,b| reply[:gateway_entry_points][a.underscore.to_sym] = b } node.elements.each{|e| parse_element(reply[:gateway_entry_points], e) } if node.has_elements? else k = node.name.underscore.to_sym if node.has_elements? reply[k] = {} node.elements.each{|e| parse_element(reply[k], e) } else if node.has_attributes? reply[k] = {} node.attributes.each{|a,b| reply[k][a.underscore.to_sym] = b } else reply[k] = node.text end end end reply end def currency_code(currency) CURRENCY_CODES[currency] end end end end