activemerchant-realex3ds 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,60 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ activemerchant-realex3ds (1.0.0)
5
+ activemerchant (~> 1.34)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actionmailer (2.3.18)
11
+ actionpack (= 2.3.18)
12
+ actionpack (2.3.18)
13
+ activesupport (= 2.3.18)
14
+ rack (~> 1.1.0)
15
+ active_utils (1.0.5)
16
+ activesupport (>= 2.3.11)
17
+ i18n
18
+ activemerchant (1.34.1)
19
+ active_utils (>= 1.0.2)
20
+ activesupport (>= 2.3.14)
21
+ builder (>= 2.0.0)
22
+ i18n
23
+ json (>= 1.5.1)
24
+ money
25
+ nokogiri (< 1.6.0)
26
+ activerecord (2.3.18)
27
+ activesupport (= 2.3.18)
28
+ activeresource (2.3.18)
29
+ activesupport (= 2.3.18)
30
+ activesupport (2.3.18)
31
+ builder (3.2.2)
32
+ equivalent-xml (0.3.0)
33
+ nokogiri (>= 1.4.3)
34
+ i18n (0.6.4)
35
+ json (1.8.0)
36
+ metaclass (0.0.1)
37
+ mocha (0.13.3)
38
+ metaclass (~> 0.0.1)
39
+ money (5.1.1)
40
+ i18n (~> 0.6.0)
41
+ nokogiri (1.5.10)
42
+ rack (1.1.6)
43
+ rails (2.3.18)
44
+ actionmailer (= 2.3.18)
45
+ actionpack (= 2.3.18)
46
+ activerecord (= 2.3.18)
47
+ activeresource (= 2.3.18)
48
+ activesupport (= 2.3.18)
49
+ rake (>= 0.8.3)
50
+ rake (10.1.0)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ activemerchant-realex3ds!
57
+ equivalent-xml
58
+ mocha (~> 0.13.0)
59
+ rails (>= 2.3.14)
60
+ rake
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005-2010 Tobias Luetke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ Realex 3D Secure Gateway for ActiveMerchant
2
+ ===========================================
3
+
4
+ The Gateway included in ActiveMerchant does not support 3D Secure. This implementation was done by David Rice and sponsored by [Ticketsolve](http://ticketsolve.com).
5
+
6
+ In your Gemfile
7
+
8
+ ```ruby
9
+ gem 'activemerchant-realex3ds'
10
+ ```
11
+
12
+ In your code
13
+
14
+ ```ruby
15
+ require 'active_merchant'
16
+ require 'active_merchant/billing/gateways/realex3ds'
17
+
18
+ gateway = ActiveMerchant::Billing::Realex3dsGateway.new(
19
+ :login => 'TestMerchant',
20
+ :password => 'password')
21
+ ```
22
+
23
+ See [ActiveMerchant](http://github.com/Shopify/active_merchant) for more details.
24
+
25
+ Ported to current version of ActiveMerchant and extracted into a Gem by Arne Brasseur.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ $:.unshift File.expand_path('../lib', __FILE__)
2
+
3
+ begin
4
+ require 'bundler'
5
+ Bundler.setup
6
+ rescue LoadError => e
7
+ puts "Error loading bundler (#{e.message}): \"gem install bundler\" for bundler support."
8
+ require 'rubygems'
9
+ end
10
+
11
+ require 'rake'
12
+ require 'rake/testtask'
13
+ require 'rubygems/package_task'
14
+ # require 'support/gateway_support'
15
+ # require 'support/ssl_verify'
16
+ # require 'support/outbound_hosts'
17
+
18
+ desc "Run the unit test suite"
19
+ task :default => 'test:units'
20
+
21
+ task :test => 'test:units'
22
+
23
+ namespace :test do
24
+
25
+ Rake::TestTask.new(:units) do |t|
26
+ t.pattern = 'test/unit/**/*_test.rb'
27
+ t.ruby_opts << '-rubygems'
28
+ t.libs << 'test'
29
+ t.verbose = true
30
+ end
31
+
32
+ Rake::TestTask.new(:remote) do |t|
33
+ t.pattern = 'test/remote/**/*_test.rb'
34
+ t.ruby_opts << '-rubygems'
35
+ t.libs << 'test'
36
+ t.verbose = true
37
+ end
38
+
39
+ end
40
+
41
+ desc "Delete tar.gz / zip"
42
+ task :cleanup => [ :clobber_package ]
43
+
44
+ spec = eval(File.read('activemerchant-realex3ds.gemspec'))
45
+
46
+ Gem::PackageTask.new(spec) do |p|
47
+ p.gem_spec = spec
48
+ end
49
+
50
+ desc "Release the gems and docs to RubyForge"
51
+ task :release => [ 'gemcutter:publish' ]
52
+
53
+ namespace :gemcutter do
54
+ desc "Publish to gemcutter"
55
+ task :publish => :package do
56
+ sh "gem push pkg/activemerchant-realex3ds-#{spec.version}.gem"
57
+ end
58
+ end
@@ -0,0 +1,28 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'rubygems'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.platform = Gem::Platform::RUBY
7
+ s.name = 'activemerchant-realex3ds'
8
+ s.version = '1.0.0'
9
+ s.summary = 'Realex gateway for ActiveMerchant with 3D Secure support'
10
+ s.description = 'Realex is the leading payment provider for Ireland. The default gateway included in ActiveMerchant does not support 3D Secure. This implementation does, it was sponsored by Ticketsolve, written by David Rice, and released as a gem for the current version of ActiveMerchant by Arne Brasseur.'
11
+
12
+ s.authors = ['David Rice', 'Arne Brasseur']
13
+ s.email = 'arne@arnebrasseur.net'
14
+ s.homepage = 'https://github.com/plexus/active_merchant-realex3ds'
15
+ s.rubyforge_project = 'activemerchant-realex3ds'
16
+
17
+ s.require_paths = %w[lib]
18
+ s.files = `git ls-files`.split($/)
19
+ s.test_files = `git ls-files -- spec`.split($/)
20
+ s.extra_rdoc_files = %w[README.md]
21
+
22
+ s.add_dependency('activemerchant', '~> 1.34')
23
+
24
+ s.add_development_dependency('rake')
25
+ s.add_development_dependency('mocha', '~> 0.13.0')
26
+ s.add_development_dependency('rails', '>= 2.3.14')
27
+ s.add_development_dependency('equivalent-xml')
28
+ end
@@ -0,0 +1,610 @@
1
+ require 'rexml/document'
2
+ require 'digest/sha1'
3
+
4
+ module ActiveMerchant
5
+ module Billing
6
+ # For more information on the Realex Payment Gateway visit their site {realexpayments.com}[http://realexpayments.com].
7
+ # Realex is the leading gateway in Ireland
8
+ #
9
+ # === Merchant ID and Password
10
+ #
11
+ # To be able to use this library you will need to obtain an account from Realex, you can find contact them
12
+ # via their website.
13
+ #
14
+ # === Caveats
15
+ #
16
+ # Realex requires that you specify the account to which your transactions are made.
17
+ #
18
+ # gateway = ActiveMerchant::Billing::Realex3dsGateway.new(:login => 'xxx', :password => 'xxx', :acction => 'xxx')
19
+ #
20
+ # If you wish to accept multiple currencies, you need to create an account per currency.
21
+ # This you would need to handle within your application logic.
22
+ # Again, contact Realex for more information.
23
+ #
24
+ # They also require accepting payment from a Diners card (Mastercard) go through a different account.
25
+ #
26
+ # Realex also requires that you send several (extra) required identifiers with credit and void methods
27
+ #
28
+ # * order_id
29
+ # * pasref
30
+ # * authorization
31
+ #
32
+ # The pasref can be accessed from the response params. i.e.
33
+ # response.params['pasref']
34
+ #
35
+ # === Testing
36
+ #
37
+ # Realex provide test card numbers on a per-account basis, you will need to request these.
38
+ # Then if you copy the fixtures file that comes with this library to ~/.active_merchant/fixtures.yml
39
+ # you can add in the required card number (and account) fixtures.
40
+ #
41
+ class Realex3dsGateway < Gateway
42
+ URL = 'https://epage.payandshop.com/epage-remote.cgi'
43
+ THREE_D_SECURE_URL = 'https://epage.payandshop.com/epage-3dsecure.cgi'
44
+ RECURRING_PAYMENTS_URL = "https://epage.payandshop.com/epage-remote-plugins.cgi"
45
+
46
+ CARD_MAPPING = {
47
+ 'master' => 'MC',
48
+ 'visa' => 'VISA',
49
+ 'visa_delta' => 'VISA',
50
+ 'visa_electron' => 'VISA',
51
+ 'american_express' => 'AMEX',
52
+ 'diners_club' => 'DINERS',
53
+ 'switch' => 'SWITCH',
54
+ 'solo' => 'SWITCH',
55
+ 'laser' => 'LASER'
56
+ }
57
+
58
+ self.money_format = :cents
59
+ self.default_currency = 'EUR'
60
+ self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :switch, :solo, :laser ]
61
+ self.supported_countries = [ 'IE', 'GB' ]
62
+ self.homepage_url = 'http://www.realexpayments.com/'
63
+ self.display_name = 'Realex'
64
+
65
+ SUCCESS, DECLINED = "Successful", "Declined"
66
+ BANK_ERROR = REALEX_ERROR = "Gateway is in maintenance. Please try again later."
67
+ ERROR = CLIENT_DEACTIVATED = "Gateway Error"
68
+
69
+ def initialize(options = {})
70
+ requires!(options, :login, :password)
71
+ options[:refund_hash] = Digest::SHA1.hexdigest(options[:rebate_secret]) if options.has_key?(:rebate_secret)
72
+ @options = options
73
+ super
74
+ end
75
+
76
+ # Performs an authorization, which reserves the funds on the customer's credit card, but does not
77
+ # charge the card.
78
+ #
79
+ # ==== Parameters
80
+ #
81
+ # * <tt>money</tt> -- The amount to be authorized. Either an Integer value in cents or a Money object.
82
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
83
+ # * <tt>options</tt> -- A hash of optional parameters.
84
+ #
85
+ # ==== Options
86
+ #
87
+ # * <tt>:order_id</tt> -- The application generated order identifier. (REQUIRED)
88
+ #
89
+ def authorize(money, creditcard, options = {})
90
+ requires!(options, :order_id)
91
+
92
+ if options[:three_d_secure]
93
+ three_d_secure_request = build_3d_secure_verify_signature_or_enrolled_request("3ds-verifyenrolled", money, creditcard, options)
94
+ three_d_secure_response = commit(three_d_secure_request, :three_d_secure)
95
+ return three_d_secure_response if three_d_secure_response.enrolled?
96
+ end
97
+
98
+ request = build_purchase_or_authorization_request(:authorization, money, creditcard, options)
99
+ commit(request)
100
+ end
101
+
102
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
103
+ #
104
+ # ==== Parameters
105
+ #
106
+ # * <tt>money</tt> -- The amount to be purchased. Either an Integer value in cents or a Money object.
107
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
108
+ # * <tt>options</tt> -- A hash of optional parameters.
109
+ #
110
+ # ==== Options
111
+ #
112
+ # * <tt>:order_id</tt> -- The application generated order identifier. (REQUIRED)
113
+ #
114
+ def purchase(money, creditcard, options = {})
115
+ requires!(options, :order_id)
116
+
117
+ if options[:three_d_secure_auth]
118
+ three_d_secure_request = build_3d_secure_verify_signature_or_enrolled_request("3ds-verifysig", money, creditcard, options)
119
+ three_d_secure_response = commit(three_d_secure_request, :three_d_secure)
120
+ result = three_d_secure_response.params['result']
121
+ if result == '00'
122
+ status = three_d_secure_response.params['threedsecure_status']
123
+ # success
124
+ if status == 'Y' || status == 'A'
125
+ # Y: 3d Secure complete.
126
+ # A: ACS service aknowledges.
127
+ # not-liable. continue
128
+ options[:three_d_secure_sig] = {}
129
+ options[:three_d_secure_sig][:eci] = three_d_secure_response.params['threedsecure_eci']
130
+ options[:three_d_secure_sig][:xid] = three_d_secure_response.params['threedsecure_xid']
131
+ options[:three_d_secure_sig][:cavv] = three_d_secure_response.params['threedsecure_cavv']
132
+ # TODO add option[:accept_liability_authentication_failed]
133
+ # TODO add option[:accept_liability_acs_failure]
134
+
135
+ elsif status == 'N'
136
+ # password entered incorrectly
137
+ # liable. abort?
138
+ return Response.new(false, "3DSecure password entered incorrectly. Aborting transaction.",{},{})
139
+ elsif status == 'U'
140
+ # Bank ACS service having dificulty.
141
+ # liable. abort?
142
+ return Response.new(false, "3DSecure Bank ACS service 500 errors. Aborting transaction.",{},{})
143
+ end
144
+ elsif result == "110"
145
+ # fail, message tampered with.
146
+ return Response.new(false, "3DSecure message tampered. Aborting transaction.",{},{})
147
+ else
148
+ return Response.new(false, "Unknown 3DSecure Error.",{},{})
149
+ end
150
+ end
151
+
152
+ if options[:three_d_secure] && !options[:three_d_secure_auth]
153
+ three_d_secure_request = build_3d_secure_verify_signature_or_enrolled_request("3ds-verifyenrolled", money, creditcard, options)
154
+ three_d_secure_response = commit(three_d_secure_request, :three_d_secure)
155
+ return three_d_secure_response if three_d_secure_response.enrolled?
156
+ end
157
+
158
+ request = build_purchase_or_authorization_request(:purchase, money, creditcard, options)
159
+ commit(request)
160
+ end
161
+
162
+ # Captures the funds from an authorized transaction.
163
+ #
164
+ # ==== Parameters
165
+ #
166
+ # * <tt>money</tt> -- The amount to be captured. Either an Integer value in cents or a Money object.
167
+ # * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
168
+ #
169
+ # ==== Options
170
+ #
171
+ # * <tt>:order_id</tt> -- The application generated order identifier. (REQUIRED)
172
+ # * <tt>:pasref</tt> -- The realex payments reference of the original transaction. (REQUIRED)
173
+ #
174
+ def capture(money, authorization, options = {})
175
+ requires!(options, :pasref)
176
+ requires!(options, :order_id)
177
+
178
+ request = build_capture_request(authorization, options)
179
+ commit(request)
180
+ end
181
+
182
+ # Credit an account.
183
+ #
184
+ # This transaction is also referred to as a Refund (or Rebate) and indicates to the gateway that
185
+ # money should flow from the merchant to the customer.
186
+ #
187
+ # ==== Parameters
188
+ #
189
+ # * <tt>money</tt> -- The amount to be credited to the customer. Either an Integer value in cents or a Money object.
190
+ # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
191
+ # * <tt>options</tt> -- A hash of parameters.
192
+ #
193
+ # ==== Options
194
+ #
195
+ # * <tt>:order_id</tt> -- The application generated order identifier. (REQUIRED)
196
+ # * <tt>:pasref</tt> -- The realex payments reference of the original transaction. (REQUIRED)
197
+ #
198
+ def credit(money, authorization, options = {})
199
+ requires!(options, :order_id)
200
+ requires!(options, :pasref)
201
+
202
+ request = build_credit_request(money, authorization, options)
203
+ commit(request)
204
+ end
205
+
206
+ # Void a previous transaction
207
+ #
208
+ # ==== Parameters
209
+ #
210
+ # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
211
+ #
212
+ # ==== Options
213
+ #
214
+ # * <tt>:order_id</tt> -- The application generated order identifier. (REQUIRED)
215
+ # * <tt>:pasref</tt> -- The realex payments reference of the original transaction. (REQUIRED)
216
+ #
217
+ def void(authorization, options = {})
218
+ requires!(options, :order_id)
219
+ requires!(options, :pasref)
220
+
221
+ request = build_void_request(authorization, options)
222
+ commit(request)
223
+ end
224
+
225
+ # Recurring Payments
226
+
227
+ def recurring(money, credit_card, options = {})
228
+ requires!(options, :order_id)
229
+
230
+ request = build_receipt_in_request(money, credit_card, options)
231
+ commit(request, :recurring)
232
+ end
233
+
234
+ def store(credit_card, options = {})
235
+ requires!(options, :order_id)
236
+ request = build_new_card_request(credit_card, options)
237
+ commit(request, :recurring)
238
+ end
239
+
240
+ def unstore(creditcard, options = {})
241
+ request = build_cancel_card_request(creditcard, options)
242
+ commit(request, :recurring)
243
+ end
244
+
245
+ def store_user(options = {})
246
+ requires!(options, :order_id)
247
+ request = build_new_payee_request(options)
248
+ commit(request, :recurring)
249
+ end
250
+
251
+ private
252
+ def commit(request, endpoint=:default)
253
+ url = URL
254
+ url = THREE_D_SECURE_URL if endpoint == :three_d_secure
255
+ url = RECURRING_PAYMENTS_URL if endpoint == :recurring
256
+
257
+ response = ssl_post(url, request)
258
+ parsed = parse(response)
259
+
260
+ options = {
261
+ :test => parsed[:message] =~ /\[ test system \]/,
262
+ :authorization => parsed[:authcode],
263
+ :cvv_result => parsed[:cvnresult],
264
+ :body => response,
265
+ :avs_result => {
266
+ :street_match => parsed[:avsaddressresponse],
267
+ :postal_match => parsed[:avspostcoderesponse]
268
+ }
269
+ }
270
+
271
+ if endpoint == :three_d_secure
272
+ options.merge!({
273
+ :pa_req => parsed[:pareq],
274
+ :acs_url => parsed[:url],
275
+ :three_d_secure => true,
276
+ :xid => parsed[:xid],
277
+ :three_d_secure_enrolled => parsed[:enrolled] == "Y" ? true : false
278
+ })
279
+ end
280
+
281
+ Response.new(parsed[:result] == "00", message_from(parsed), parsed, options)
282
+ end
283
+
284
+ def parse(xml)
285
+ response = {}
286
+
287
+ xml = REXML::Document.new(xml)
288
+
289
+ return response unless xml.root
290
+
291
+ xml.elements.each('//response/*') do |node|
292
+
293
+ if (node.elements.size == 0)
294
+ response[node.name.downcase.to_sym] = normalize(node.text)
295
+ else
296
+ node.elements.each do |childnode|
297
+ name = "#{node.name.downcase}_#{childnode.name.downcase}"
298
+ response[name.to_sym] = normalize(childnode.text)
299
+ end
300
+ end
301
+
302
+ end
303
+
304
+ response
305
+ end
306
+
307
+ def build_purchase_or_authorization_request(action, money, credit_card, options)
308
+ timestamp = self.class.timestamp
309
+ xml = Builder::XmlMarkup.new :indent => 2
310
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'auth' do
311
+ add_merchant_details(xml, options)
312
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
313
+ add_ammount(xml, money, options)
314
+ add_card(xml, credit_card)
315
+ xml.tag! 'autosettle', 'flag' => auto_settle_flag(action)
316
+ add_three_d_secure(xml, options) if options[:three_d_secure_sig]
317
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money)), credit_card.number)
318
+ add_comments(xml, options)
319
+ add_address_and_customer_info(xml, options)
320
+ end
321
+ xml.target!
322
+ end
323
+
324
+ def build_capture_request(authorization, options)
325
+ timestamp = self.class.timestamp
326
+ xml = Builder::XmlMarkup.new :indent => 2
327
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'settle' do
328
+ add_merchant_details(xml, options)
329
+ add_transaction_identifiers(xml, authorization, options)
330
+ add_comments(xml, options)
331
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], '', '', '')
332
+ end
333
+ xml.target!
334
+ end
335
+
336
+ def build_credit_request(money, authorization, options)
337
+ timestamp = self.class.timestamp
338
+ xml = Builder::XmlMarkup.new :indent => 2
339
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'rebate' do
340
+ add_merchant_details(xml, options)
341
+ add_transaction_identifiers(xml, authorization, options)
342
+ xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money)
343
+ xml.tag! 'refundhash', @options[:refund_hash] if @options[:refund_hash]
344
+ xml.tag! 'autosettle', 'flag' => 1
345
+ add_comments(xml, options)
346
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money)), '')
347
+ end
348
+ xml.target!
349
+ end
350
+
351
+ def build_void_request(authorization, options)
352
+ timestamp = self.class.timestamp
353
+ xml = Builder::XmlMarkup.new :indent => 2
354
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'void' do
355
+ add_merchant_details(xml, options)
356
+ add_transaction_identifiers(xml, authorization, options)
357
+ add_comments(xml, options)
358
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], '', '', '')
359
+ end
360
+ xml.target!
361
+ end
362
+
363
+ def build_cancel_card_request(creditcard, options = {})
364
+ timestamp = self.class.timestamp
365
+ xml = Builder::XmlMarkup.new :indent => 2
366
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'card-cancel-card' do
367
+ add_merchant_details(xml, options)
368
+ xml.tag! 'card' do
369
+ xml.tag! 'ref', options[:payment_method]
370
+ xml.tag! 'payerref', options[:user][:id]
371
+ xml.tag! 'expdate', expiry_date(creditcard)
372
+ end
373
+ # TODO userid . card ref . expiry date
374
+ add_signed_digest(xml, timestamp, @options[:login], options[:user][:id], options[:payment_method])
375
+ end
376
+ end
377
+
378
+ def build_new_card_request(credit_card, options = {})
379
+ timestamp = self.class.timestamp
380
+ xml = Builder::XmlMarkup.new :indent => 2
381
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'card-new' do
382
+ add_merchant_details(xml, options)
383
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
384
+ xml.tag! 'card' do
385
+ xml.tag! 'ref', options[:payment_method]
386
+ xml.tag! 'payerref', options[:user][:id]
387
+ xml.tag! 'number', credit_card.number
388
+ xml.tag! 'expdate', expiry_date(credit_card)
389
+ xml.tag! 'chname', credit_card.name
390
+ xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s]
391
+ xml.tag! 'issueno', credit_card.issue_number
392
+ xml.tag! 'cvn' do
393
+ xml.tag! 'number', credit_card.verification_value
394
+ xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil))
395
+ end
396
+ end
397
+ # timestamp.merchantid.orderid.amount.currency.payerref.chname.(card)number
398
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], '', '', options[:user][:id], credit_card.name, credit_card.number)
399
+ end
400
+ xml.target!
401
+ end
402
+
403
+ def build_new_payee_request(options)
404
+ timestamp = self.class.timestamp
405
+ xml = Builder::XmlMarkup.new :indent => 2
406
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'payer-new' do
407
+ add_merchant_details(xml, options)
408
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
409
+ xml.tag! 'payer', 'type' => 'Business', 'ref' => options[:user][:id] do
410
+ xml.tag! 'firstname', options[:user][:first_name]
411
+ xml.tag! 'surname', options[:user][:last_name]
412
+ end
413
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], '', '', options[:user][:id])
414
+ end
415
+ xml.target!
416
+ end
417
+
418
+ def build_3d_secure_verify_signature_or_enrolled_request(action, money, credit_card, options)
419
+ timestamp = self.class.timestamp
420
+ xml = Builder::XmlMarkup.new :indent => 2
421
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => action do
422
+ add_merchant_details(xml, options)
423
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
424
+ add_ammount(xml, money, options)
425
+ add_card(xml, credit_card)
426
+ xml.tag!('pares', options[:three_d_secure_auth][:pa_res]) if(action == '3ds-verifysig' && options[:three_d_secure_auth] )
427
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money)), credit_card.number)
428
+ add_comments(xml, options)
429
+ end
430
+ xml.target!
431
+ end
432
+
433
+ def add_three_d_secure(xml, options)
434
+ if options[:three_d_secure_sig]
435
+ xml.tag! 'mpi' do
436
+ xml.tag! 'cavv', options[:three_d_secure_sig][:cavv]
437
+ xml.tag! 'xid', options[:three_d_secure_sig][:xid]
438
+ xml.tag! 'eci', options[:three_d_secure_sig][:eci]
439
+ end
440
+ end
441
+ end
442
+
443
+ def build_receipt_in_request(money, credit_card, options)
444
+ timestamp = self.class.timestamp
445
+ xml = Builder::XmlMarkup.new :indent => 2
446
+ xml.tag! 'request', 'timestamp' => timestamp, 'type' => 'receipt-in' do
447
+ add_merchant_details(xml, options)
448
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
449
+ add_ammount(xml, money, options)
450
+ xml.tag! 'payerref', options[:user][:id]
451
+ xml.tag! 'paymentmethod', options[:payment_method]
452
+ xml.tag! 'autosettle', 'flag' => '1'
453
+ add_signed_digest(xml, timestamp, @options[:login], options[:order_id], amount(money), (options[:currency] || currency(money)), options[:user][:id])
454
+ add_comments(xml, options)
455
+ add_address_and_customer_info(xml, options)
456
+ end
457
+ xml.target!
458
+ end
459
+
460
+ def add_address_and_customer_info(xml, options)
461
+ billing_address = options[:billing_address] || options[:address]
462
+ shipping_address = options[:shipping_address]
463
+
464
+ return unless billing_address || shipping_address || options[:customer] || options[:invoice] || options[:ip]
465
+
466
+ xml.tag! 'tssinfo' do
467
+
468
+ xml.tag! 'custnum', options[:customer] if options[:customer]
469
+ xml.tag! 'prodid', options[:invoice] if options[:invoice]
470
+ xml.tag! 'custipaddress', options[:ip] if options[:ip]
471
+ # xml.tag! 'varref'
472
+
473
+ if billing_address
474
+ xml.tag! 'address', 'type' => 'billing' do
475
+ xml.tag! 'code', avs_input_code_or_zip( billing_address, options )
476
+ xml.tag! 'country', billing_address[:country]
477
+ end
478
+ end
479
+
480
+ if shipping_address
481
+ xml.tag! 'address', 'type' => 'shipping' do
482
+ xml.tag! 'code', shipping_address[:zip]
483
+ xml.tag! 'country', shipping_address[:country]
484
+ end
485
+ end
486
+
487
+ end
488
+ end
489
+
490
+ def avs_input_code_or_zip(address, options)
491
+ options[ :skip_avs_check ] ? address[ :zip ] : avs_input_code( address )
492
+ end
493
+
494
+ def add_merchant_details(xml, options)
495
+ xml.tag! 'merchantid', @options[:login]
496
+ if options[:account] || @options[:account]
497
+ xml.tag! 'account', options[:account] || @options[:account]
498
+ end
499
+ end
500
+
501
+ def add_transaction_identifiers(xml, authorization, options)
502
+ xml.tag! 'orderid', sanitize_order_id(options[:order_id])
503
+ xml.tag! 'pasref', options[:pasref]
504
+ xml.tag! 'authcode', authorization
505
+ end
506
+
507
+ def add_comments(xml, options)
508
+ return unless options[:description]
509
+ xml.tag! 'comments' do
510
+ xml.tag! 'comment', options[:description], 'id' => 1
511
+ end
512
+ end
513
+
514
+ def add_ammount(xml, money, options)
515
+ xml.tag! 'amount', amount(money), 'currency' => options[:currency] || currency(money)
516
+ end
517
+
518
+ def add_card(xml, credit_card)
519
+ xml.tag! 'card' do
520
+ xml.tag! 'number', credit_card.number
521
+ xml.tag! 'expdate', expiry_date(credit_card)
522
+ xml.tag! 'chname', credit_card.name
523
+ xml.tag! 'type', CARD_MAPPING[card_brand(credit_card).to_s]
524
+ xml.tag! 'issueno', credit_card.issue_number
525
+ xml.tag! 'cvn' do
526
+ xml.tag! 'number', credit_card.verification_value
527
+ xml.tag! 'presind', (options['presind'] || (credit_card.verification_value? ? 1 : nil))
528
+ end
529
+ end
530
+ end
531
+
532
+ def avs_input_code(address)
533
+ address.values_at(:zip, :address1).map{ |v| extract_digits(v) }.join('|')
534
+ end
535
+
536
+ def extract_digits(string)
537
+ return "" if string.nil?
538
+ string.gsub(/[\D]/,'')
539
+ end
540
+
541
+ def stringify_values(values)
542
+ string = ""
543
+ values.each do |val|
544
+ string << "#{val}"
545
+ string << "." unless val.equal?(values.last)
546
+ end
547
+ string
548
+ end
549
+
550
+ def add_signed_digest(xml, *values)
551
+ string = stringify_values(values)
552
+ xml.tag! 'sha1hash', sha1from(string)
553
+ end
554
+
555
+ def auto_settle_flag(action)
556
+ action == :authorization ? '0' : '1'
557
+ end
558
+
559
+ def expiry_date(credit_card)
560
+ "#{format(credit_card.month, :two_digits)}#{format(credit_card.year, :two_digits)}"
561
+ end
562
+
563
+ def sha1from(string)
564
+ Digest::SHA1.hexdigest("#{Digest::SHA1.hexdigest(string)}.#{@options[:password]}")
565
+ end
566
+
567
+ def normalize(field)
568
+ case field
569
+ when "true" then true
570
+ when "false" then false
571
+ when "" then nil
572
+ when "null" then nil
573
+ else field
574
+ end
575
+ end
576
+
577
+ def message_from(response)
578
+ message = nil
579
+ case response[:result]
580
+ when "00"
581
+ message = SUCCESS
582
+ when "101"
583
+ message = response[:message]
584
+ when "102", "103"
585
+ message = DECLINED
586
+ when /^2[0-9][0-9]/
587
+ message = BANK_ERROR
588
+ when /^3[0-9][0-9]/
589
+ message = REALEX_ERROR
590
+ when /^5[0-9][0-9]/
591
+ message = response[:message]
592
+ when "600", "601", "603"
593
+ message = ERROR
594
+ when "666"
595
+ message = CLIENT_DEACTIVATED
596
+ else
597
+ message = DECLINED
598
+ end
599
+ end
600
+
601
+ def sanitize_order_id(order_id)
602
+ order_id.to_s.gsub(/[^a-zA-Z0-9\-_]/, '')
603
+ end
604
+
605
+ def self.timestamp
606
+ Time.now.strftime('%Y%m%d%H%M%S')
607
+ end
608
+ end
609
+ end
610
+ end