activemerchant 1.42.7 → 1.42.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NDRjYjY2NmNjZWUyNDMwMjdhOTZjOGMyMzJjOTUxYWMxNjFlNjVkNw==
5
- data.tar.gz: !binary |-
6
- ZTFjN2I2ZjQ3ZTdkZTIwZWY0ZGY0OTFhNzhjOTkwYWIxNDFlYTI3OA==
2
+ SHA1:
3
+ metadata.gz: 81edb8d2513f065dd9170ead1d0d6f72d4fc39c8
4
+ data.tar.gz: b14933f6c29ebd5ed555e12238a29828a959687a
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OTZiMWRlNzE0OWZmOWQwZjk2NjNlZmJjYmYxOGNhMWIzMjk4ZDAxMDg5MjZk
10
- Y2I3ZDVhYjkzZDA4NTAyYjk0ZDU3ZjZkODAyMmEzOWVjMjg1OTJlNmRiYzg4
11
- YTQ5YTc4ZGRjMmVmZGU5N2ZlMWI0MjdiNmRjZjUyZmVlN2NhMzU=
12
- data.tar.gz: !binary |-
13
- YTY3ZGViYTg2MTIxOTFhZmFhMzE5Y2JiZmYxNzZlMzMzMDJhNDYwNTA1OTE0
14
- ZDEzNWQ1OGYyZjJkOTM5ODE0OGViNGFiNmFmNjg3NDczMmQyOTI1OWYyMDZi
15
- ZTZjYzVkZWM5ZTFhNzhkOGU4MTc3YWY1NGI3OWMyM2Q0OGIzYTA=
6
+ metadata.gz: 9350271f4a9016fd5c8d0113def4e0ec6a654578d63d824b07647a59128110f6b9bd2a33c3205ad2c89debe1b013b555d08cbd51e68b2333a24a0f98a7a20326
7
+ data.tar.gz: 3ce7cf004ee44048d02e6805dd2b63808c104a6cad0d42980246707fb8d50a302d4798c63ced00a2ff7526bedf93e84dfd88ebf2adac2326f10a5a51666bc32c
Binary file
data.tar.gz.sig CHANGED
@@ -1,3 +1,2 @@
1
- <v;�5Z����gw��@��>u�O5�h~�����P���n*L�%_��D�,c�g�`v-v����!��!>7f2s���f���B!
2
- O �,��i���Ӛ�@�%k�Ĩs}E|@�o�g��R��L9l0.7j��p����}V
3
- ,�!8TD)�(�J3��"��;�"i�l���5�B�
1
+ e��kM����6Q�)Q ��*ki-1P������{��2\v2����68B��kU�?i�� ��]CE1m����\�3�׵Jh�`�T�4���U���n�œM������B�S֌����u�zL�u������e2Q�b"ͯu��7<��S���^A-��j�n+/}n/U.R �'>>lޡvy� �o�>���?i#]Q�\$z
2
+ ��c��׵�"P����q�x �i�[(Wau�[��t)J�(Tx���;
data/CHANGELOG CHANGED
@@ -1,5 +1,15 @@
1
1
  = ActiveMerchant CHANGELOG
2
2
 
3
+ == Version 1.42.8 (April 4, 2014)
4
+
5
+ * Cecabank: Handle invalid xml response body [duff]
6
+ * Wirecard: Capture error code in the response [duff]
7
+ * Litle: Remove gem dependency [duff]
8
+ * Litle: Fix case of missing address parts [duff]
9
+ * Universal: Add universal offsite API implementation [bslobodin]
10
+ * Iridium: Add more currencies [bslobodin]
11
+ * iDeal: Add Mollie iDeal offsite implementation [wvanbergen, maartenvg]
12
+
3
13
  == Version 1.42.7 (March 18, 2014)
4
14
 
5
15
  * SagePay: Add support for ReferrerID [markabe]
@@ -113,30 +113,24 @@ module ActiveMerchant #:nodoc:
113
113
  root = REXML::Document.new(body).root
114
114
 
115
115
  response[:success] = (root.attributes['valor'] == "OK")
116
-
117
- #common params to all responses
118
116
  response[:date] = root.attributes['fecha']
119
117
  response[:operation_number] = root.attributes['numeroOperacion']
120
118
  response[:message] = root.attributes['valor']
121
119
 
122
- #success
123
120
  if root.elements['OPERACION']
124
121
  response[:operation_type] = root.elements['OPERACION'].attributes['tipo']
125
122
  response[:amount] = root.elements['OPERACION/importe'].text.strip
126
123
  end
127
124
 
128
- #optional params
129
125
  response[:description] = root.elements['OPERACION/descripcion'].text if root.elements['OPERACION/descripcion']
130
126
  response[:authorization_number] = root.elements['OPERACION/numeroAutorizacion'].text if root.elements['OPERACION/numeroAutorizacion']
131
127
  response[:reference] = root.elements['OPERACION/referencia'].text if root.elements['OPERACION/referencia']
132
128
  response[:pan] = root.elements['OPERACION/pan'].text if root.elements['OPERACION/pan']
133
129
 
134
130
  if root.elements['ERROR']
135
- #error
136
131
  response[:error_code] = root.elements['ERROR/codigo'].text
137
132
  response[:error_message] = root.elements['ERROR/descripcion'].text
138
133
  else
