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, :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[: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[: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
country_code = Country.find(address[:country]).code(:numeric) unless address[:country].blank?
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