activemerchant 1.21.0 → 1.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +63 -0
  3. data/CONTRIBUTORS +29 -0
  4. data/README.md +195 -0
  5. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -0
  6. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +2 -2
  7. data/lib/active_merchant/billing/gateways/blue_pay.rb +492 -11
  8. data/lib/active_merchant/billing/gateways/braintree_blue.rb +46 -19
  9. data/lib/active_merchant/billing/gateways/certo_direct.rb +1 -1
  10. data/lib/active_merchant/billing/gateways/cyber_source.rb +342 -106
  11. data/lib/active_merchant/billing/gateways/elavon.rb +2 -0
  12. data/lib/active_merchant/billing/gateways/epay.rb +3 -1
  13. data/lib/active_merchant/billing/gateways/itransact.rb +450 -0
  14. data/lib/active_merchant/billing/gateways/migs.rb +259 -0
  15. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  16. data/lib/active_merchant/billing/gateways/moneris_us.rb +211 -0
  17. data/lib/active_merchant/billing/gateways/ogone.rb +104 -12
  18. data/lib/active_merchant/billing/gateways/orbital.rb +15 -6
  19. data/lib/active_merchant/billing/gateways/paybox_direct.rb +1 -4
  20. data/lib/active_merchant/billing/gateways/payflow.rb +8 -3
  21. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +4 -1
  22. data/lib/active_merchant/billing/gateways/payflow_express.rb +4 -2
  23. data/lib/active_merchant/billing/gateways/payment_express.rb +1 -1
  24. data/lib/active_merchant/billing/gateways/paypal.rb +3 -18
  25. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +287 -1
  26. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +245 -0
  27. data/lib/active_merchant/billing/gateways/paypal_express.rb +14 -66
  28. data/lib/active_merchant/billing/gateways/realex.rb +5 -7
  29. data/lib/active_merchant/billing/gateways/stripe.rb +1 -9
  30. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +2 -2
  31. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -5
  32. data/lib/active_merchant/billing/gateways/viaklix.rb +7 -2
  33. data/lib/active_merchant/billing/gateways/vindicia.rb +359 -0
  34. data/lib/active_merchant/billing/integrations/dotpay.rb +22 -0
  35. data/lib/active_merchant/billing/integrations/dotpay/helper.rb +77 -0
  36. data/lib/active_merchant/billing/integrations/dotpay/notification.rb +86 -0
  37. data/lib/active_merchant/billing/integrations/dotpay/return.rb +11 -0
  38. data/lib/active_merchant/billing/integrations/epay.rb +21 -0
  39. data/lib/active_merchant/billing/integrations/epay/helper.rb +55 -0
  40. data/lib/active_merchant/billing/integrations/epay/notification.rb +110 -0
  41. data/lib/active_merchant/billing/integrations/paypal/notification.rb +2 -1
  42. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +2 -3
  43. data/lib/active_merchant/billing/integrations/robokassa.rb +49 -0
  44. data/lib/active_merchant/billing/integrations/robokassa/common.rb +19 -0
  45. data/lib/active_merchant/billing/integrations/robokassa/helper.rb +50 -0
  46. data/lib/active_merchant/billing/integrations/robokassa/notification.rb +55 -0
  47. data/lib/active_merchant/billing/integrations/robokassa/return.rb +17 -0
  48. data/lib/active_merchant/billing/integrations/two_checkout.rb +25 -3
  49. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +15 -0
  50. data/lib/active_merchant/billing/integrations/verkkomaksut.rb +20 -0
  51. data/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb +87 -0
  52. data/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +59 -0
  53. data/lib/active_merchant/version.rb +1 -1
  54. metadata +59 -26
  55. metadata.gz.sig +0 -0
@@ -9,17 +9,23 @@ module ActiveMerchant #:nodoc:
9
9
  # communication between Ogone systems and your e-commerce website.
10
10
  #
11
11
  # This implementation follows the specification provided in the DirectLink integration
12
- # guide version 4.2.0 (26 October 2011), available here:
12
+ # guide version 4.3.0 (25 April 2012), available here:
13
13
  # https://secure.ogone.com/ncol/Ogone_DirectLink_EN.pdf
14
14
  #
15
15
  # It also features aliases, which allow to store/unstore credit cards, as specified in
16
- # the Alias Manager Option guide version 3.2.0 (26 October 2011) available here:
16
+ # the Alias Manager Option guide version 3.2.1 (25 April 2012) available here:
17
17
  # https://secure.ogone.com/ncol/Ogone_Alias_EN.pdf
18
18
  #
19
- # It was last tested on Release 4.89 of Ogone DirectLink + AliasManager (26 October 2011).
19
+ # It also implements the 3-D Secure feature, as specified in the DirectLink with
20
+ # 3-D Secure guide version 3.0 (25 April 2012) available here:
21
+ # https://secure.ogone.com/ncol/Ogone_DirectLink-3-D_EN.pdf
22
+ #
23
+ # It was last tested on Release 4.92 of Ogone DirectLink + AliasManager + Direct Link 3D
24
+ # (25 April 2012).
20
25
  #
