tomriley-active_merchant 1.4.2.3 → 1.4.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/CHANGELOG +14 -0
  2. data/CONTRIBUTERS +12 -0
  3. data/active_merchant.gemspec +3 -2
  4. data/init.rb +0 -1
  5. data/lib/active_merchant/billing/credit_card_methods.rb +1 -1
  6. data/lib/active_merchant/billing/expiry_date.rb +10 -4
  7. data/lib/active_merchant/billing/gateway.rb +4 -0
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +12 -1
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -1
  10. data/lib/active_merchant/billing/gateways/bogus.rb +19 -0
  11. data/lib/active_merchant/billing/gateways/eway.rb +6 -1
  12. data/lib/active_merchant/billing/gateways/first_pay.rb +172 -0
  13. data/lib/active_merchant/billing/gateways/merchant_ware.rb +283 -0
  14. data/lib/active_merchant/billing/gateways/ogone.rb +259 -0
  15. data/lib/active_merchant/billing/gateways/paypal.rb +22 -9
  16. data/lib/active_merchant/billing/gateways/{protx.rb → sage_pay.rb} +45 -12
  17. data/lib/active_merchant/billing/integrations/nochex/notification.rb +1 -1
  18. data/lib/active_merchant/billing/response.rb +9 -1
  19. data/test/fixtures.yml +16 -1
  20. data/test/remote/gateways/remote_first_pay_test.rb +87 -0
  21. data/test/remote/gateways/remote_merchant_ware_test.rb +113 -0
  22. data/test/remote/gateways/remote_ogone_test.rb +108 -0
  23. data/test/remote/gateways/remote_paypal_test.rb +12 -1
  24. data/test/remote/gateways/remote_protx_three_d_secure_test.rb +259 -0
  25. data/test/remote/gateways/{remote_protx_test.rb → remote_sage_pay_test.rb} +8 -8
  26. data/test/unit/credit_card_methods_test.rb +9 -0
  27. data/test/unit/expiry_date_test.rb +12 -1
  28. data/test/unit/gateways/bogus_test.rb +31 -0
  29. data/test/unit/gateways/first_pay_test.rb +125 -0
  30. data/test/unit/gateways/gateway_test.rb +6 -0
  31. data/test/unit/gateways/merchant_ware_test.rb +188 -0
  32. data/test/unit/gateways/ogone_test.rb +256 -0
  33. data/test/unit/gateways/paypal_test.rb +49 -0
  34. data/test/unit/gateways/sage_pay_test.rb +183 -0
  35. data/test/unit/integrations/notifications/nochex_notification_test.rb +1 -1
  36. data/test/unit/response_test.rb +16 -0
  37. metadata +16 -5
  38. data/test/unit/gateways/protx_test.rb +0 -139
