tomriley-active_merchant 1.4.2.3 → 1.4.2.4

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.
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