139
- #authorization
140
134
  if("000" == root.elements['OPERACION'].attributes['numeroOperacion'])
141
135
  if(root.elements['OPERACION/numeroAutorizacion'])
142
136
  response[:authorization] = root.elements['OPERACION/numeroAutorizacion'].text
@@ -146,6 +140,12 @@ module ActiveMerchant #:nodoc:
146
140
  end
147
141
  end
148
142
 
143
+ return response
144
+
145
+ rescue REXML::ParseException => e
146
+ response[:success] = false
147
+ response[:message] = "Unable to parse the response."
148
+ response[:error_message] = e.message
149
149
  response
150
150
  end
151
151
 
@@ -24,13 +24,186 @@ module ActiveMerchant #:nodoc:
24
24
  self.display_name = 'Iridium'
25
25
 
26
26
  CURRENCY_CODES = {
27
+ "AED" => '784',
28
+ "AFN" => '971',
29
+ "ALL" => '008',
30
+ "AMD" => '051',
31
+ "ANG" => '532',
32
+ "AOA" => '973',
33
+ "ARS" => '032',
27
34
  "AUD" => '036',
35
+ "AWG" => '533',
36
+ "AZN" => '944',
37
+ "BAM" => '977',
38
+ "BBD" => '052',
39
+ "BDT" => '050',
40
+ "BGN" => '975',
41
+ "BHD" => '048',
42
+ "BIF" => '108',
43
+ "BMD" => '060',
44
+ "BND" => '096',
45
+ "BOB" => '068',
46
+ "BOV" => '984',
47
+ "BRL" => '986',
48
+ "BSD" => '044',
49
+ "BTN" => '064',
50
+ "BWP" => '072',
51
+ "BYR" => '974',
52
+ "BZD" => '084',
28
53
  "CAD" => '124',
54
+ "CDF" => '976',
55
+ "CHE" => '947',
56
+ "CHF" => '756',
57
+ "CHW" => '948',
58
+ "CLF" => '990',
59
+ "CLP" => '152',
60
+ "CNY" => '156',
61
+ "COP" => '170',
62
+ "COU" => '970',
63
+ "CRC" => '188',
64
+ "CUP" => '192',
65
+ "CVE" => '132',
66
+ "CYP" => '196',
67
+ "CZK" => '203',
68
+ "DJF" => '262',
69
+ "DKK" => '208',
70
+ "DOP" => '214',
71
+ "DZD" => '012',
72
+ "EEK" => '233',
73
+ "EGP" => '818',
74
+ "ERN" => '232',
75
+ "ETB" => '230',
29
76
  "EUR" => '978',
77
+ "FJD" => '242',
78
+ "FKP" => '238',
30
79
  "GBP" => '826',
80
+ "GEL" => '981',
81
+ "GHS" => '288',
82
+ "GIP" => '292',
83
+ "GMD" => '270',
84
+ "GNF" => '324',
85
+ "GTQ" => '320',
86
+ "GYD" => '328',
87
+ "HKD" => '344',
88
+ "HNL" => '340',
89
+ "HRK" => '191',
90
+ "HTG" => '332',
91
+ "HUF" => '348',
92
+ "IDR" => '360',
93
+ "ILS" => '376',
94
+ "INR" => '356',
95
+ "IQD" => '368',
96
+ "IRR" => '364',
97
+ "ISK" => '352',
98
+ "JMD" => '388',
99
+ "JOD" => '400',
100
+ "JPY" => '392',
101
+ "KES" => '404',
102
+ "KGS" => '417',
103
+ "KHR" => '116',
104
+ "KMF" => '174',
105
+ "KPW" => '408',
106
+ "KRW" => '410',
107
+ "KWD" => '414',
108
+ "KYD" => '136',
109
+ "KZT" => '398',
110
+ "LAK" => '418',
111
+ "LBP" => '422',
112
+ "LKR" => '144',
113
+ "LRD" => '430',
114
+ "LSL" => '426',
115
+ "LTL" => '440',
116
+ "LVL" => '428',
117
+ "LYD" => '434',
118
+ "MAD" => '504',
119
+ "MDL" => '498',
120
+ "MGA" => '969',
121
+ "MKD" => '807',
122
+ "MMK" => '104',
123
+ "MNT" => '496',
124
+ "MOP" => '446',
125
+ "MRO" => '478',
126
+ "MTL" => '470',
127
+ "MUR" => '480',
128
+ "MVR" => '462',
129
+ "MWK" => '454',
31
130
  "MXN" => '484',
131
+ "MXV" => '979',
132
+ "MYR" => '458',
133
+ "MZN" => '943',
134
+ "NAD" => '516',
135
+ "NGN" => '566',
136
+ "NIO" => '558',
137
+ "NOK" => '578',
138
+ "NPR" => '524',
32
139
  "NZD" => '554',
140
+ "OMR" => '512',
141
+ "PAB" => '590',
142
+ "PEN" => '604',
143
+ "PGK" => '598',
144
+ "PHP" => '608',
145
+ "PKR" => '586',
146
+ "PLN" => '985',
147
+ "PYG" => '600',
148
+ "QAR" => '634',
149
+ "ROL" => '642',
150
+ "RON" => '946',
151
+ "RSD" => '941',
152
+ "RUB" => '643',
153
+ "RWF" => '646',
154
+ "SAR" => '682',
155
+ "SBD" => '090',
156
+ "SCR" => '690',
157
+ "SDG" => '938',
158
+ "SEK" => '752',
159
+ "SGD" => '702',
160
+ "SHP" => '654',
161
+ "SKK" => '703',
162
+ "SLL" => '694',
163
+ "SOS" => '706',
164
+ "SRD" => '968',
165
+ "STD" => '678',
166
+ "SYP" => '760',
167
+ "SZL" => '748',
168
+ "THB" => '764',
169
+ "TJS" => '972',
170
+ "TMM" => '795',
171
+ "TND" => '788',
172
+ "TOP" => '776',
173
+ "TRY" => '949',
174
+ "TTD" => '780',
175
+ "TWD" => '901',
176
+ "TZS" => '834',
177
+ "UAH" => '980',
178
+ "UGX" => '800',
33
179
  "USD" => '840',
180
+ "USN" => '997',
181
+ "USS" => '998',
182
+ "UYU" => '858',
183
+ "UZS" => '860',
184
+ "VEB" => '862',
185
+ "VND" => '704',
186
+ "VUV" => '548',
187
+ "WST" => '882',
188
+ "XAF" => '950',
189
+ "XAG" => '961',
190
+ "XAU" => '959',
191
+ "XBA" => '955',
192
+ "XBB" => '956',
193
+ "XBC" => '957',
194
+ "XBD" => '958',
195
+ "XCD" => '951',
196
+ "XDR" => '960',
197
+ "XOF" => '952',
198
+ "XPD" => '964',
199
+ "XPF" => '953',
200
+ "XPT" => '962',
201
+ "XTS" => '963',
202
+ "XXX" => '999',
203
+ "YER" => '886',
204
+ "ZAR" => '710',
205
+ "ZMK" => '894',
206
+ "ZWD" => '716',
34
207
  }