21
26
  # For any questions or comments, please contact one of the following:
22
- # - Nicolas Jacobeus (nj@belighted.com),
27
+ # - Joel Cogen (joel.cogen@belighted.com)
28
+ # - Nicolas Jacobeus (nicolas.jacobeus@belighted.com),
23
29
  # - Sébastien Grosjean (public@zencocoon.com),
24
30
  # - Rémy Coutable (remy@jilion.com).
25
31
  #
@@ -52,17 +58,46 @@ module ActiveMerchant #:nodoc:
52
58
  # puts response.success? # Check whether the transaction was successful
53
59
  # puts response.message # Retrieve the message returned by Ogone
54
60
  # puts response.authorization # Retrieve the unique transaction ID returned by Ogone
61
+ # puts response.order_id # Retrieve the order ID
55
62
  #
56
63
  # == Alias feature
57
64
  #
58
- # To use the alias feature, simply add :store in the options hash:
65
+ # To use the alias feature, simply add :billing_id in the options hash:
59
66
  #
60
67
  # # Associate the alias to that credit card
61
- # gateway.purchase(1000, creditcard, :order_id => "1", :store => "myawesomecustomer")
68
+ # gateway.purchase(1000, creditcard, :order_id => "1", :billing_id => "myawesomecustomer")
62
69
  #
63
70
  # # You can use the alias instead of the credit card for subsequent orders
64
71
  # gateway.purchase(2000, "myawesomecustomer", :order_id => "2")
65
72
  #
73
+ # # You can also create an alias without making a purchase using store
74
+ # gateway.store(creditcard, :billing_id => "myawesomecustomer")
75
+ #
76
+ # # When using store, you can also let Ogone generate the alias for you
77
+ # response = gateway.store(creditcard)
78
+ # puts response.billing_id # Retrieve the generated alias
79
+ #
80
+ # == 3-D Secure feature
81
+ #
82
+ # To use the 3-D Secure feature, simply add :d3d => true in the options hash:
83
+ # gateway.purchase(2000, "myawesomecustomer", :order_id => "2", :d3d => true)
84
+ #
85
+ # Specific 3-D Secure request options are (please refer to the documentation for more infos about these options):
86
+ # :win_3ds => :main_window (default), :pop_up or :pop_ix.
87
+ # :http_accept => "*/*" (default), or any other HTTP_ACCEPT header value.
88
+ # :http_user_agent => The cardholder's User-Agent string
89
+ # :accept_url => URL of the web page to show the customer when the payment is authorized.
90
+ # (or waiting to be authorized).
91
+ # :decline_url => URL of the web page to show the customer when the acquirer rejects the authorization
92
+ # more than the maximum permitted number of authorization attempts (10 by default, but can
93
+ # be changed in the "Global transaction parameters" tab, "Payment retry" section of the
94
+ # Technical Information page).
95
+ # :exception_url => URL of the web page to show the customer when the payment result is uncertain.
96
+ # :paramplus => Field to submit the miscellaneous parameters and their values that you wish to be
97
+ # returned in the post sale request or final redirection.
98
+ # :complus => Field to submit a value you wish to be returned in the post sale request or output.
99
+ # :language => Customer's language, for example: "en_EN"
100
+ #
66
101
  class OgoneGateway < Gateway
67
102
 
