tlconnor-activemerchant 1.20.4 → 1.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGELOG +86 -6
  2. data/CONTRIBUTORS +33 -0
  3. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +2 -0
  4. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +4 -4
  5. data/lib/active_merchant/billing/gateways/blue_pay.rb +492 -11
  6. data/lib/active_merchant/billing/gateways/braintree_blue.rb +46 -19
  7. data/lib/active_merchant/billing/gateways/certo_direct.rb +1 -1
  8. data/lib/active_merchant/billing/gateways/elavon.rb +2 -0
  9. data/lib/active_merchant/billing/gateways/epay.rb +3 -1
  10. data/lib/active_merchant/billing/gateways/itransact.rb +450 -0
  11. data/lib/active_merchant/billing/gateways/litle.rb +275 -0
  12. data/lib/active_merchant/billing/gateways/migs.rb +259 -0
  13. data/lib/active_merchant/billing/gateways/migs/migs_codes.rb +100 -0
  14. data/lib/active_merchant/billing/gateways/moneris.rb +4 -30
  15. data/lib/active_merchant/billing/gateways/moneris_us.rb +211 -0
  16. data/lib/active_merchant/billing/gateways/nab_transact.rb +1 -1
  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 +60 -13
  24. data/lib/active_merchant/billing/gateways/paypal.rb +3 -18
  25. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +333 -3
  26. data/lib/active_merchant/billing/gateways/paypal/paypal_recurring_api.rb +245 -0
  27. data/lib/active_merchant/billing/gateways/paypal_digital_goods.rb +43 -0
  28. data/lib/active_merchant/billing/gateways/paypal_express.rb +14 -65
  29. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +8 -3
  30. data/lib/active_merchant/billing/gateways/realex.rb +5 -7
  31. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +3 -2
  32. data/lib/active_merchant/billing/gateways/stripe.rb +1 -9
  33. data/lib/active_merchant/billing/gateways/usa_epay_advanced.rb +2 -2
  34. data/lib/active_merchant/billing/gateways/usa_epay_transaction.rb +1 -5
  35. data/lib/active_merchant/billing/gateways/viaklix.rb +7 -2
  36. data/lib/active_merchant/billing/gateways/vindicia.rb +359 -0
  37. data/lib/active_merchant/billing/integrations/dotpay.rb +22 -0
  38. data/lib/active_merchant/billing/integrations/dotpay/helper.rb +77 -0
  39. data/lib/active_merchant/billing/integrations/dotpay/notification.rb +86 -0
  40. data/lib/active_merchant/billing/integrations/dotpay/return.rb +11 -0
  41. data/lib/active_merchant/billing/integrations/epay.rb +21 -0
  42. data/lib/active_merchant/billing/integrations/epay/helper.rb +55 -0
  43. data/lib/active_merchant/billing/integrations/epay/notification.rb +110 -0
  44. data/lib/active_merchant/billing/integrations/paypal/notification.rb +2 -1
  45. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +2 -3
  46. data/lib/active_merchant/billing/integrations/robokassa.rb +49 -0
  47. data/lib/active_merchant/billing/integrations/robokassa/common.rb +19 -0
  48. data/lib/active_merchant/billing/integrations/robokassa/helper.rb +50 -0
  49. data/lib/active_merchant/billing/integrations/robokassa/notification.rb +55 -0
  50. data/lib/active_merchant/billing/integrations/robokassa/return.rb +17 -0
  51. data/lib/active_merchant/billing/integrations/two_checkout.rb +25 -3
  52. data/lib/active_merchant/billing/integrations/two_checkout/helper.rb +58 -26
  53. data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +71 -46
  54. data/lib/active_merchant/billing/integrations/verkkomaksut.rb +20 -0
  55. data/lib/active_merchant/billing/integrations/verkkomaksut/helper.rb +87 -0
  56. data/lib/active_merchant/billing/integrations/verkkomaksut/notification.rb +59 -0
  57. data/lib/active_merchant/version.rb +1 -1
  58. metadata +28 -5