@@ -0,0 +1,259 @@
1
+ require 'rexml/document'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ # = Ogone DirectLink Gateway
6
+ #
7
+ # DirectLink is the API version of the Ogone Payment Platform. It allows server to server
8
+ # communication between Ogone systems and your e-commerce website.
9
+ #
10
+ # This implementation follows the specification provided in the DirectLink integration
11
+ # guide version 2.4 (December 2008), available here:
12
+ # https://secure.ogone.com/ncol/Ogone_DirectLink_EN.pdf
13
+ #
14
+ # It also features aliases, which allow to store/unstore credit cards, as specified in
15
+ # the Alias Manager Option guide version 2.2 available here:
16
+ # https://secure.ogone.com/ncol/Ogone_Alias_EN.pdf
17
+ #
18
+ # It was last tested on Release 04.79 of Ogone e-Commerce (dated 11/02/2009).
19
+ #
20
+ # For any questions or comments, please contact Nicolas Jacobeus (nj@belighted.com).
21
+ #
22
+ # == Example use:
23
+ #
24
+ # gateway = ActiveMerchant::Billing::OgoneGateway.new(
25
+ # :login => "my_ogone_psp_id",
26
+ # :user => "my_ogone_user_id",
27
+ # :password => "my_ogone_pswd",
28
+ # :signature => "my_ogone_sha1_signature" # extra security, only if you configured your Ogone environment so
29
+ # )
30
+ #
31
+ # # set up credit card obj as in main ActiveMerchant example
32
+ # creditcard = ActiveMerchant::Billing::CreditCard.new(
33
+ # :type => 'visa',
34
+ # :number => '4242424242424242',
35
+ # :month => 8,
36
+ # :year => 2009,
37
+ # :first_name => 'Bob',
38
+ # :last_name => 'Bobsen'
39
+ # )
40
+ #
41
+ # # run request
42
+ # response = gateway.purchase(1000, creditcard, :order_id => "1") # charge 10 EUR
43
+ #
44
+ # If you don't provide an :order_id, the gateway will generate a random one for you.
45
+ #
46
+ # puts response.success? # Check whether the transaction was successful
47
+ # puts response.message # Retrieve the message returned by Ogone
48
+ # puts response.authorization # Retrieve the unique transaction ID returned by Ogone
49
+ #
50
+ # To use the alias feature, simply add :alias in the options hash:
51
+ #
52
+ # gateway.purchase(1000, creditcard, :order_id => "1", :alias => "myawesomecustomer") # associates the alias to that creditcard
53
+ # gateway.purchase(2000, nil, :order_id => "2", :alias => "myawesomecustomer") # don't need to know the creditcard for subsequent orders
54
+ #
55
+ class OgoneGateway < Gateway
56
+
57
+ URLS = {
58
+ :test => { :order => 'https://secure.ogone.com/ncol/test/orderdirect.asp',
59
+ :maintenance => 'https://secure.ogone.com/ncol/test/maintenancedirect.asp' },
60
+ :production => { :order => 'https://secure.ogone.com/ncol/prod/orderdirect.asp',
61
+ :maintenance => 'https://secure.ogone.com/ncol/prod/maintenancedirect.asp' }
62
+ }
63
+
64
+ CVV_MAPPING = { 'OK' => 'M',
65
+ 'KO' => 'N',
66
+ 'NO' => 'P' }
67
+
68
+ AVS_MAPPING = { 'OK' => 'M',
69
+ 'KO' => 'N',
70
+ 'NO' => 'R' }
71
+ SUCCESS_MESSAGE = "The transaction was successful"
72
+
73
+ self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH']
74
+ # also supports Airplus and UATP
75
+ self.supported_cardtypes = [:visa, :master, :american_express, :diners_club, :discover, :jcb, :maestro]
76
+ self.homepage_url = 'http://www.ogone.com/'
77
+ self.display_name = 'Ogone'
78
+ self.default_currency = 'EUR'
79
+ self.money_format = :cents
80
+
81
+ def initialize(options = {})
82
+ requires!(options, :login, :user, :password)
83
+ @options = options
84
+ super
85
+ end
86
+
87
+ # Verify and reserve the specified amount on the account, without actually doing the transaction.
88
+ def authorize(money, payment_source, options = {})
89
+ post = {}
90
+ add_invoice(post, options)
91
+ add_payment_source(post, payment_source, options)
92
+ add_address(post, payment_source, options)
93
+ add_customer_data(post, options)
94
+ add_money(post, money, options)
95
+ commit('RES', post)
96
+ end
97
+
98
+ # Verify and transfer the specified amount.
99
+ def purchase(money, payment_source, options = {})
100
+ post = {}
101
+ add_invoice(post, options)
102
+ add_payment_source(post, payment_source, options)
103
+ add_address(post, payment_source, options)
104
+ add_customer_data(post, options)
105
+ add_money(post, money, options)
106
+ commit('SAL', post)
107
+ end
108
+
109
+ # Complete a previously authorized transaction.
110
+ def capture(money, authorization, options = {})
111
+ post = {}
112
+ add_authorization(post, reference_from(authorization))
113
+ add_invoice(post, options)
114
+ add_customer_data(post, options)
115
+ add_money(post, money, options)
116
+ commit('SAL', post)
117
+ end
118
+
119
+ # Cancels a previously authorized transaction.
120
+ def void(identification, options = {})
121
+ post = {}
122
+ add_authorization(post, reference_from(identification))
123
+ commit('DES', post)
124
+ end
125
+
126
+ # Credit the specified account by a specific amount.
127
+ def credit(money, identification_or_credit_card, options = {})
128
+ if reference_transaction?(identification_or_credit_card)
129
+ # Referenced credit: refund of a settled transaction
130
+ perform_reference_credit(money, identification_or_credit_card, options)
131
+ else # must be a credit card or card reference
132
+ perform_non_referenced_credit(money, identification_or_credit_card, options)
133
+ end
134
+ end
135
+
136
+ private
137
+ def reference_from(authorization)
138
+ authorization.split(";").first
139
+ end
140
+
141
+ def reference_transaction?(identifier)
142
+ return false unless identifier.is_a?(String)
143
+ reference, action = identifier.split(";")
144
+ !action.nil?
145
+ end
146
+
147
+ def perform_reference_credit(money, payment_target, options = {})
148
+ post = {}
149
+ add_authorization(post, reference_from(payment_target))
150
+ add_money(post, money, options)
151
+ commit('RFD', post)
152
+ end
153
+
154
+ def perform_non_referenced_credit(money, payment_target, options = {})
155
+ # Non-referenced credit: acts like a reverse purchase
156
+ post = {}
157
+ add_invoice(post, options)
158
+ add_payment_source(post, payment_target, options)
159
+ add_address(post, payment_target, options)
160
+ add_customer_data(post, options)
161
+ add_money(post, money, options)
162
+ commit('RFD', post)
163
+ end
164
+
165
+ def add_payment_source(post, payment_source, options)
166
+ if payment_source.is_a?(String)
167
+ add_alias(post, payment_source)
168
+ add_eci(post, '9')
169
+ else
170
+ add_alias(post, options[:store])
171
+ add_creditcard(post, payment_source)
172
+ end
173
+ end
174
+
175
+ def add_eci(post, eci)
176
+ add_pair post, 'ECI', eci
177
+ end
178
+
179
+ def add_alias(post, _alias)
180
+ add_pair post, 'ALIAS', _alias
181
+ end
182
+
183
+ def add_authorization(post, authorization)
184
+ add_pair post, 'PAYID', authorization
185
+ end
186
+
187
+ def add_money(post, money, options)
188
+ add_pair post, 'currency', options[:currency] || currency(money)
189
+ add_pair post, 'amount', amount(money)
190
+ end
191
+
192
+ def add_customer_data(post, options)
193
+ add_pair post, 'EMAIL', options[:email]
194
+ add_pair post, 'REMOTE_ADDR', options[:ip]
195
+ end
196
+
197
+ def add_address(post, creditcard, options)
198
+ return unless options[:billing_address]
199
+ add_pair post, 'Owneraddress', options[:billing_address][:address1]
200
+ add_pair post, 'OwnerZip', options[:billing_address][:zip]
201
+ add_pair post, 'ownertown', options[:billing_address][:city]
202
+ add_pair post, 'ownercty', options[:billing_address][:country]
203
+ add_pair post, 'ownertelno', options[:billing_address][:phone]
204
+ end
205
+
206
+ def add_invoice(post, options)
207
+ add_pair post, 'orderID', options[:order_id] || generate_unique_id[0...30]
208
+ add_pair post, 'COM', options[:description]
209
+ end
210
+
211
+ def add_creditcard(post, creditcard)
212
+ add_pair post, 'CN', creditcard.name
213
+ add_pair post, 'CARDNO', creditcard.number
214
+ add_pair post, 'ED', "%02d%02s" % [creditcard.month, creditcard.year.to_s[-2..-1]]
215
+ add_pair post, 'CVC', creditcard.verification_value
216
+ end
217
+
218
+ def parse(body)
219
+ xml = REXML::Document.new(body)
220
+ xml.root.attributes
221
+ end
222
+
223
+ def commit(action, parameters)
224
+ add_pair parameters, 'PSPID', @options[:login]
225
+ add_pair parameters, 'USERID', @options[:user]
226
+ add_pair parameters, 'PSWD', @options[:password]
227
+ url = URLS[test? ? :test : :production][parameters['PAYID'] ? :maintenance : :order ]
228
+ response = parse(ssl_post(url, post_data(action, parameters)))
229
+ options = { :authorization => [response["PAYID"], action].join(";"),
230
+ :test => test?,
231
+ :avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] },
232
+ :cvv_result => CVV_MAPPING[response["CVCCheck"]] }
233
+ Response.new(successful?(response), message_from(response), response, options)
234
+ end
235
+
236
+ def successful?(response)
237
+ response["NCERROR"] == "0"
238
+ end
239
+
240
+ def message_from(response)
241
+ successful?(response) ? SUCCESS_MESSAGE : response["NCERRORPLUS"].to_s.strip.gsub("|", ", ")
242
+ end
243
+
244
+ def post_data(action, parameters = {})
245
+ add_pair parameters, 'Operation' , action
246
+ if @options[:signature] # the user wants a SHA-1 signature
247
+ string = ['orderID','amount','currency','CARDNO','PSPID','Operation','ALIAS'].map{|s|parameters[s]}.join + @options[:signature]
248
+ add_pair parameters, 'SHASign' , Digest::SHA1.hexdigest(string)
249
+ end
250
+ parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
251
+ end
252
+
253
+ def add_pair(post, key, value, options = {})
254
+ post[key] = value if !value.blank? || options[:required]
255
+ end
256
+
257
+ end
258
+ end
259
+ end
@@ -11,14 +11,14 @@ module ActiveMerchant #:nodoc:
11
11
  self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside'