68
103
  URLS = {
@@ -80,8 +115,16 @@ module ActiveMerchant #:nodoc:
80
115
 
81
116
  SUCCESS_MESSAGE = "The transaction was successful"
82
117
 
118
+ THREE_D_SECURE_DISPLAY_WAYS = { :main_window => 'MAINW', # display the identification page in the main window
119
+ # (default value).
120
+ :pop_up => 'POPUP', # display the identification page in a pop-up window
121
+ # and return to the main window at the end.
122
+ :pop_ix => 'POPIX' } # display the identification page in a pop-up window
123
+ # and remain in the pop-up window.
124
+
83
125
  OGONE_NO_SIGNATURE_DEPRECATION_MESSAGE = "Signature usage will be required from a future release of ActiveMerchant's Ogone Gateway. Please update your Ogone account to use it."
84
126
  OGONE_LOW_ENCRYPTION_DEPRECATION_MESSAGE = "SHA512 signature encryptor will be required from a future release of ActiveMerchant's Ogone Gateway. Please update your Ogone account to use it."
127
+ OGONE_STORE_OPTION_DEPRECATION_MESSAGE = "The 'store' option has been renamed to 'billing_id', and its usage is deprecated."
85
128
 
86
129
  self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH']
87
130
  # also supports Airplus and UATP
@@ -152,6 +195,14 @@ module ActiveMerchant #:nodoc:
152
195
  perform_reference_credit(money, reference, options)
153
196
  end
154
197
 
198
+ # Store a credit card by creating an Ogone Alias
199
+ def store(payment_source, options = {})
200
+ options.merge!(:alias_operation => 'BYOGONE') unless options.has_key?(:billing_id) || options.has_key?(:store)
201
+ response = authorize(1, payment_source, options)
202
+ void(response.authorization) if response.success?
203
+ response
204
+ end
205
+
155
206
  def test?
156
207
  @options[:test] || super
157
208
  end
@@ -164,7 +215,7 @@ module ActiveMerchant #:nodoc:
164
215
 
165
216
  def reference_transaction?(identifier)
166
217
  return false unless identifier.is_a?(String)
167
- reference, action = identifier.split(";")
218
+ _, action = identifier.split(";")
168
219
  !action.nil?
169
220
  end
170
221
 
@@ -188,21 +239,44 @@ module ActiveMerchant #:nodoc:
188
239
 
189
240
  def add_payment_source(post, payment_source, options)
190
241
  if payment_source.is_a?(String)
191
- add_alias(post, payment_source)
242
+ add_alias(post, payment_source, options[:alias_operation])
192
243
  add_eci(post, options[:eci] || '9')
193
244
  else
194
- add_alias(post, options[:store])
245
+ if options.has_key?(:store)
246
+ deprecated OGONE_STORE_OPTION_DEPRECATION_MESSAGE
247
+ options[:billing_id] ||= options[:store]
248
+ end
249
+ add_alias(post, options[:billing_id], options[:alias_operation])
195
250
  add_eci(post, options[:eci] || '7')
251
+ add_d3d(post, options) if options[:d3d]
196
252
  add_creditcard(post, payment_source)
197
253
  end
198
254
  end
199
255
 
256
+ def add_d3d(post, options)
257
+ add_pair post, 'FLAG3D', 'Y'
258
+ win_3ds = THREE_D_SECURE_DISPLAY_WAYS.key?(options[:win_3ds]) ?
259
+ THREE_D_SECURE_DISPLAY_WAYS[options[:win_3ds]] :
260
+ THREE_D_SECURE_DISPLAY_WAYS[:main_window]
261
+ add_pair post, 'WIN3DS', win_3ds
262
+
263
+ add_pair post, 'HTTP_ACCEPT', options[:http_accept] || "*/*"
264
+ add_pair post, 'HTTP_USER_AGENT', options[:http_user_agent] if options[:http_user_agent]
265
+ add_pair post, 'ACCEPTURL', options[:accept_url] if options[:accept_url]
266
+ add_pair post, 'DECLINEURL', options[:decline_url] if options[:decline_url]
267
+ add_pair post, 'EXCEPTIONURL', options[:exception_url] if options[:exception_url]
268
+ add_pair post, 'PARAMPLUS', options[:paramplus] if options[:paramplus]
269
+ add_pair post, 'COMPLUS', options[:complus] if options[:complus]
270
+ add_pair post, 'LANGUAGE', options[:language] if options[:language]
271
+ end
272
+
200
273
  def add_eci(post, eci)
201
274
  add_pair post, 'ECI', eci.to_s
202
275
  end
203
276
 
204
- def add_alias(post, _alias)
277
+ def add_alias(post, _alias, alias_operation = nil)
205
278
  add_pair post, 'ALIAS', _alias
279
+ add_pair post, 'ALIASOPERATION', alias_operation unless alias_operation.nil?
206
280
  end
207
281
 
208
282
  def add_authorization(post, authorization)
@@ -242,7 +316,15 @@ module ActiveMerchant #:nodoc:
242
316
 
243
317
  def parse(body)
244
318
  xml_root = REXML::Document.new(body).root
245
- convert_attributes_to_hash(xml_root.attributes)
319
+ response = convert_attributes_to_hash(xml_root.attributes)
320
+
321
+ # Add HTML_ANSWER element (3-D Secure specific to the response's params)
322
+ # Note: HTML_ANSWER is not an attribute so we add it "by hand" to the response
323
+ if html_answer = REXML::XPath.first(xml_root, "//HTML_ANSWER")
324
+ response["HTML_ANSWER"] = html_answer.text
325
+ end
326
+
327
+ response
246
328
  end
247
329
 
248
330
  def commit(action, parameters)
@@ -259,7 +341,7 @@ module ActiveMerchant #:nodoc:
259
341
  :avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] },
260
342
  :cvv_result => CVV_MAPPING[response["CVCCheck"]]
261
343
  }
262
- Response.new(successful?(response), message_from(response), response, options)
344
+ OgoneResponse.new(successful?(response), message_from(response), response, options)
263
345
  end
264
346
 
265
347
  def successful?(response)
@@ -326,5 +408,15 @@ module ActiveMerchant #:nodoc:
326
408
  response_hash
327
409
  end
328
410
  end
411
+
412
+ class OgoneResponse < Response
413
+ def order_id
414
+ @params['orderID']
415
+ end
416
+
417
+ def billing_id
418
+ @params['ALIAS']
419
+ end
420
+ end
329
421
  end
330
422
  end
@@ -77,6 +77,8 @@ module ActiveMerchant #:nodoc:
77
77
  "EUR" => '978'
78
78
  }
79
79
 