@@ -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,11 +16,11 @@ 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'
23
-
23
+
24
24
  URL = 'https://sec.paymentexpress.com/pxpost.aspx'
25
25
 
26
26
  APPROVED = '1'
@@ -34,8 +34,13 @@ module ActiveMerchant #:nodoc:
34
34
  }
35
35
 
36
36
  # We require the DPS gateway username and password when the object is created.
37
+ #
38
+ # The PaymentExpress gateway also supports a :use_custom_payment_token boolean option.
39
+ # If set to true the gateway will use BillingId for the Token type. If set to false,
40
+ # then the token will be sent as the DPS specified "DpsBillingId". This is per the documentation at
41
+ # http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Tokenbilling
37
42
  def initialize(options = {})
38
- # A DPS username and password must exist
43
+ # A DPS username and password must exist
39
44
  requires!(options, :login, :password)
40
45
  # Make the options an instance variable
41
46
  @options = options
@@ -43,6 +48,12 @@ module ActiveMerchant #:nodoc:
43
48
  end
44
49
 
45
50
  # Funds are transferred immediately.
51
+ #
52
+ # `payment_source` can be a usual ActiveMerchant credit_card object, or can also
53
+ # be a string of the `DpsBillingId` or `BillingId` which can be gotten through the
54
+ # store method. If you are using a `BillingId` instead of `DpsBillingId` you must
55
+ # also set the instance method `#use_billing_id_for_token` to true, see the `#store`
56
+ # method for an example of how to do this.
46
57
  def purchase(money, payment_source, options = {})
47
58
  request = build_purchase_or_authorization_request(money, payment_source, options)
48
59
  commit(:purchase, request)
@@ -51,6 +62,8 @@ module ActiveMerchant #:nodoc:
51
62
  # NOTE: Perhaps in options we allow a transaction note to be inserted
52
63
  # Verifies that funds are available for the requested card and amount and reserves the specified amount.
53
64
  # See: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Authcomplete
65
+ #
66
+ # `payment_source` can be a usual ActiveMerchant credit_card object or a token, see #purchased method
54
67
  def authorize(money, payment_source, options = {})
55
68
  request = build_purchase_or_authorization_request(money, payment_source, options)
56
69
  commit(:authorization, request)
@@ -76,18 +89,47 @@ module ActiveMerchant #:nodoc:
76
89
  refund(money, identification, options)
77
90
  end
78
91
 
79
- # token based billing
80
-
81
- # initiates a "Validate" transcation to store card data on payment express servers
82
- # returns a "token" that can be used to rebill this card
92
+ # Token Based Billing
93
+ #
94
+ # Instead of storing the credit card details locally, you can store them inside the
95
+ # Payment Express system and instead bill future transactions against a token.
96
+ #
97
+ # This token can either be specified by your code or autogenerated by the PaymentExpress
98
+ # system. The default is to let PaymentExpress generate the token for you and so use
99
+ # the `DpsBillingId`. If you do not pass in any option of the `billing_id`, then the store
100
+ # method will ask PaymentExpress to create a token for you. Additionally, if you are
101
+ # using the default `DpsBillingId`, you do not have to do anything extra in the
102
+ # initialization of your gateway object.
103
+ #
104
+ # To specify and use your own token, you need to do two things.
105
+ #
106
+ # Firstly, pass in a `:billing_id` as an option in the hash of this store method. No
107
+ # validation is done on this BillingId by PaymentExpress so you must ensure that it is unique.
108
+ #
109
+ # gateway.store(credit_card, {:billing_id => 'YourUniqueBillingId'})
110
+ #
111
+ # Secondly, you will need to pass in the option `{:use_custom_payment_token => true}` when
112
+ # initializing your gateway instance, like so:
113
+ #
114
+ # gateway = ActiveMerchant::Billing::PaymentExpressGateway.new(
115
+ # :login => 'USERNAME',
116
+ # :password => 'PASSWORD',
117
+ # :use_custom_payment_token => true
118
+ # )
119
+ #
83
120
  # see: http://www.paymentexpress.com/technical_resources/ecommerce_nonhosted/pxpost.html#Tokenbilling