12
12
  self.display_name = 'PayPal Website Payments Pro (US)'
13
13
 
14
- def authorize(money, credit_card, options = {})
14
+ def authorize(money, credit_card_or_referenced_id, options = {})
15
15
  requires!(options, :ip)
16
- commit 'DoDirectPayment', build_sale_or_authorization_request('Authorization', money, credit_card, options)
16
+ commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Authorization', money, credit_card_or_referenced_id, options)
17
17
  end
18
18
 
19
- def purchase(money, credit_card, options = {})
19
+ def purchase(money, credit_card_or_referenced_id, options = {})
20
20
  requires!(options, :ip)
21
- commit 'DoDirectPayment', build_sale_or_authorization_request('Sale', money, credit_card, options)
21
+ commit define_transaction_type(credit_card_or_referenced_id), build_sale_or_authorization_request('Sale', money, credit_card_or_referenced_id, options)
22
22
  end
23
23
 
24
24
  def express
@@ -26,15 +26,28 @@ module ActiveMerchant #:nodoc:
26
26
  end
27
27
 
28
28
  private
29
- def build_sale_or_authorization_request(action, money, credit_card, options)
29
+
30
+ def define_transaction_type(transaction_arg)
31
+ if transaction_arg.is_a?(String)
32
+ return 'DoReferenceTransaction'
33
+ else
34
+ return 'DoDirectPayment'
35
+ end
36
+ end
37
+
38
+ def build_sale_or_authorization_request(action, money, credit_card_or_referenced_id, options)
39
+ transaction_type = define_transaction_type(credit_card_or_referenced_id)
40
+ reference_id = credit_card_or_referenced_id if transaction_type == "DoReferenceTransaction"
41
+
30
42
  billing_address = options[:billing_address] || options[:address]