80
+ AVS_SUPPORTED_COUNTRIES = ['US', 'CA', 'UK', 'GB']
81
+
80
82
  def initialize(options = {})
81
83
  requires!(options, :merchant_id)
82
84
  requires!(options, :login, :password) unless options[:ip_authentication]
@@ -148,12 +150,7 @@ module ActiveMerchant #:nodoc:
148
150
 
149
151
  def add_address(xml, creditcard, options)
150
152
  if address = options[:billing_address] || options[:address]
151
- xml.tag! :AVSzip, address[:zip]
152
- xml.tag! :AVSaddress1, address[:address1]
153
- xml.tag! :AVSaddress2, address[:address2]
154
- xml.tag! :AVScity, address[:city]
155
- xml.tag! :AVSstate, address[:state]
156
- xml.tag! :AVSphoneNum, address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil
153
+ add_avs_details(xml, address)
157
154
  xml.tag! :AVSname, creditcard.name
158
155
  xml.tag! :AVScountryCode, address[:country]
159
156
  end
@@ -177,6 +174,18 @@ module ActiveMerchant #:nodoc:
177
174
  xml.tag! :CurrencyExponent, '2' # Will need updating to support currencies such as the Yen.
178
175
  end
179
176
 
177
+ def add_avs_details(xml, address)
178
+ return unless AVS_SUPPORTED_COUNTRIES.include?(address[:country].to_s)
179
+
180
+ xml.tag! :AVSzip, address[:zip]
181
+ xml.tag! :AVSaddress1, address[:address1]
182
+ xml.tag! :AVSaddress2, address[:address2]
183
+ xml.tag! :AVScity, address[:city]
184
+ xml.tag! :AVSstate, address[:state]
185
+ xml.tag! :AVSphoneNum, address[:phone] ? address[:phone].scan(/\d/).join.to_s : nil
186
+ end
187
+
188
+
180
189
  def parse(body)
181
190
  response = {}
182
191
  xml = REXML::Document.new(body)
@@ -1,5 +1,3 @@
1
- require 'iconv'
2
-
3
1
  module ActiveMerchant #:nodoc:
4
2
  module Billing #:nodoc:
5
3
  class PayboxDirectGateway < Gateway
@@ -39,7 +37,7 @@ module ActiveMerchant #:nodoc:
39
37
 
40
38
  SUCCESS_CODES = ['00000']
41
39
  UNAVAILABILITY_CODES = ['00001', '00097', '00098']
42
- FRAUD_CODES = ['00102','00104','00105','00134','00138','00141','00143','00156','00157','00159']
40
+ FRAUD_CODES = ['00102','00104','00134','00138','00141','00143','00157','00159']
43
41
  SUCCESS_MESSAGE = 'The transaction was approved'
44
42
  FAILURE_MESSAGE = 'The transaction failed'
45
43
 
@@ -132,7 +130,6 @@ module ActiveMerchant #:nodoc:
132
130
  end
133
131
 
134
132
  def parse(body)
135
- body = Iconv.iconv("UTF-8","LATIN1", body.to_s).join
136
133
  results = {}
137
134
  body.split(/&/).each do |pair|
138
135
  key,val = pair.split(/\=/)
@@ -198,7 +198,7 @@ module ActiveMerchant #:nodoc:
198
198
  xml.tag! 'PayPeriod', get_pay_period(options)
199
199
  xml.tag! 'Term', options[:payments] unless options[:payments].nil?
200
200
  xml.tag! 'Comment', options[:comment] unless options[:comment].nil?
201
-
201
+ xml.tag! 'RetryNumDays', options[:retry_num_days] unless options[:retry_num_days].nil?
202
202
 
203
203
  if initial_tx = options[:initial_transaction]
204
204
  requires!(initial_tx, [:type, :authorization, :purchase])
@@ -207,8 +207,13 @@ module ActiveMerchant #:nodoc:
207
207
  xml.tag! 'OptionalTrans', TRANSACTIONS[initial_tx[:type]]
208
208
  xml.tag! 'OptionalTransAmt', amount(initial_tx[:amount]) unless initial_tx[:amount].blank?
209
209
  end
210
-
211
- xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 )
210
+
211
+ if action == :add
212
+ xml.tag! 'Start', format_rp_date(options[:starting_at] || Date.today + 1 )
213
+ else
214
+ xml.tag! 'Start', format_rp_date(options[:starting_at]) unless options[:starting_at].nil?
215
+ end
216
+
212
217
  xml.tag! 'EMail', options[:email] unless options[:email].nil?
213
218
 
214
219
  billing_address = options[:billing_address] || options[:address]
@@ -111,7 +111,10 @@ module ActiveMerchant #:nodoc:
111
111
 
112
112
  unless money.nil?
113
113
  xml.tag! 'Invoice' do