84
- # PaymentExpress does not support unstoring a stored card.
121
+ #
122
+ # Note, once stored, PaymentExpress does not support unstoring a stored card.
85
123
  def store(credit_card, options = {})
86
124
  request = build_token_request(credit_card, options)
87
125
  commit(:validate, request)
88
126
  end
89
-
127
+
90
128
  private
129
+
130
+ def use_custom_payment_token?
131
+ @options[:use_custom_payment_token]
132
+ end
91
133
 
92
134
  def build_purchase_or_authorization_request(money, payment_source, options)
93
135
  result = new_transaction
@@ -137,6 +179,7 @@ module ActiveMerchant #:nodoc:
137
179
 
138
180
  if credit_card.verification_value?
139
181
  xml.add_element("Cvc2").text = credit_card.verification_value
182
+ xml.add_element("Cvc2Presence").text = "1"
140
183
  end
141
184
 
142
185
  if requires_start_date_or_issue_number?(credit_card)
@@ -144,9 +187,13 @@ module ActiveMerchant #:nodoc:
144
187
  xml.add_element("IssueNumber").text = credit_card.issue_number unless credit_card.issue_number.blank?
145
188
  end
146
189
  end
147
-
148
- def add_billing_token(xml, token)
149
- xml.add_element("DpsBillingId").text = token
190
+
191
+ def add_billing_token(xml, token)
192
+ if use_custom_payment_token?
193
+ xml.add_element("BillingId").text = token
194
+ else
195
+ xml.add_element("DpsBillingId").text = token
196
+ end
150
197
  end
151
198
 
152
199
  def add_token_request(xml, options)
@@ -232,4 +279,4 @@ module ActiveMerchant #:nodoc:
232
279
  end
233
280
  end
234
281
  end
235
- end
282
+ end
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'openssl'
2
+ require 'base64'
1
3
  module ActiveMerchant #:nodoc:
2
4
  module Billing #:nodoc:
3
5
  # This module is included in both PaypalGateway and PaypalExpressGateway
@@ -63,7 +65,15 @@ module ActiveMerchant #:nodoc:
63
65
  def initialize(options = {})
64
66
  requires!(options, :login, :password)
65
67
 