35
208
 
36
209
  def initialize(options = {})
@@ -1,141 +1,119 @@
1
+ require 'nokogiri'
2
+
1
3
  module ActiveMerchant #:nodoc:
2
4
  module Billing #:nodoc:
3
5
  class LitleGateway < Gateway
4
- # Specific to Litle options:
5
- # * <tt>:merchant_id</tt> - Merchant Id assigned by Litle
6
- # * <tt>:user</tt> - Username assigned by Litle
7
- # * <tt>:password</tt> - Password assigned by Litle
8
- # * <tt>:version</tt> - The version of the api you are using (eg, '8.10')
9
- # * <tt>:proxy_addr</tt> - Proxy address - nil if not needed
10
- # * <tt>:proxy_port</tt> - Proxy port - nil if not needed
11
- # * <tt>:url</tt> - URL assigned by Litle (for testing, use the sandbox)
12
- #
13
- # Standard Active Merchant options
14
- # * <tt>:order_id</tt> - The order number
15
- # * <tt>:ip</tt> - The IP address of the customer making the purchase
16
- # * <tt>:customer</tt> - The name, customer number, or other information that identifies the customer
17
- # * <tt>:invoice</tt> - The invoice number
18
- # * <tt>:merchant</tt> - The name or description of the merchant offering the product
19
- # * <tt>:description</tt> - A description of the transaction
20
- # * <tt>:email</tt> - The email address of the customer
21
- # * <tt>:currency</tt> - The currency of the transaction. Only important when you are using a currency that is not the default with a gateway that supports multiple currencies.
22
- # * <tt>:billing_address</tt> - A hash containing the billing address of the customer.
23
- # * <tt>:shipping_address</tt> - A hash containing the shipping address of the customer.
24
- #
25
- # The <tt>:billing_address</tt>, and <tt>:shipping_address</tt> hashes can have the following keys:
26
- #
27
- # * <tt>:name</tt> - The full name of the customer.
28
- # * <tt>:company</tt> - The company name of the customer.
29
- # * <tt>:address1</tt> - The primary street address of the customer.
30
- # * <tt>:address2</tt> - Additional line of address information.
31
- # * <tt>:city</tt> - The city of the customer.
32
- # * <tt>:state</tt> - The state of the customer. The 2 digit code for US and Canadian addresses. The full name of the state or province for foreign addresses.
33
- # * <tt>:country</tt> - The [ISO 3166-1-alpha-2 code](http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm) for the customer.
34
- # * <tt>:zip</tt> - The zip or postal code of the customer.
35
- # * <tt>:phone</tt> - The phone number of the customer.
6
+ SCHEMA_VERSION = '8.18'
36
7
 
37
8
  self.test_url = 'https://www.testlitle.com/sandbox/communicator/online'
38
9
  self.live_url = 'https://payments.litle.com/vap/communicator/online'
39
10
 
40
- LITLE_SCHEMA_VERSION = '8.13'
41
-
42
- # The countries the gateway supports merchants from as 2 digit ISO country codes
43
11
  self.supported_countries = ['US']
44
-
45
- # The card types supported by the payment gateway
12
+ self.default_currency = 'USD'
46
13
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
47
14
 
48
- # The homepage URL of the gateway
49
- self.homepage_url = 'http://www.litle.com/'
50
-
51
- # The name of the gateway
52
- self.display_name = 'Litle & Co.'
15
+ self.homepage_url = 'http://www.litle.com/'
16
+ self.display_name = 'Litle & Co.'
53
17
 