114
- xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
114
+ xml.tag!('TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money))
115
+ xml.tag!('Description', options[:description]) unless options[:description].blank?
116
+ xml.tag!('Comment', options[:comment]) unless options[:comment].blank?
117
+ xml.tag!('ExtData', 'Name'=> 'COMMENT2', 'Value'=> options[:comment2]) unless options[:comment2].blank?
115
118
  end
116
119
  end
117
120
  end
@@ -33,6 +33,7 @@ module ActiveMerchant #:nodoc:
33
33
  # [<tt>:notify_url</tt>] (opt) Your URL for receiving Instant Payment Notification (IPN) about this transaction.
34
34
  # [<tt>:comment</tt>] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment1
35
35
  # [<tt>:comment2</tt>] (opt) Comment field which will be reported to Payflow backend (at manager.paypal.com) as Comment2
36
+ # [<tt>:discount</tt>] (opt) Total discounts in cents
36
37
  #
37
38
  # ==Line Items
38
39
  # Support for order line items is available, but has to be enabled on the PayFlow backend. This is what I was told by Todd Sieber at Technical Support:
@@ -177,10 +178,11 @@ module ActiveMerchant #:nodoc:
177
178
  end
178
179
  if items.any?
179
180
  xml.tag! 'ExtData', 'Name' => 'CURRENCY', 'Value' => options[:currency] || currency(money)
180
- xml.tag! 'ExtData', 'Name' => "ITEMAMT", 'Value' => amount(money)
181
+ xml.tag! 'ExtData', 'Name' => "ITEMAMT", 'Value' => amount(options[:subtotal] || money)
181
182
  end
182
-
183
+ xml.tag! 'DiscountAmt', amount(options[:discount]) if options[:discount]
183
184
  xml.tag! 'TotalAmt', amount(money), 'Currency' => options[:currency] || currency(money)
185
+
184
186
  end
185
187
 
186
188
  xml.tag! 'Tender' do
@@ -16,7 +16,7 @@ module ActiveMerchant #:nodoc:
16
16
  # However, regular accounts with DPS only support VISA and Mastercard
17
17
  self.supported_cardtypes = [ :visa, :master, :american_express, :diners_club, :jcb ]
18
18
 
19
- self.supported_countries = [ 'AU', 'MY', 'NZ', 'SG', 'ZA', 'GB', 'US' ]
19
+ self.supported_countries = %w[ AU MY NZ SG ZA GB US ]
20
20
 
21
21
  self.homepage_url = 'http://www.paymentexpress.com/'
22
22
  self.display_name = 'PaymentExpress'
@@ -1,10 +1,12 @@
1
1
  require File.dirname(__FILE__) + '/paypal/paypal_common_api'
2
+ require File.dirname(__FILE__) + '/paypal/paypal_recurring_api'
2
3
  require File.dirname(__FILE__) + '/paypal_express'
3
4
 
4
5
  module ActiveMerchant #:nodoc:
5
6
  module Billing #:nodoc:
6
7
  class PaypalGateway < Gateway
7
8
  include PaypalCommonAPI
9
+ include PaypalRecurringApi
8
10
 
9
11
  self.supported_cardtypes = [:visa, :master, :american_express, :discover]
10
12
  self.supported_countries = ['US']
@@ -49,24 +51,7 @@ module ActiveMerchant #:nodoc:
49
51
  xml.tag! 'n2:' + transaction_type + 'RequestDetails' do
50
52
  xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
51
53
  xml.tag! 'n2:PaymentAction', action
52
- xml.tag! 'n2:PaymentDetails' do
53
- xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
54
-
55
- # All of the values must be included together and add up to the order total
56
- if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
57
- xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
58
- xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code
59
- xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code
60
- xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
61
- end
62
-
63
- xml.tag! 'n2:NotifyURL', options[:notify_url]
64
- xml.tag! 'n2:OrderDescription', options[:description]
65
- xml.tag! 'n2:InvoiceID', options[:order_id]
66
- xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
67
-
68
- add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) if options[:shipping_address]
69
- end
54
+ add_payment_details(xml, money, currency_code, options)
70
55
  add_credit_card(xml, credit_card_or_referenced_id, billing_address, options) unless transaction_type == 'DoReferenceTransaction'
71
56
  xml.tag! 'n2:IPAddress', options[:ip]
72
57
  end
@@ -117,7 +117,158 @@ module ActiveMerchant #:nodoc:
117
117
  refund(money, identification, options)
118
118
  end
119
119
 
