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