54
- self.default_currency = 'USD'
18
+ # Public: Create a new Litle gateway.
19
+ #
20
+ # options - A hash of options:
21
+ # :login - The user.
22
+ # :password - The password.
23
+ # :merchant_id - The merchant id.
24
+ def initialize(options={})
25
+ requires!(options, :login, :password, :merchant_id)
26
+ super
27
+ end
55
28
 
56
- def initialize(options = {})
57
- begin
58
- require 'LitleOnline'
59
- rescue LoadError
60
- raise "Could not load the LitleOnline gem (> 08.15.0). Use `gem install LitleOnline` to install it."
29
+ def purchase(money, payment_method, options={})
30
+ request = build_xml_request do |doc|
31
+ add_authentication(doc)
32
+ doc.sale(transaction_attributes(options)) do
33
+ add_auth_purchase_params(doc, money, payment_method, options)
34
+ end
61
35
  end
62
36
 
63
- if wiredump_device
64
- LitleOnline::Configuration.logger = ((Logger === wiredump_device) ? wiredump_device : Logger.new(wiredump_device))
65
- LitleOnline::Configuration.logger.level = Logger::DEBUG
66
- else
67
- LitleOnline::Configuration.logger = Logger.new(STDOUT)
68
- LitleOnline::Configuration.logger.level = Logger::WARN
37
+ commit(:sale, request)
38
+ end
39
+
40
+ def authorize(money, payment_method, options={})
41
+ request = build_xml_request do |doc|
42
+ add_authentication(doc)
43
+ doc.authorization(transaction_attributes(options)) do
44
+ add_auth_purchase_params(doc, money, payment_method, options)
45
+ end
69
46
  end
70
47
 
71
- @litle = LitleOnline::LitleOnlineRequest.new
48
+ commit(:authorization, request)
49
+ end
72
50
 
73
- options[:version] ||= LITLE_SCHEMA_VERSION
74
- options[:merchant] ||= 'Default Report Group'
75
- options[:user] ||= options[:login]
51
+ def capture(money, authorization, options={})
52
+ transaction_id, kind = split_authorization(authorization)
76
53
 
77
- requires!(options, :merchant_id, :user, :password, :merchant, :version)
54
+ request = build_xml_request do |doc|
55
+ add_authentication(doc)
56
+ doc.capture_(transaction_attributes(options)) do
57
+ doc.litleTxnId(transaction_id)
58
+ doc.amount(money) if money
59
+ end
60
+ end
78
61
 
79
- super
62
+ commit(:capture, request)
80
63
  end
81
64
 
82
- def authorize(money, creditcard_or_token, options = {})
83
- to_pass = build_authorize_request(money, creditcard_or_token, options)
84
- build_response(:authorization, @litle.authorization(to_pass))
65
+ def credit(money, authorization, options = {})
66
+ deprecated CREDIT_DEPRECATION_MESSAGE
67
+ refund(money, authorization, options)
85
68
  end
86
69
 
87
- def purchase(money, creditcard_or_token, options = {})
88
- to_pass = build_purchase_request(money, creditcard_or_token, options)
89
- build_response(:sale, @litle.sale(to_pass))
70
+ def refund(money, authorization, options={})
71
+ transaction_id, kind = split_authorization(authorization)
72
+
73
+ request = build_xml_request do |doc|
74
+ add_authentication(doc)
75
+ doc.credit(transaction_attributes(options)) do
76
+ doc.litleTxnId(transaction_id)
77
+ doc.amount(money) if money
78
+ end
79
+ end
80
+
81
+ commit(:credit, request)
90
82
  end
91
83
 
92
- def capture(money, authorization, options = {})
84
+ def void(authorization, options={})
93
85
  transaction_id, kind = split_authorization(authorization)
94
- to_pass = create_capture_hash(money, transaction_id, options)
95
- build_response(:capture, @litle.capture(to_pass))
96
- end
97
86
 
98
- # Note: Litle requires that authorization requests be voided via auth_reversal
99
- # and other requests via void. To maintain the same interface as the other
100
- # gateways the transaction_id and the kind of transaction are concatenated
101
- # together with a ; separator (e.g. 1234;authorization)
102
- #
103
- # A partial auth_reversal can be accomplished by passing :amount as an option
104
- def void(identification, options = {})
105
- transaction_id, kind = split_authorization(identification)
106
- if(kind == 'authorization')
107
- to_pass = create_auth_reversal_hash(transaction_id, options[:amount], options)
108
- build_response(:authReversal, @litle.auth_reversal(to_pass))
109
- else
110
- to_pass = create_void_hash(transaction_id, options)
111
- build_response(:void, @litle.void(to_pass))
87
+ request = build_xml_request do |doc|
88
+ add_authentication(doc)
89
+ doc.send(void_type(kind), transaction_attributes(options)) do
90
+ doc.litleTxnId(transaction_id)
91
+ end
112
92
  end
113
- end
114
93
 
115
- def refund(money, authorization, options = {})
116
- to_pass = build_credit_request(money, authorization, options)
117
- build_response(:credit, @litle.credit(to_pass))
94
+ commit(void_type(kind), request)
118
95
  end
119
96
 