66
- headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
68
+ headers = if options[:access_token]
69
+ acess_token = options.delete(:access_token)
70
+ access_secret = options.delete(:access_secret)
71
+
72
+ {'X-PAYPAL-AUTHORIZATION' => x_pp_authorization_header(access_token, access_secret)}
73
+ else
74
+ {}
75
+ end
76
+
67
77
  @options = {
68
78
  :pem => pem_file,
69
79
  :signature => signature,
@@ -117,7 +127,158 @@ module ActiveMerchant #:nodoc:
117
127
  refund(money, identification, options)
118
128
  end
119
129
 
130
+ # ==== 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]
131
+ # ==== Parameter:
132
+ # * <tt>:money</tt> -- (Required) The amount of this new transaction,
133
+ # required fo the payment details portion of this request
134
+ #
135
+ # ==== Options:
136
+ # * <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.
137
+ # * <tt>:payment_action</tt> -- (Optional) How you want to obtain payment. It is one of the following values:
138
+ #
139
+ # Authorization – This payment is a basic authorization subject to settlement with PayPal Authorization and Capture.
140
+ # Sale – This is a final sale for which you are requesting payment.
141
+ #
142
+ # * <tt>:ip_address</tt> -- (Optional) IP address of the buyer’s browser.
143
+ # Note: PayPal records this IP addresses as a means to detect possible fraud.
144
+ # * <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:
145
+ #
146
+ # 0 – You do not require that the buyer’s shipping address be a confirmed address.
147
+ # 1 – You require that the buyer’s shipping address be a confirmed address.
148
+ #
149
+ # * <tt>:merchant_session_id</tt> -- (Optional) Your buyer session identification token.
150
+ # * <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:
151
+ #
152
+ # 0 – Do not receive FMF details (default)
153
+ # 1 – Receive FMF details
154
+ #
155
+ # * <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:
156
+ #
157
+ # <PP * | PAYPAL *><Merchant descriptor as set in the Payment Receiving Preferences><1 space><soft descriptor>
158
+ # The soft descriptor can contain only the following characters:
159
+ #
160
+ # Alphanumeric characters
161
+ # - (dash)
162
+ # * (asterisk)
163
+ # . (period)
164
+ # {space}
165
+ #
166
+ def reference_transaction(money, options = {})
167
+ requires!(options, :reference_id)
168
+ commit 'DoReferenceTransaction', build_reference_transaction_request(money, options)
169
+ end
170
+
171
+ def transaction_details(transaction_id)
172
+ commit 'GetTransactionDetails', build_get_transaction_details(transaction_id)
173
+ end
174
+
175
+ # ==== 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]
176
+ # ==== Options:
177
+ # * <tt>:payer </tt> -- (Optional) Search by the buyer’s email address.
178
+ # * <tt>:receipt_id </tt> -- (Optional) Search by the PayPal Account Optional receipt ID.
179
+ # * <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.
180
+ # * <tt>:transaction_id</tt> -- (Optional) Search by the transaction ID. The returned results are from the merchant’s transaction records.
181
+ # * <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.
182
+ # * <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.
183
+ # * <tt>:auction_item_number </tt> -- (Optional) Search by auction item number of the purchased goods.
184
+ # * <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:
185
+ # All – All transaction classifications
186
+ # Sent – Only payments sent
187
+ # Received – Only payments received
188
+ # MassPay – Only mass payments
189
+ # MoneyRequest – Only money requests
190
+ # FundsAdded – Only funds added to balance
191
+ # FundsWithdrawn – Only funds withdrawn from balance
192
+ # Referral – Only transactions involving referrals
193
+ # Fee – Only transactions involving fees
194
+ # Subscription – Only transactions involving subscriptions
195
+ # Dividend – Only transactions involving dividends
196
+ # Billpay – Only transactions involving BillPay Transactions
197
+ # Refund – Only transactions involving funds
198
+ # CurrencyConversions – Only transactions involving currency conversions
199
+ # BalanceTransfer – Only transactions involving balance transfers
200
+ # Reversal – Only transactions involving BillPay reversals
201
+ # Shipping – Only transactions involving UPS shipping fees
202
+ # BalanceAffecting – Only transactions that affect the account balance
203
+ # ECheck – Only transactions involving eCheck
204
+ #
205
+ # * <tt>:currency_code </tt> -- (Optional) Search by currency code.
206
+ # * <tt>:status</tt> -- (Optional) Search by transaction status. It is one of the following values:
207
+ # One of:
208
+ # Pending – The payment is pending. The specific reason the payment is pending is returned by the GetTransactionDetails API PendingReason field.
209
+ # Processing – The payment is being processed.
210
+ # Success – The payment has been completed and the funds have been added successfully to your account balance.
211
+ # Denied – You denied the payment. This happens only if the payment was previously pending.
212
+ # 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.
213
+ #
214
+ def transaction_search(options)
215
+ requires!(options, :start_date)
216
+ commit 'TransactionSearch', build_transaction_search(options)
217
+ end
218
+
219
+ # ==== Parameters:
220
+ # * <tt>:return_all_currencies</tt> -- Either '1' or '0'
221
+ # 0 – Return only the balance for the primary currency holding.
222
+ # 1 – Return the balance for each currency holding.
223
+ #
224
+ def balance(return_all_currencies = false)
225
+ clean_currency_argument = case return_all_currencies
226
+ when 1, '1' , true; '1'
227
+ else
228
+ '0'
229
+ end
230
+ commit 'GetBalance', build_get_balance(clean_currency_argument)
231
+ end
232
+
233
+ # DoAuthorization takes the transaction_id returned when you call
234
+ # DoExpressCheckoutPayment with a PaymentAction of 'Order'.
235
+ # When you did that, you created an order authorization subject to settlement
236
+ # with PayPal DoAuthorization and DoCapture
237
+ #
238
+ # ==== Parameters:
239
+ # * <tt>:transaction_id</tt> -- The ID returned by DoExpressCheckoutPayment with a PaymentAction of 'Order'.
240
+ # * <tt>:money</tt> -- The amount of money to be authorized for this purchase.
241
+ #
242
+ def authorize_transaction(transaction_id, money, options = {})
243
+ commit 'DoAuthorization', build_do_authorize(transaction_id, money, options)
244
+ end
245
+
246
+ # The ManagePendingTransactionStatus API operation accepts or denys a
247
+ # pending transaction held by Fraud Management Filters.
248
+ #
249
+ # ==== Parameters:
250
+ # * <tt>:transaction_id</tt> -- The ID of the transaction held by Fraud Management Filters.
251
+ # * <tt>:action</tt> -- Either 'Accept' or 'Deny'
252
+ #
253
+ def manage_pending_transaction(transaction_id, action)
254
+ commit 'ManagePendingTransactionStatus', build_manage_pending_transaction_status(transaction_id, action)
255
+ end
256
+
120
257
  private
258
+ def build_request_wrapper(action, options = {})
259
+ xml = Builder::XmlMarkup.new :indent => 2
260
+ xml.tag! action + 'Req', 'xmlns' => PAYPAL_NAMESPACE do
261
+ xml.tag! action + 'Request', 'xmlns:n2' => EBAY_NAMESPACE do
262
+ xml.tag! 'n2:Version', API_VERSION
263
+ if options[:request_details]
264
+ xml.tag! 'n2:' + action + 'RequestDetails' do
265
+ yield(xml)
266
+ end
267
+ else
268
+ yield(xml)
269
+ end
270
+ end
271
+ end
272
+ xml.target!
273
+ end
274
+
275
+ def build_do_authorize(transaction_id, money, options = {})
276
+ build_request_wrapper('DoAuthorization') do |xml|
277
+ xml.tag! 'TransactionID', transaction_id
278
+ xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
279
+ end
280
+ end
281
+
121
282
  def build_reauthorize_request(money, authorization, options)
122
283
  xml = Builder::XmlMarkup.new
123
284
 
@@ -140,7 +301,7 @@ module ActiveMerchant #:nodoc:
140
301
  xml.tag! 'n2:Version', API_VERSION
141
302
  xml.tag! 'AuthorizationID', authorization
142
303
  xml.tag! 'Amount', amount(money), 'currencyID' => options[:currency] || currency(money)
143
- xml.tag! 'CompleteType', 'Complete'
304
+ xml.tag! 'CompleteType', options[:complete_type] || 'Complete'
144
305
  xml.tag! 'InvoiceID', options[:order_id] unless options[:order_id].blank?
145
306
  xml.tag! 'Note', options[:description]
146
307
  end
@@ -204,6 +365,55 @@ module ActiveMerchant #:nodoc:
204
365
  xml.target!
205
366
  end
206
367
 
368
+ def build_manage_pending_transaction_status(transaction_id, action)
369
+ build_request_wrapper('ManagePendingTransactionStatus') do |xml|
370
+ xml.tag! 'TransactionID', transaction_id
371
+ xml.tag! 'Action', action
372
+ end
373
+ end
374
+
375
+ def build_reference_transaction_request(money, options)
376
+ opts = options.dup
377
+ opts[:ip_address] ||= opts[:ip]
378
+ currency_code = opts[:currency] || currency(money)
379
+ reference_transaction_optional_fields = %w{ n2:ReferenceID n2:PaymentAction
380
+ n2:PaymentType n2:IPAddress
381
+ n2:ReqConfirmShipping n2:MerchantSessionId
382
+ n2:ReturnFMFDetails n2:SoftDescriptor }
383
+ build_request_wrapper('DoReferenceTransaction', :request_details => true) do |xml|
384
+ add_optional_fields(xml, reference_transaction_optional_fields, opts)
385
+ add_payment_details(xml, money, currency_code, opts)
386
+ end
387
+ end
388
+
389
+ def build_get_transaction_details(transaction_id)
390
+ build_request_wrapper('GetTransactionDetails') do |xml|
391
+ xml.tag! 'TransactionID', transaction_id
392
+ end
393
+ end
394
+
395
+ def build_transaction_search(options)
396
+ currency_code = options[:currency_code]
397
+ currency_code ||= currency(options[:amount]) if options[:amount]
398
+ transaction_search_optional_fields = %w{ Payer ReceiptID Receiver
399
+ TransactionID InvoiceID CardNumber
400
+ AuctionItemNumber TransactionClass
401
+ CurrencyCode Status }
402
+ build_request_wrapper('TransactionSearch') do |xml|
403
+ xml.tag! 'StartDate', date_to_iso(options[:start_date])
404
+ xml.tag! 'EndDate', date_to_iso(options[:end_date]) unless options[:end_date].blank?
405
+ add_optional_fields(xml, transaction_search_optional_fields, options)
406
+ xml.tag! 'Amount', localized_amount(options[:amount], currency_code), 'currencyID' => currency_code unless options[:amount].blank?
407
+ end
408
+ end
409
+
410
+
411
+ def build_get_balance(return_all_currencies)
412
+ build_request_wrapper('GetBalance') do |xml|
413
+ xml.tag! 'ReturnAllCurrencies', return_all_currencies unless return_all_currencies.nil?
414
+ end
415
+ end
416
+
207
417
  def parse(action, xml)
208
418
  legacy_hash = legacy_parse(action, xml)
209
419
  xml = strip_attributes(xml)
@@ -313,11 +523,93 @@ module ActiveMerchant #:nodoc:
313
523
  xml.tag! 'n2:CityName', address[:city]
314
524
  xml.tag! 'n2:StateOrProvince', address[:state].blank? ? 'N/A' : address[:state]
315
525
  xml.tag! 'n2:Country', address[:country]
316
- xml.tag! 'n2:Phone', address[:phone]
526
+ xml.tag! 'n2:Phone', address[:phone] unless address[:phone].blank?
317
527
  xml.tag! 'n2:PostalCode', address[:zip]
318
528
  end
319
529
  end
320
530
 
531
+ def add_payment_details_items_xml(xml, options, currency_code)
532
+ options[:items].each do |item|
533
+ xml.tag! 'n2:PaymentDetailsItem' do
534
+ xml.tag! 'n2:Name', item[:name]
535
+ xml.tag! 'n2:Number', item[:number]
536
+ xml.tag! 'n2:Quantity', item[:quantity]
537
+ if item[:amount]
538
+ xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
539
+ end
540
+ xml.tag! 'n2:Description', item[:description]
541
+ xml.tag! 'n2:ItemURL', item[:url]
542
+ xml.tag! 'n2:ItemCategory', item[:category] if item[:category]
543
+ end
544
+ end
545
+ end
546
+
547
+ def add_payment_details(xml, money, currency_code, options = {})
548
+ xml.tag! 'n2:PaymentDetails' do
549
+ xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
550
+
551
+ # All of the values must be included together and add up to the order total
552
+ if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
553
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
554
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code
555
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code
556
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
557
+ end
558
+
559
+ xml.tag! 'n2:InsuranceTotal', localized_amount(options[:insurance_total], currency_code),'currencyID' => currency_code unless options[:insurance_total].blank?
560
+ xml.tag! 'n2:ShippingDiscount', localized_amount(options[:shipping_discount], currency_code),'currencyID' => currency_code unless options[:shipping_discount].blank?
561
+ xml.tag! 'n2:InsuranceOptionOffered', options[:insurance_option_offered] if options.has_key?(:insurance_option_offered)
562
+
563
+ xml.tag! 'n2:OrderDescription', options[:description] unless options[:description].blank?
564
+
565
+ # Custom field Character length and limitations: 256 single-byte alphanumeric characters
566
+ xml.tag! 'n2:Custom', options[:custom] unless options[:custom].blank?
567
+
568
+ xml.tag! 'n2:InvoiceID', (options[:order_id] || options[:invoice_id]) unless (options[:order_id] || options[:invoice_id]).blank?
569
+ xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
570
+
571
+ # The notify URL applies only to DoExpressCheckoutPayment.
572
+ # This value is ignored when set in SetExpressCheckout or GetExpressCheckoutDetails
573
+ xml.tag! 'n2:NotifyURL', options[:notify_url] unless options[:notify_url].blank?
574
+
575
+ add_address(xml, 'n2:ShipToAddress', options[:shipping_address]) unless options[:shipping_address].blank?
576
+
577
+ add_payment_details_items_xml(xml, options, currency_code) unless options[:items].blank?
578
+
579
+ add_express_only_payment_details(xml, options) if options[:express_request]
580
+
581
+ # Any value other than Y – This is not a recurring transaction
582
+ # To pass Y in this field, you must have established a billing agreement with
583
+ # the buyer specifying the amount, frequency, and duration of the recurring payment.
584
+ # requires version 80.0 of the API
585
+ xml.tag! 'n2:Recurring', options[:recurring] unless options[:recurring].blank?
586
+ end
587
+ end
588
+
589
+ def add_express_only_payment_details(xml, options = {})
590
+ add_optional_fields(xml,
591
+ %w{n2:NoteText n2:SoftDescriptor
592
+ n2:TransactionId n2:AllowedPaymentMethodType
593
+ n2:PaymentRequestID n2:PaymentAction},
594
+ options)
595
+ end
596
+
597
+ def add_optional_fields(xml, optional_fields, options = {})
598
+ optional_fields.each do |optional_text_field|
599
+ if optional_text_field =~ /(\w+:)(\w+)/
600
+ ns = $1
601
+ field = $2
602
+ field_as_symbol = field.underscore.to_sym
603
+ else
604
+ ns = ''
605
+ field = optional_text_field
606
+ field_as_symbol = optional_text_field.underscore.to_sym
607
+ end
608
+ xml.tag! ns + field, options[field_as_symbol] unless options[field_as_symbol].blank?
609
+ end
610
+ xml
611
+ end
612
+
321
613
  def endpoint_url
322
614
  URLS[test? ? :test : :live][@options[:signature].blank? ? :certificate : :signature]
323
615
  end
@@ -349,6 +641,44 @@ module ActiveMerchant #:nodoc:
349
641
  def message_from(response)
350
642
  response[:message] || response[:ack]
351
643
  end
644
+
645
+ def date_to_iso(date)
646
+ (date.is_a?(Date) ? date.to_time : date).utc.iso8601
647
+ end
648
+
649
+ def x_pp_authorization_header(access_token, access_secret)
650
+ timestamp = Time.now.to_i.to_s
651
+ signature = x_pp_authorization_signature(timestamp, access_token, access_secret)
652
+ "token=#{access_token},signature=#{signature},timestamp=#{timestamp}"
653
+ end
654
+
655
+ def x_pp_authorization_signature(timestamp, access_token, access_secret)
656
+ # no query params, but if there were, this is where they'd go
657
+ query_params = {}
658
+ key = [
659
+ URI.encode(@options[:password]),
660
+ URI.encode(access_secret),
661
+ ].join("&")
662
+
663
+ params = query_params.dup.merge({
664
+ "oauth_consumer_key" => @options[:login],
665
+ "oauth_version" => "1.0",
666
+ "oauth_signature_method" => "HMAC-SHA1",
667
+ "oauth_token" => access_token,
668
+ "oauth_timestamp" => timestamp,
669
+ })
670
+ sorted_params = Hash[params.sort]
671
+ sorted_query_string = sorted_params.to_query
672
+
673
+ base = [
674
+ "POST",
675
+ URI.encode(endpoint_url),
676
+ URI.encode(sorted_query_string)
677
+ ].join("&")
678
+
679
+ hexdigest = OpenSSL::HMAC.hexdigest('sha1', key, base)
680
+ Base64.encode64(hexdigest).chomp
681
+ end
352
682
  end
353
683
  end
354
684
  end