120
+ # ==== For full documentation see {Paypal API Reference:}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_DoReferenceTransaction]
121
+ # ==== Parameter:
122
+ # * <tt>:money</tt> -- (Required) The amount of this new transaction,
123
+ # required fo the payment details portion of this request
124
+ #
125
+ # ==== Options:
126
+ # * <tt>:reference_id</tt> -- (Required) A transaction ID from a previous purchase, such as a credit card charge using the DoDirectPayment API, or a billing agreement ID.
127
+ # * <tt>:payment_action</tt> -- (Optional) How you want to obtain payment. It is one of the following values:
128
+ #
129
+ # Authorization – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture.
130
+ # Sale – This is a final sale for which you are requesting payment.
131
+ #
132
+ # * <tt>:ip_address</tt> -- (Optional) IP address of the buyer’s browser.
133
+ # Note: PayPal records this IP addresses as a means to detect possible fraud.
134
+ # * <tt>:req_confirm_shipping</tt> -- Whether you require that the buyer’s shipping address on file with PayPal be a confirmed address. You must have permission from PayPal to not require a confirmed address. It is one of the following values:
135
+ #
136
+ # 0 – You do not require that the buyer’s shipping address be a confirmed address.
137
+ # 1 – You require that the buyer’s shipping address be a confirmed address.
138
+ #
139
+ # * <tt>:merchant_session_id</tt> -- (Optional) Your buyer session identification token.
140
+ # * <tt>:return_fmf_details</tt> -- (Optional) Flag to indicate whether you want the results returned by Fraud Management Filters. By default, you do not receive this information. It is one of the following values:
141
+ #
142
+ # 0 – Do not receive FMF details (default)
143
+ # 1 – Receive FMF details
144
+ #
145
+ # * <tt>:soft_descriptor</tt> -- (Optional) Per transaction description of the payment that is passed to the consumer’s credit card statement. If the API request provides a value for the soft descriptor field, the full descriptor displayed on the buyer’s statement has the following format:
146
+ #
147
+ # <PP * | PAYPAL *><Merchant descriptor as set in the Payment Receiving Preferences><1 space><soft descriptor>
148
+ # The soft descriptor can contain only the following characters:
149
+ #
150
+ # Alphanumeric characters
151
+ # - (dash)
152
+ # * (asterisk)
153
+ # . (period)
154
+ # {space}
155
+ #
156
+ def reference_transaction(money, options = {})
157
+ requires!(options, :reference_id)
158
+ commit 'DoReferenceTransaction', build_reference_transaction_request(money, options)
159
+ end
160
+
161
+ def transaction_details(transaction_id)
162
+ commit 'GetTransactionDetails', build_get_transaction_details(transaction_id)
163
+ end
164
+
165
+ # ==== For full documentation see {PayPal API Reference}[https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_soap_r_TransactionSearch]
166
+ # ==== Options:
167
+ # * <tt>:payer </tt> -- (Optional) Search by the buyer’s email address.
168
+ # * <tt>:receipt_id </tt> -- (Optional) Search by the PayPal Account Optional receipt ID.
169
+ # * <tt>:receiver </tt> -- (Optional) Search by the receiver’s email address. If the merchant account has only one email address, this is the primary email. It can also be a non-primary email address.
170
+ # * <tt>:transaction_id</tt> -- (Optional) Search by the transaction ID. The returned results are from the merchant’s transaction records.
171
+ # * <tt>:invoice_id</tt> -- (Optional) Search by invoice identification key, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased.
172
+ # * <tt>:card_number </tt> -- (Optional) Search by credit card number, as set by you for the original transaction. This field searches the records for items the merchant sells, not the items purchased.
173
+ # * <tt>:auction_item_number </tt> -- (Optional) Search by auction item number of the purchased goods.
174
+ # * <tt>:transaction_class </tt> -- (Optional) Search by classification of transaction. Some kinds of possible classes of transactions are not searchable with this field. You cannot search for bank transfer withdrawals, for example. It is one of the following values:
175
+ # All – All transaction classifications
176
+ # Sent – Only payments sent
177
+ # Received – Only payments received
178
+ # MassPay – Only mass payments
179
+ # MoneyRequest – Only money requests
180
+ # FundsAdded – Only funds added to balance
181
+ # FundsWithdrawn – Only funds withdrawn from balance
182
+ # Referral – Only transactions involving referrals
183
+ # Fee – Only transactions involving fees
184
+ # Subscription – Only transactions involving subscriptions
185
+ # Dividend – Only transactions involving dividends
186
+ # Billpay – Only transactions involving BillPay Transactions
187
+ # Refund – Only transactions involving funds
188
+ # CurrencyConversions – Only transactions involving currency conversions
189
+ # BalanceTransfer – Only transactions involving balance transfers
190
+ # Reversal – Only transactions involving BillPay reversals
191
+ # Shipping – Only transactions involving UPS shipping fees
192
+ # BalanceAffecting – Only transactions that affect the account balance
193
+ # ECheck – Only transactions involving eCheck
194
+ #
195
+ # * <tt>:currency_code </tt> -- (Optional) Search by currency code.
196
+ # * <tt>:status</tt> -- (Optional) Search by transaction status. It is one of the following values:
197
+ # One of:
198
+ # Pending – The payment is pending. The specific reason the payment is pending is returned by the GetTransactionDetails API PendingReason field.
199
+ # Processing – The payment is being processed.
200
+ # Success – The payment has been completed and the funds have been added successfully to your account balance.
201
+ # Denied – You denied the payment. This happens only if the payment was previously pending.
202
+ # Reversed – A payment was reversed due to a chargeback or other type of reversal. The funds have been removed from your account balance and returned to the buyer.
203
+ #
204
+ def transaction_search(options)
205
+ requires!(options, :start_date)
206
+ commit 'TransactionSearch', build_transaction_search(options)
207
+ end
208
+
209
+ # ==== Parameters:
210
+ # * <tt>:return_all_currencies</tt> -- Either '1' or '0'
211
+ # 0 – Return only the balance for the primary currency holding.
212
+ # 1 – Return the balance for each currency holding.
213
+ #
214
+ def balance(return_all_currencies = false)
215
+ clean_currency_argument = case return_all_currencies
216
+ when 1, '1' , true; '1'
217
+ else
218
+ '0'
219
+ end
220
+ commit 'GetBalance', build_get_balance(clean_currency_argument)
221
+ end
222
+
223
+ # DoAuthorization takes the transaction_id returned when you call
224
+ # DoExpressCheckoutPayment with a PaymentAction of 'Order'.
225
+ # When you did that, you created an order authorization subject to settlement
226
+ # with PayPal DoAuthorization and DoCapture
227
+ #
228
+ # ==== Parameters:
229
+ # * <tt>:transaction_id</tt> -- The ID returned by DoExpressCheckoutPayment with a PaymentAction of 'Order'.
230
+ # * <tt>:money</tt> -- The amount of money to be authorized for this purchase.
231
+ #
232
+ def authorize_transaction(transaction_id, money, options = {})
233
+ commit 'DoAuthorization', build_do_authorize(transaction_id, money, options)
234
+ end
235
+
236
+ # The ManagePendingTransactionStatus API operation accepts or denys a
237
+ # pending transaction held by Fraud Management Filters.
238
+ #
239
+ # ==== Parameters:
240
+ # * <tt>:transaction_id</tt> -- The ID of the transaction held by Fraud Management Filters.
241
+ # * <tt>:action</tt> -- Either 'Accept' or 'Deny'
242
+ #
243
+ def manage_pending_transaction(transaction_id, action)
244
+ commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action)
245
+ end
246
+
120
247
  private