120
- def credit(money, authorization, options = {})
121
- deprecated CREDIT_DEPRECATION_MESSAGE
122
- refund(money, authorization, options)
123
- end
97
+ def store(creditcard, options = {})
98
+ request = build_xml_request do |doc|
99
+ add_authentication(doc)
100
+ doc.registerTokenRequest(transaction_attributes(options)) do
101
+ doc.orderId(truncated(options[:order_id]))
102
+ doc.accountNumber(creditcard.number)
103
+ end
104
+ end
124
105
 
125
- def store(creditcard_or_paypage_registration_id, options = {})
126
- to_pass = create_token_hash(creditcard_or_paypage_registration_id, options)
127
- build_response(:registerToken, @litle.register_token_request(to_pass), %w(000 801 802))
106
+ commit(:registerToken, request)
128
107
  end
129
108
 
130
109
  private
131
-
132
110
  CARD_TYPE = {
133
- 'visa' => 'VI',
134
- 'master' => 'MC',
135
- 'american_express' => 'AX',
136
- 'discover' => 'DI',
137
- 'jcb' => 'DI',
138
- 'diners_club' => 'DI'
111
+ 'visa' => 'VI',
112
+ 'master' => 'MC',
113
+ 'american_express' => 'AX',
114
+ 'discover' => 'DI',
115
+ 'jcb' => 'JC',
116
+ 'diners_club' => 'DC'
139
117
  }
140
118
 
141
119
  AVS_RESPONSE_CODE = {
@@ -156,410 +134,172 @@ module ActiveMerchant #:nodoc:
156
134
  '40' => 'E'
157
135
  }
158
136
 
159
- def url
160
- return @options[:url] if @options[:url].present?
161
-
162
- test? ? self.test_url : self.live_url
163
- end
164
-
165
- def build_response(kind, litle_response, valid_responses=%w(000))
166
- response = Hash.from_xml(litle_response.raw_xml.to_s)['litleOnlineResponse']
167
-
168
- if response['response'] == "0"
169
- detail = response["#{kind}Response"]
170
- fraud = fraud_result(detail)
171
- Response.new(
172
- valid_responses.include?(detail['response']),
173
- detail['message'],
174
- { litleOnlineResponse: response, response_code: detail['response'] },
175
- authorization: authorization_from(detail, kind),
176
- avs_result: { :code => fraud['avs'] },
177
- cvv_result: fraud['cvv'],
178
- test: test?
179
- )
180
- else
181
- Response.new(false, response['message'], :litleOnlineResponse => response, :test => test?)
182
- end
137
+ def void_type(kind)
138
+ (kind == 'authorization') ? :authReversal : :void
183
139
  end
184
140
 
185
- # Generates an authorization string of the appropriate id and the kind of transaction
186
- # See #void for how the kind is used
187
- def authorization_from(litle_response, kind)
188
- case kind
189
- when :registerToken
190
- authorization = litle_response['litleToken']
191
- else
192
- authorization = [litle_response['litleTxnId'], kind.to_s].join(";")
141
+ def add_authentication(doc)
142
+ doc.authentication do
143
+ doc.user(@options[:login])
144
+ doc.password(@options[:password])
193
145
  end
194
146
  end
195
147
 
196
- def split_authorization(authorization)
197
- transaction_id, kind = authorization.to_s.split(';')
198
- [transaction_id, kind]
199
- end
200
-
201
- def build_authorize_request(money, creditcard_or_token, options)
202
- payment_method = build_payment_method(creditcard_or_token, options)
203
-
204
- hash = create_hash(money, options)
205
-
206
- add_creditcard_or_cardtoken_hash(hash, payment_method)
207
-
208
- hash
148
+ def add_auth_purchase_params(doc, money, payment_method, options)
149
+ doc.orderId(truncated(options[:order_id]))
150
+ doc.amount(money)
151
+ doc.orderSource('ecommerce')
152
+ add_billing_address(doc, payment_method, options)
153
+ add_shipping_address(doc, payment_method, options)
154
+ add_payment_method(doc, payment_method)
209
155
  end
210
156
 
211
- def build_purchase_request(money, creditcard_or_token, options)
212
- payment_method = build_payment_method(creditcard_or_token, options)
213
-
214
- hash = create_hash(money, options)
215
-
216
- add_creditcard_or_cardtoken_hash(hash, payment_method)
217
-
218
- hash
157
+ def add_payment_method(doc, payment_method)
158
+ if payment_method.is_a?(String)
159
+ doc.token do
160
+ doc.litleToken(payment_method)
161
+ end
162
+ else
163
+ doc.card do
164
+ doc.type_(CARD_TYPE[payment_method.brand])
165
+ doc.number(payment_method.number)
166
+ doc.expDate(exp_date(payment_method))
167
+ doc.cardValidationNum(payment_method.verification_value)
168
+ end
169
+ end
219
170
  end
220
171
 
221
- def build_credit_request(money, identification_or_token, options)
222
- payment_method = build_payment_method(identification_or_token, options)
172
+ def add_billing_address(doc, payment_method, options)
173
+ return if payment_method.is_a?(String)
223
174
 
224
- hash = create_hash(money, options)
175
+ doc.billToAddress do
176
+ doc.name(payment_method.name)
177
+ doc.email(options[:email]) if options[:email]
225
178
 
226
- add_identification_or_cardtoken_hash(hash, payment_method)
227
-
228
- unless payment_method.is_a?(LitleCardToken)
229
- hash['orderSource'] = nil
230
- hash['orderId'] = nil
179
+ add_address(doc, options[:billing_address])
231
180
  end