31
43
  currency_code = options[:currency] || currency(money)
32
44
 
33
45
  xml = Builder::XmlMarkup.new :indent => 2
34
- xml.tag! 'DoDirectPaymentReq', 'xmlns' => PAYPAL_NAMESPACE do
35
- xml.tag! 'DoDirectPaymentRequest', 'xmlns:n2' => EBAY_NAMESPACE do
46
+ xml.tag! transaction_type + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
47
+ xml.tag! transaction_type + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
36
48
  xml.tag! 'n2:Version', API_VERSION
37
- xml.tag! 'n2:DoDirectPaymentRequestDetails' do
49
+ xml.tag! 'n2:' + transaction_type + 'RequestDetails' do
50
+ xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
38
51
  xml.tag! 'n2:PaymentAction', action
39
52
  xml.tag! 'n2:PaymentDetails' do
40
53
  xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
@@ -54,7 +67,7 @@ module ActiveMerchant #:nodoc:
54
67
 
55
68
  add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) if options[:shipping_address]
56
69
  end
57
- add_credit_card(xml, credit_card, billing_address, options)
70
+ add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction'
58
71
  xml.tag! 'n2:IPAddress', options[:ip]
59
72
  end
60
73
  end
@@ -1,12 +1,12 @@
1
1
  module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
- class ProtxGateway < Gateway
3
+ class SagePayGateway < Gateway
4
4
  cattr_accessor :simulate
5
5
  self.simulate = false
6
6
 
7
- TEST_URL = 'https://ukvpstest.protx.com/vspgateway/service'
8
- LIVE_URL = 'https://ukvps.protx.com/vspgateway/service'
9
- SIMULATOR_URL = 'https://ukvpstest.protx.com/VSPSimulator'
7
+ TEST_URL = 'https://test.sagepay.com/gateway/service'
8
+ LIVE_URL = 'https://live.sagepay.com/gateway/service'
9
+ SIMULATOR_URL = 'https://test.sagepay.com/Simulator'
10
10
 
11
11
  APPROVED = 'OK'
12
12
  REGISTERED = 'REGISTERED'
@@ -47,10 +47,11 @@ module ActiveMerchant #:nodoc:
47
47
 
48
48
  self.supported_cardtypes = [:visa, :master, :american_express, :discover, :jcb, :switch, :solo, :maestro, :diners_club]
49
49
  self.supported_countries = ['GB']
50
+ self.supports_3d_secure = true
50
51
  self.default_currency = 'GBP'
51
52
 
52
- self.homepage_url = 'http://www.protx.com'
53
- self.display_name = 'Protx'
53
+ self.homepage_url = 'http://www.sagepay.com'
54
+ self.display_name = 'SagePay'
54
55
 
55
56
  def initialize(options = {})
56
57
  requires!(options, :login)
@@ -61,6 +62,10 @@ module ActiveMerchant #:nodoc:
61
62
  def test?
62
63
  @options[:test] || super
63
64
  end