248
+ def build_request_wrapper(action, options = {})
249
+ xml = Builder::XmlMarkup.new :indent => 2
250
+ xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
251
+ xml.tag! action + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
252
+ xml.tag! 'n2:Version', API_VERSION
253
+ if options[:request_details]
254
+ xml.tag! 'n2:' + action + 'RequestDetails' do
255
+ yield(xml)
256
+ end
257
+ else
258
+ yield(xml)
259
+ end
260
+ end
261
+ end
262
+ xml.target!
263
+ end
264
+
265
+ def build_do_authorize(transaction_id, money, options = {})
266
+ build_request_wrapper('DoAuthorization') do |xml|
267
+ xml.tag! 'TransactionID', transaction_id
268
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
269
+ end
270
+ end
271
+
121
272
  def build_reauthorize_request(money, authorization, options)
122
273
  xml = Builder::XmlMarkup.new
123
274
 
@@ -204,6 +355,55 @@ module ActiveMerchant #:nodoc:
204
355
  xml.target!
205
356
  end
206
357
 
358
+ def build_manage_pending_transaction_status(transaction_id, action)
359
+ build_request_wrapper('ManagePendingTransactionStatus') do |xml|
360
+ xml.tag! 'TransactionID', transaction_id
361
+ xml.tag! 'Action', action
362
+ end
363
+ end
364
+
365
+ def build_reference_transaction_request(money, options)
366
+ opts = options.dup
367
+ opts[:ip_address] ||= opts[:ip]
368
+ currency_code = opts[:currency] || currency(money)
369
+ reference_transaction_optional_fields = %w{ n2:ReferenceID n2:PaymentAction
370
+ n2:PaymentType n2:IPAddress
371
+ n2:ReqConfirmShipping n2:MerchantSessionId
372
+ n2:ReturnFMFDetails n2:SoftDescriptor }
373
+ build_request_wrapper('DoReferenceTransaction', :request_details => true) do |xml|
374
+ add_optional_fields(xml, reference_transaction_optional_fields, opts)
375
+ add_payment_details(xml, money, currency_code, opts)
376
+ end
377
+ end
378
+
379
+ def build_get_transaction_details(transaction_id)
380
+ build_request_wrapper('GetTransactionDetails') do |xml|
381
+ xml.tag! 'TransactionID', transaction_id
382
+ end
383
+ end
384
+
385
+ def build_transaction_search(options)
386
+ currency_code = options[:currency_code]
387
+ currency_code ||= currency(options[:amount]) if options[:amount]
388
+ transaction_search_optional_fields = %w{ Payer ReceiptID Receiver
389
+ TransactionID InvoiceID CardNumber
390
+ AuctionItemNumber TransactionClass
391
+ CurrencyCode Status }
392
+ build_request_wrapper('TransactionSearch') do |xml|
393
+ xml.tag! 'StartDate', date_to_iso(options[:start_date])
394
+ xml.tag! 'EndDate', date_to_iso(options[:end_date]) unless options[:end_date].blank?
395
+ add_optional_fields(xml, transaction_search_optional_fields, options)
396
+ xml.tag! 'Amount', localized_amount(options[:amount], currency_code), 'currencyID' => currency_code unless options[:amount].blank?
397
+ end
398
+ end
399
+
400
+
401
+ def build_get_balance(return_all_currencies)
402
+ build_request_wrapper('GetBalance') do |xml|
403
+ xml.tag! 'ReturnAllCurrencies', return_all_currencies unless return_all_currencies.nil?
404
+ end
405
+ end
406
+
207
407
  def parse(action, xml)