232
-
233
- hash
234
181
  end
235
182
 
236
- def build_payment_method(payment_method, options)
237
- result = payment_method
183
+ def add_shipping_address(doc, payment_method, options)
184
+ return if payment_method.is_a?(String)
238
185
 
239
- # Build instance of the LitleCardToken class for internal use if this is a token request.
240
- if payment_method.is_a?(String) && options.has_key?(:token)
241
- result = LitleCardToken.new(:token => payment_method)
242
- result.month = options[:token][:month]
243
- result.year = options[:token][:year]
244
- result.verification_value = options[:token][:verification_value]
245
- result.brand = options[:token][:brand]
186
+ doc.shipToAddress do
187
+ add_address(doc, options[:shipping_address])
246
188
  end
247
-
248
- result
249
189
  end
250
190
 
251
- def add_creditcard_or_cardtoken_hash(hash, creditcard_or_cardtoken)
252
- if creditcard_or_cardtoken.is_a?(LitleCardToken)
253
- add_cardtoken_hash(hash, creditcard_or_cardtoken)
254
- else
255
- add_creditcard_hash(hash, creditcard_or_cardtoken)
256
- end
191
+ def add_address(doc, address)
192
+ return unless address
193
+
194
+ doc.companyName(address[:company]) unless address[:company].blank?
195
+ doc.addressLine1(address[:address1]) unless address[:address1].blank?
196
+ doc.addressLine2(address[:address2]) unless address[:address2].blank?
197
+ doc.city(address[:city]) unless address[:city].blank?
198
+ doc.state(address[:state]) unless address[:state].blank?
199
+ doc.zip(address[:zip]) unless address[:zip].blank?
200
+ doc.country(address[:country]) unless address[:country].blank?
201
+ doc.phone(address[:phone]) unless address[:phone].blank?
257
202
  end
258
203
 
259
- def add_identification_or_cardtoken_hash(hash, identification_or_cardtoken)
260
- if identification_or_cardtoken.is_a?(LitleCardToken)
261
- add_cardtoken_hash(hash, identification_or_cardtoken)
262
- else
263
- transaction_id, kind = split_authorization(identification_or_cardtoken)
264
- hash['litleTxnId'] = transaction_id
265
- end
204
+ def exp_date(payment_method)
205
+ "#{format(payment_method.month, :two_digits)}#{format(payment_method.year, :two_digits)}"
266
206
  end
267
207
 
268
- def add_cardtoken_hash(hash, cardtoken)
269
- token_info = {}
270
- token_info['litleToken'] = cardtoken.token
271
- token_info['expDate'] = cardtoken.exp_date if cardtoken.exp_date?
272
- token_info['cardValidationNum'] = cardtoken.verification_value unless cardtoken.verification_value.blank?
273
- token_info['type'] = cardtoken.type unless cardtoken.type.blank?
208
+ def parse(kind, xml)
209
+ parsed = {}
210
+
211
+ doc = Nokogiri::XML(xml).remove_namespaces!
212
+ doc.xpath("//litleOnlineResponse/#{kind}Response/*").each do |node|
213
+ if (node.elements.empty?)
214
+ parsed[node.name.to_sym] = node.text
215
+ else
216
+ node.elements.each do |childnode|
217
+ name = "#{node.name}_#{childnode.name}"
218
+ parsed[name.to_sym] = childnode.text
219
+ end
220
+ end
221
+ end
274
222
 
275
- hash['token'] = token_info
276
- hash
223
+ parsed
277
224
  end
278
225
 