65
+
66
+ def three_d_secure_enabled?
67
+ @options[:enable_3d_secure]
68
+ end
64
69
 
65
70
  def purchase(money, credit_card, options = {})
66
71
  requires!(options, :order_id)
@@ -71,7 +76,8 @@ module ActiveMerchant #:nodoc:
71
76
  add_credit_card(post, credit_card)
72
77
  add_address(post, options)
73
78
  add_customer_data(post, options)
74
-
79
+ add_three_d_secure_flag(post, options)
80
+
75
81
  commit(:purchase, post)
76
82
  end
77
83
 
@@ -84,7 +90,8 @@ module ActiveMerchant #:nodoc:
84
90
  add_credit_card(post, credit_card)
85
91
  add_address(post, options)
86
92
  add_customer_data(post, options)
87
-
93
+ add_three_d_secure_flag(post, options)
94
+
88
95
  commit(:authorization, post)
89
96
  end
90
97
 
@@ -152,6 +159,11 @@ module ActiveMerchant #:nodoc:
152
159
  commit(:credit, post)
153
160
  end
154
161
 
162
+ # Completes a 3D Secure transaction
163
+ def three_d_complete(pa_res, md)
164
+ commit(:three_d_complete, 'PARes' => pa_res, 'MD' => md)
165
+ end
166
+
155
167
  private
156
168
  def add_reference(post, identification)
157
169
  order_id, transaction_id, authorization, security_key = identification.split(';')
@@ -186,6 +198,14 @@ module ActiveMerchant #:nodoc:
186
198
  add_pair(post, :BillingPhone, options[:phone].gsub(/[^0-9+]/, '')[0,20]) unless options[:phone].blank?
187
199
  add_pair(post, :ClientIPAddress, options[:ip])
188
200
  end
201
+
202
+ def add_three_d_secure_flag(post, options)
203
+ if three_d_secure_enabled? && options[:skip_3d_secure] != true
204
+ add_pair(post, :Apply3DSecure, '0')
205
+ else
206
+ add_pair(post, :Apply3DSecure, '2')
207
+ end
208
+ end
189
209
 
190
210
  def add_address(post, options)
191
211
  if billing_address = options[:billing_address] || options[:address]
@@ -270,7 +290,11 @@ module ActiveMerchant #:nodoc:
270
290
  :street_match => AVS_CVV_CODE[ response["AddressResult"] ],
271
291
  :postal_match => AVS_CVV_CODE[ response["PostCodeResult"] ],
272
292
  },
273
- :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ]
293
+ :cvv_result => AVS_CVV_CODE[ response["CV2Result"] ],
294
+ :three_d_secure => response["Status"] == '3DAUTH',
295
+ :pa_req => response["PAReq"],
296
+ :md => response["MD"],
297
+ :acs_url => response["ACSURL"]
274
298
  )
275
299
  end
276
300
 
@@ -300,12 +324,20 @@ module ActiveMerchant #:nodoc:
300
324
  end
301
325
 
302
326
  def build_url(action)
303
- endpoint = [ :purchase, :authorization, :authenticate ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase
327
+ if action == :three_d_complete
328
+ endpoint = 'direct3dcallback'
329
+ else
330
+ endpoint = [ :purchase, :authorization, :authenticate ].include?(action) ? "vspdirect-register" : TRANSACTIONS[action].downcase
331
+ end
304
332
  "#{test? ? TEST_URL : LIVE_URL}/#{endpoint}.vsp"
305
333
  end
306
334
 
307
335
  def build_simulator_url(action)
308
- endpoint = [ :purchase, :authorization, :authenticate ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
336
+ if action == :three_d_complete
337
+ endpoint = 'VSPDirectCallback.asp'
338
+ else
339
+ endpoint = [ :purchase, :authorization, :authenticate ].include?(action) ? "VSPDirectGateway.asp" : "VSPServerGateway.asp?Service=Vendor#{TRANSACTIONS[action].capitalize}Tx"
340
+ end
309
341
  "#{SIMULATOR_URL}/#{endpoint}"
310
342
  end
311
343
 
@@ -323,7 +355,7 @@ module ActiveMerchant #:nodoc:
323
355
  parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
324
356
  end
325
357
 
326
- # Protx returns data in the following format
358
+ # SagePay returns data in the following format
327
359
  # Key1=value1
328
360
  # Key2=value2
329
361
  def parse(body)
@@ -344,6 +376,7 @@ module ActiveMerchant #:nodoc:
344
376
  [ first_name[0,20], last_name[0,20] ]
345
377
  end
346
378
  end
379
+ ProtxGateway = SagePayGateway
347
380
  end
348
381
  end
349
382