208
408
  legacy_hash = legacy_parse(action, xml)
209
409
  xml = strip_attributes(xml)
@@ -313,11 +513,93 @@ module ActiveMerchant #:nodoc:
313
513
  xml.tag! 'n2:CityName', address[:city]
314
514
  xml.tag! 'n2:StateOrProvince', address[:state].blank? ? 'N/A' : address[:state]
315
515
  xml.tag! 'n2:Country', address[:country]
316
- xml.tag! 'n2:Phone', address[:phone]
516
+ xml.tag! 'n2:Phone', address[:phone] unless address[:phone].blank?
317
517
  xml.tag! 'n2:PostalCode', address[:zip]
318
518
  end
319
519
  end
320
520
 
521
+ def add_payment_details_items_xml(xml, options, currency_code)
522
+ options[:items].each do |item|
523
+ xml.tag! 'n2:PaymentDetailsItem' do
524
+ xml.tag! 'n2:Name', item[:name]
525
+ xml.tag! 'n2:Number', item[:number]
526
+ xml.tag! 'n2:Quantity', item[:quantity]
527
+ if item[:amount]
528
+ xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
529
+ end
530
+ xml.tag! 'n2:Description', item[:description]
531
+ xml.tag! 'n2:ItemURL', item[:url]
532
+ xml.tag! 'n2:ItemCategory', item[:category] if item[:category]
533
+ end
534
+ end
535
+ end
536
+
537
+ def add_payment_details(xml, money, currency_code, options = {})
538
+ xml.tag! 'n2:PaymentDetails' do
539
+ xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
540
+
541
+ # All of the values must be included together and add up to the order total
542
+ if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
543
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
544
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code
545
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code
546
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
547
+ end
548
+
549
+ xml.tag! 'n2:InsuranceTotal', localized_amount(options[:insurance_total], currency_code),'currencyID' => currency_code unless options[:insurance_total].blank?
550
+ xml.tag! 'n2:ShippingDiscount', localized_amount(options[:shipping_discount], currency_code),'currencyID' => currency_code unless options[:shipping_discount].blank?
551
+ xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_option_offered] if options.has_key?(:insurance_option_offered)
552
+
553
+ xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank?
554
+
555
+ # Custom field Character length and limitations: 256 single-byte alphanumeric characters
556
+ xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
557
+
558
+ xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank?
559
+ xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
560
+
561
+ # The notify URL applies only to DoExpressCheckoutPayment.
562
+ # This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails
563
+ xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
564
+
565
+ add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) unless options[:shipping_address].blank?
566
+
567
+ add_payment_details_items_xml(xml, options, currency_code) unless options[:items].blank?
568
+
569
+ add_express_only_payment_details(xml, options) if options[:express_request]
570
+
571
+ # Any value other than Y – This is not a recurring transaction
572
+ # To pass Y in this field, you must have established a billing agreement with
573
+ # the buyer specifying the amount, frequency, and duration of the recurring payment.
574
+ # requires version 80.0 of the API
575
+ xml.tag! 'n2:Recurring', options[:recurring] unless options[:recurring].blank?
576
+ end
577
+ end
578
+
579
+ def add_express_only_payment_details(xml, options = {})
580
+ add_optional_fields(xml,
581
+ %w{n2:NoteText n2:SoftDescriptor
582
+ n2:TransactionId n2:AllowedPaymentMethodType
583
+ n2:PaymentRequestID n2:PaymentAction},
584
+ options)
585
+ end
586
+
587
+ def add_optional_fields(xml, optional_fields, options = {})
588
+ optional_fields.each do |optional_text_field|
589
+ if optional_text_field =~ /(\w+:)(\w+)/
590
+ ns = $1
591
+ field = $2
592
+ field_as_symbol = field.underscore.to_sym
593
+ else
594
+ ns = ''
595
+ field = optional_text_field
596
+ field_as_symbol = optional_text_field.underscore.to_sym
597
+ end
598
+ xml.tag! ns + field, options[field_as_symbol] unless options[field_as_symbol].blank?
599
+ end
600
+ xml
601
+ end
602
+
321
603
  def endpoint_url
322
604
  URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
323
605
  end
@@ -349,6 +631,10 @@ module ActiveMerchant #:nodoc:
349
631
  def message_from(response)
350
632
  response[:message] || response[:ack]
351
633
  end
634
+
635
+ def date_to_iso(date)
636
+ (date.is_a?(Date) ? date.to_time : date).utc.iso8601
637
+ end
352
638
  end
353
639
  end
354
640
  end