279
- def add_creditcard_hash(hash, creditcard)
280
- cc_type = CARD_TYPE[creditcard.brand]
281
- exp_date_yr = creditcard.year.to_s[2..3]
282
- exp_date_mo = '%02d' % creditcard.month.to_i
283
- exp_date = exp_date_mo + exp_date_yr
284
-
285
- card_info = {
286
- 'type' => cc_type,
287
- 'number' => creditcard.number,
288
- 'expDate' => exp_date,
289
- 'cardValidationNum' => creditcard.verification_value
226
+ def commit(kind, request)
227
+ parsed = parse(kind, ssl_post(url, request, headers))
228
+
229
+ options = {
230
+ authorization: authorization_from(kind, parsed),
231
+ test: test?,
232
+ :avs_result => { :code => AVS_RESPONSE_CODE[parsed[:fraudResult_avsResult]] },
233
+ :cvv_result => parsed[:fraudResult_cardValidationResult]
290
234
  }
291
235
 
292
- hash['card'] = card_info
293
- hash
236
+ Response.new(success_from(kind, parsed), parsed[:message], parsed, options)
294
237
  end
295
238
 
296
- def create_capture_hash(money, authorization, options={})
297
- hash = create_hash(money, options)
298
- hash['partial'] = true if money
299
- hash['litleTxnId'] = authorization
300
- hash
239
+ def success_from(kind, parsed)
240
+ return (parsed[:response] == '000') unless kind == :registerToken
241
+ %w(000 801 802).include?(parsed[:response])
301
242
  end
302
243
 
303
- def create_token_hash(creditcard_or_paypage_registration_id, options)
304
- hash = create_hash(0, options)
305
-
306
- if creditcard_or_paypage_registration_id.is_a?(String)
307
- hash['paypageRegistrationId'] = creditcard_or_paypage_registration_id
308
- else
309
- hash['accountNumber'] = creditcard_or_paypage_registration_id.number
310
- end
311
-
312
- hash
244
+ def authorization_from(kind, parsed)
245
+ (kind == :registerToken) ? parsed[:litleToken] : "#{parsed[:litleTxnId]};#{kind}"
313
246
  end
314
247
 
315
- def create_void_hash(identification, options)
316
- hash = create_hash(nil, options)
317
- hash['litleTxnId'] = identification
318
- hash
248
+ def split_authorization(authorization)
249
+ transaction_id, kind = authorization.to_s.split(';')
250
+ [transaction_id, kind]
319
251
  end
320
252
 
321
- def create_auth_reversal_hash(identification, money, options)
322
- hash = create_hash(money, options)
323
- hash['litleTxnId'] = identification
324
- hash
253
+ def transaction_attributes(options)
254
+ attributes = {}
255
+ attributes[:id] = truncated(options[:id] || options[:order_id])
256
+ attributes[:reportGroup] = options[:merchant] || 'Default Report Group'
257
+ attributes[:customerId] = options[:customer]
258
+ attributes.delete_if { |key, value| value == nil }
259
+ attributes
325
260
  end
326
261
 
327
- def create_hash(money, options)
328
- fraud_check_type = {}
329
- if options[:ip]
330
- fraud_check_type['customerIpAddress'] = options[:ip]
331
- end
332
-
333
- enhanced_data = {}
334
- if options[:invoice]
335
- enhanced_data['invoiceReferenceNumber'] = options[:invoice]
336
- end
337
-
338
- if options[:description]
339
- enhanced_data['customerReference'] = options[:description][0..16]
340
- end
262
+ def root_attributes
263
+ {
264
+ merchantId: @options[:merchant_id],
265
+ version: SCHEMA_VERSION,
266
+ xmlns: "http://www.litle.com/schema"
267
+ }
268
+ end
341
269
 
342
- if options[:billing_address]
343
- bill_to_address = {
344
- 'name' => options[:billing_address][:name],
345
- 'companyName' => options[:billing_address][:company],
346
- 'addressLine1' => options[:billing_address][:address1],
347
- 'addressLine2' => options[:billing_address][:address2],
348
- 'city' => options[:billing_address][:city],
349
- 'state' => options[:billing_address][:state],
350
- 'zip' => options[:billing_address][:zip],
351
- 'country' => options[:billing_address][:country],
352
- 'email' => options[:email],
353
- 'phone' => options[:billing_address][:phone]
354
- }
270
+ def build_xml_request
271
+ builder = Nokogiri::XML::Builder.new
272
+ builder.__send__('litleOnlineRequest', root_attributes) do |doc|
273
+ yield(doc)
355
274
  end
356
- if options[:shipping_address]
357
- ship_to_address = {
358
- 'name' => options[:shipping_address][:name],
359
- 'companyName' => options[:shipping_address][:company],
360
- 'addressLine1' => options[:shipping_address][:address1],
361
- 'addressLine2' => options[:shipping_address][:address2],
362
- 'city' => options[:shipping_address][:city],
363
- 'state' => options[:shipping_address][:state],
364
- 'zip' => options[:shipping_address][:zip],
365
- 'country' => options[:shipping_address][:country],
366
- 'email' => options[:email],
367
- 'phone' => options[:shipping_address][:phone]
368
- }
369
- end
370
-
371
- hash = {
372
- 'billToAddress' => bill_to_address,
373
- 'shipToAddress' => ship_to_address,
374
- 'orderId' => truncated_order_id(options),
375
- 'customerId' => options[:customer],
376
- 'reportGroup' => (options[:merchant] || @options[:merchant]),
377
- 'merchantId' => (options[:merchant_id] || @options[:merchant_id]),
378
- 'orderSource' => (options[:order_source] || 'ecommerce'),
379
- 'enhancedData' => enhanced_data,
380
- 'fraudCheckType' => fraud_check_type,
381
- 'user' => (options[:user] || @options[:user]),
382
- 'password' => (options[:password] || @options[:password]),
383
- 'version' => (options[:version] || @options[:version]),
384
- 'url' => (options[:url] || url),
385
- 'proxy_addr' => (options[:proxy_addr] || @options[:proxy_addr]),
386
- 'proxy_port' => (options[:proxy_port] || @options[:proxy_port]),
387
- 'id' => (options[:id] || options[:order_id] || @options[:order_id])
388
- }
275
+ builder.doc.root.to_xml
276
+ end
389
277
 
390
- hash.merge!('amount' => money) if(money && (money != ""))
278
+ def url
279
+ test? ? test_url : live_url
280
+ end
391
281
 
392
- hash
282
+ def truncated(value)
283
+ return unless value
284
+ value[0..24]
393
285
  end
394
286
 
395
287
  def truncated_order_id(options)
396
- order_id = options[:order_id] || @options[:order_id]
397
- return unless order_id
398
- order_id[0..24]
288
+ return unless options[:order_id]
289
+ options[:order_id][0..24]
399
290
  end
400
291
 
401
- def fraud_result(authorization_response)
402
- if result = authorization_response['fraudResult']
403
- if result.key?('cardValidationResult')
404
- cvv_to_pass = result['cardValidationResult'].blank? ? "P" : result['cardValidationResult']
405
- end
406
-
407
- avs_to_pass = AVS_RESPONSE_CODE[result['avsResult']] unless result['avsResult'].blank?
408
- end
409
- { 'cvv' => cvv_to_pass, 'avs' => avs_to_pass }
292
+ def truncated_id(options)
293
+ return unless options[:id]
294
+ options[:id][0..24]
410
295
  end
411
296
 
412
- # A +LitleCardToken+ object represents a tokenized credit card, and is capable of validating the various
413
- # data associated with these.
414
- #
415
- # == Example Usage
416
- # token = LitleCardToken.new(
417
- # :token => '1234567890123456',
418
- # :month => '9',
419
- # :year => '2010',
420
- # :brand => 'visa',
421
- # :verification_value => '123'
422
- # )
423
- #
424
- # token.valid? # => true
425
- # cc.exp_date # => 0910
426
- #
427
- class LitleCardToken
428
- include Validateable
429
-
430
- # Returns or sets the token. (required)
431
- #
432
- # @return [String]
433
- attr_accessor :token
434
-
435
- # Returns or sets the expiry month for the card associated with token. (optional)
436
- #
437
- # @return [Integer]
438
- attr_accessor :month
439
-
440
- # Returns or sets the expiry year for the card associated with token. (optional)
441
- #
442
- # @return [Integer]
443
- attr_accessor :year
444
-
445
- # Returns or sets the card verification value. (optional)
446
- #
447
- # @return [String] the verification value
448
- attr_accessor :verification_value
449
-
450
- # Returns or sets the credit card brand. (optional)
451
- #
452
- # Valid card types are
453
- #
454
- # * +'visa'+
455
- # * +'master'+
456
- # * +'discover'+
457
- # * +'american_express'+
458
- # * +'diners_club'+
459
- # * +'jcb'+
460
- # * +'switch'+
461
- # * +'solo'+
462
- # * +'dankort'+
463
- # * +'maestro'+
464
- # * +'forbrugsforeningen'+
465
- # * +'laser'+
466
- #
467
- # @return (String) the credit card brand
468
- attr_accessor :brand
469
-
470
- # Returns the Litle credit card type identifier.
471
- #
472
- # @return (String) the credit card type identifier
473
- def type
474
- CARD_TYPE[brand] unless brand.blank?
475
- end
476
-
477
- # Returns true if the expiration date is set.
478
- #
479
- # @return (Boolean)
480
- def exp_date?
481
- !month.to_i.zero? && !year.to_i.zero?
482
- end
483
-
484
- # Returns the card token expiration date in MMYY format.
485
- #
486
- # @return (String) the expiration date in MMYY format
487
- def exp_date
488
- result = ''
489
- if exp_date?
490
- exp_date_yr = year.to_s[2..3]
491
- exp_date_mo = '%02d' % month.to_i
492
-
493
- result = exp_date_mo + exp_date_yr
494
- end
495
- result
496
- end
497
-
498
- # Validates the card token details.
499
- #
500
- # Any validation errors are added to the {#errors} attribute.
501
- def validate
502
- validate_card_token
503
- validate_expiration_date
504
- validate_card_brand
505
- end
506
-
507
- def check?
508
- false
509
- end
510
-
511
- private
512
-
513
- CARD_TYPE = {
514
- 'visa' => 'VI',
515
- 'master' => 'MC',
516
- 'american_express' => 'AX',
517
- 'discover' => 'DI',
518
- 'jcb' => 'DI',
519
- 'diners_club' => 'DI'
297
+ def headers
298
+ {
299
+ 'Content-Type' => 'text/xml'
520
300
  }
521
-
522
- def before_validate #:nodoc:
523
- self.month = month.to_i
524
- self.year = year.to_i
525
- end
526
-
527
- # Litle XML Reference Guide 1.8.2
528
- #
529
- # The length of the original card number is reflected in the token, so a
530
- # submitted 16-digit number results in a 16-digit token. Also, all tokens
531
- # use only numeric characters, so you do not have to change your
532
- # systems to accept alpha-numeric characters.
533
- #
534
- # The credit card token numbers themselves have two parts.
535
- # The last four digits match the last four digits of the card number.
536
- # The remaining digits (length can vary based upon original card number
537
- # length) are a randomly generated.
538
- def validate_card_token #:nodoc:
539
- if token.to_s.length < 12 || token.to_s.match(/\A\d+\Z/).nil?
540
- errors.add :token, "is not a valid card token"
541
- end
542
- end
543
-
544
- def validate_expiration_date #:nodoc:
545
- if !month.to_i.zero? || !year.to_i.zero?
546
- errors.add :month, "is not a valid month" unless valid_month?(month)
547
- errors.add :year, "is not a valid year" unless valid_expiry_year?(year)
548
- end
549
- end
550
-
551
- def validate_card_brand #:nodoc:
552
- errors.add :brand, "is invalid" unless brand.blank? || CreditCard.card_companies.keys.include?(brand)
553
- end
554
-
555
- def valid_month?(month)
556
- (1..12).include?(month.to_i)
557
- end
558
-
559
- def valid_expiry_year?(year)
560
- year.to_s =~ /\A\d{4}\Z/ && year.to_i > 1987
561
- end
562
301
  end
302
+
563
303
  end
564
304
  end
565
305
  end