bitfluent-activemerchant 1.5.1.1 → 1.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG +168 -0
  2. data/CONTRIBUTORS +96 -1
  3. data/README.rdoc +33 -5
  4. data/lib/active_merchant.rb +12 -0
  5. data/lib/active_merchant/billing/credit_card.rb +59 -46
  6. data/lib/active_merchant/billing/credit_card_methods.rb +3 -3
  7. data/lib/active_merchant/billing/gateway.rb +16 -9
  8. data/lib/active_merchant/billing/gateways/authorize_net.rb +19 -9
  9. data/lib/active_merchant/billing/gateways/authorize_net_cim.rb +134 -12
  10. data/lib/active_merchant/billing/gateways/barclays_epdq.rb +309 -0
  11. data/lib/active_merchant/billing/gateways/beanstream.rb +39 -2
  12. data/lib/active_merchant/billing/gateways/beanstream/beanstream_core.rb +64 -26
  13. data/lib/active_merchant/billing/gateways/blue_pay.rb +11 -0
  14. data/lib/active_merchant/billing/gateways/bogus.rb +33 -3
  15. data/lib/active_merchant/billing/gateways/braintree.rb +11 -11
  16. data/lib/active_merchant/billing/gateways/braintree/braintree_common.rb +9 -0
  17. data/lib/active_merchant/billing/gateways/braintree_blue.rb +293 -0
  18. data/lib/active_merchant/billing/gateways/braintree_orange.rb +17 -0
  19. data/lib/active_merchant/billing/gateways/cyber_source.rb +5 -1
  20. data/lib/active_merchant/billing/gateways/data_cash.rb +6 -2
  21. data/lib/active_merchant/billing/gateways/efsnet.rb +8 -2
  22. data/lib/active_merchant/billing/gateways/epay.rb +268 -0
  23. data/lib/active_merchant/billing/gateways/eway_managed.rb +231 -0
  24. data/lib/active_merchant/billing/gateways/federated_canada.rb +168 -0
  25. data/lib/active_merchant/billing/gateways/first_pay.rb +8 -3
  26. data/lib/active_merchant/billing/gateways/garanti.rb +132 -92
  27. data/lib/active_merchant/billing/gateways/ideal/ideal_base.rb +250 -0
  28. data/lib/active_merchant/billing/gateways/ideal/ideal_rabobank.pem +13 -0
  29. data/lib/active_merchant/billing/gateways/ideal/ideal_response.rb +29 -0
  30. data/lib/active_merchant/billing/gateways/ideal_rabobank.rb +55 -0
  31. data/lib/active_merchant/billing/gateways/inspire.rb +221 -0
  32. data/lib/active_merchant/billing/gateways/iridium.rb +258 -0
  33. data/lib/active_merchant/billing/gateways/jetpay.rb +12 -6
  34. data/lib/active_merchant/billing/gateways/linkpoint.rb +6 -1
  35. data/lib/active_merchant/billing/gateways/merchant_e_solutions.rb +10 -8
  36. data/lib/active_merchant/billing/gateways/merchant_ware.rb +7 -1
  37. data/lib/active_merchant/billing/gateways/moneris.rb +6 -2
  38. data/lib/active_merchant/billing/gateways/netaxept.rb +239 -0
  39. data/lib/active_merchant/billing/gateways/nmi.rb +13 -0
  40. data/lib/active_merchant/billing/gateways/ogone.rb +16 -3
  41. data/lib/active_merchant/billing/gateways/orbital.rb +317 -0
  42. data/lib/active_merchant/billing/gateways/orbital/orbital_soft_descriptors.rb +46 -0
  43. data/lib/active_merchant/billing/gateways/pay_junction.rb +9 -9
  44. data/lib/active_merchant/billing/gateways/paybox_direct.rb +207 -0
  45. data/lib/active_merchant/billing/gateways/payflow.rb +26 -9
  46. data/lib/active_merchant/billing/gateways/payflow/payflow_common_api.rb +11 -11
  47. data/lib/active_merchant/billing/gateways/payflow_express.rb +122 -38
  48. data/lib/active_merchant/billing/gateways/payment_express.rb +7 -2
  49. data/lib/active_merchant/billing/gateways/paypal.rb +5 -5
  50. data/lib/active_merchant/billing/gateways/paypal/paypal_common_api.rb +33 -8
  51. data/lib/active_merchant/billing/gateways/paypal/paypal_express_response.rb +26 -15
  52. data/lib/active_merchant/billing/gateways/paypal_ca.rb +1 -1
  53. data/lib/active_merchant/billing/gateways/paypal_express.rb +55 -13
  54. data/lib/active_merchant/billing/gateways/paypal_express_common.rb +8 -3
  55. data/lib/active_merchant/billing/gateways/plugnpay.rb +9 -3
  56. data/lib/active_merchant/billing/gateways/psigate.rb +5 -0
  57. data/lib/active_merchant/billing/gateways/qbms.rb +295 -0
  58. data/lib/active_merchant/billing/gateways/quantum.rb +282 -0
  59. data/lib/active_merchant/billing/gateways/quickpay.rb +7 -2
  60. data/lib/active_merchant/billing/gateways/realex.rb +187 -76
  61. data/lib/active_merchant/billing/gateways/sage_pay.rb +16 -5
  62. data/lib/active_merchant/billing/gateways/secure_net.rb +330 -0
  63. data/lib/active_merchant/billing/gateways/secure_pay_au.rb +39 -3
  64. data/lib/active_merchant/billing/gateways/smart_ps.rb +9 -3
  65. data/lib/active_merchant/billing/gateways/trust_commerce.rb +7 -2
  66. data/lib/active_merchant/billing/gateways/usa_epay.rb +1 -1
  67. data/lib/active_merchant/billing/gateways/verifi.rb +6 -1
  68. data/lib/active_merchant/billing/gateways/viaklix.rb +1 -1
  69. data/lib/active_merchant/billing/gateways/worldpay.rb +280 -0
  70. data/lib/active_merchant/billing/integrations.rb +0 -12
  71. data/lib/active_merchant/billing/integrations/action_view_helper.rb +13 -24
  72. data/lib/active_merchant/billing/integrations/bogus.rb +2 -2
  73. data/lib/active_merchant/billing/integrations/chronopay.rb +2 -2
  74. data/lib/active_merchant/billing/integrations/direc_pay.rb +41 -0
  75. data/lib/active_merchant/billing/integrations/direc_pay/helper.rb +200 -0
  76. data/lib/active_merchant/billing/integrations/direc_pay/notification.rb +76 -0
  77. data/lib/active_merchant/billing/integrations/direc_pay/return.rb +32 -0
  78. data/lib/active_merchant/billing/integrations/direc_pay/status.rb +37 -0
  79. data/lib/active_merchant/billing/integrations/directebanking.rb +47 -0
  80. data/lib/active_merchant/billing/integrations/directebanking/helper.rb +90 -0
  81. data/lib/active_merchant/billing/integrations/directebanking/notification.rb +120 -0
  82. data/lib/active_merchant/billing/integrations/directebanking/return.rb +11 -0
  83. data/lib/active_merchant/billing/integrations/gestpay.rb +2 -2
  84. data/lib/active_merchant/billing/integrations/helper.rb +14 -11
  85. data/lib/active_merchant/billing/integrations/hi_trust.rb +2 -2
  86. data/lib/active_merchant/billing/integrations/ipay88.rb +4 -4
  87. data/lib/active_merchant/billing/integrations/moneybookers.rb +26 -0
  88. data/lib/active_merchant/billing/integrations/moneybookers/helper.rb +59 -0
  89. data/lib/active_merchant/billing/integrations/moneybookers/notification.rb +129 -0
  90. data/lib/active_merchant/billing/integrations/nochex.rb +2 -2
  91. data/lib/active_merchant/billing/integrations/notification.rb +1 -1
  92. data/lib/active_merchant/billing/integrations/paypal.rb +2 -2
  93. data/lib/active_merchant/billing/integrations/quickpay.rb +6 -2
  94. data/lib/active_merchant/billing/integrations/quickpay/helper.rb +1 -1
  95. data/lib/active_merchant/billing/integrations/quickpay/notification.rb +2 -2
  96. data/lib/active_merchant/billing/integrations/return.rb +10 -3
  97. data/lib/active_merchant/billing/integrations/sage_pay_form.rb +37 -0
  98. data/lib/active_merchant/billing/integrations/sage_pay_form/encryption.rb +33 -0
  99. data/lib/active_merchant/billing/integrations/sage_pay_form/helper.rb +111 -0
  100. data/lib/active_merchant/billing/integrations/sage_pay_form/notification.rb +210 -0
  101. data/lib/active_merchant/billing/integrations/sage_pay_form/return.rb +31 -0
  102. data/lib/active_merchant/billing/integrations/two_checkout.rb +2 -2
  103. data/lib/active_merchant/billing/integrations/two_checkout/notification.rb +5 -5
  104. data/lib/active_merchant/billing/integrations/valitor.rb +33 -0
  105. data/lib/active_merchant/billing/integrations/valitor/helper.rb +86 -0
  106. data/lib/active_merchant/billing/integrations/valitor/notification.rb +13 -0
  107. data/lib/active_merchant/billing/integrations/valitor/response_fields.rb +88 -0
  108. data/lib/active_merchant/billing/integrations/valitor/return.rb +13 -0
  109. data/lib/active_merchant/billing/integrations/world_pay.rb +27 -0
  110. data/lib/active_merchant/billing/integrations/world_pay/helper.rb +100 -0
  111. data/lib/active_merchant/billing/integrations/world_pay/notification.rb +160 -0
  112. data/lib/active_merchant/common.rb +1 -1
  113. data/lib/active_merchant/common/connection.rb +13 -8
  114. data/lib/active_merchant/common/country.rb +15 -6
  115. data/lib/active_merchant/common/post_data.rb +1 -1
  116. data/lib/active_merchant/common/posts_data.rb +21 -5
  117. data/lib/active_merchant/common/utils.rb +4 -0
  118. data/lib/active_merchant/common/validateable.rb +20 -15
  119. data/lib/active_merchant/version.rb +3 -0
  120. data/lib/support/gateway_support.rb +1 -1
  121. metadata +65 -27
@@ -64,12 +64,17 @@ module ActiveMerchant #:nodoc:
64
64
  end
65
65
 
66
66
  # Refund funds to the card holder
67
- def credit(money, identification, options = {})
67
+ def refund(money, identification, options = {})
68
68
  requires!(options, :description)
69
69
 
70
70
  request = build_capture_or_credit_request(money, identification, options)
71
71
  commit(:credit, request)
72
72
  end
73
+
74
+ def credit(money, identification, options = {})
75
+ deprecated CREDIT_DEPRECATION_MESSAGE
76
+ refund(money, identification, options)
77
+ end
73
78
 
74
79
  # token based billing
75
80
 
@@ -187,7 +192,7 @@ module ActiveMerchant #:nodoc:
187
192
  response = parse( ssl_post(URL, request.to_s) )
188
193
 
189
194
  # Return a response
190
- PaymentExpressResponse.new(response[:success] == APPROVED, response[:response_text], response,
195
+ PaymentExpressResponse.new(response[:success] == APPROVED, response[:card_holder_help_text], response,
191
196
  :test => response[:test_mode] == '1',
192
197
  :authorization => response[:dps_txn_ref]
193
198
  )
@@ -50,14 +50,14 @@ module ActiveMerchant #:nodoc:
50
50
  xml.tag! 'n2:ReferenceID', reference_id if transaction_type == 'DoReferenceTransaction'
51
51
  xml.tag! 'n2:PaymentAction', action
52
52
  xml.tag! 'n2:PaymentDetails' do
53
- xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
53
+ xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
54
54
 
55
55
  # All of the values must be included together and add up to the order total
56
56
  if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
57
- xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
58
- xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
59
- xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
60
- xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
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
61
  end
62
62
 
63
63
  xml.tag! 'n2:NotifyURL', options[:notify_url]
@@ -8,7 +8,7 @@ module ActiveMerchant #:nodoc:
8
8
  base.cattr_accessor :signature
9
9
  end
10
10
 
11
- API_VERSION = '59.0'
11
+ API_VERSION = '62.0'
12
12
 
13
13
  URLS = {
14
14
  :test => { :certificate => 'https://api.sandbox.paypal.com/2.0/',
@@ -105,8 +105,13 @@ module ActiveMerchant #:nodoc:
105
105
  commit 'DoVoid', build_void_request(authorization, options)
106
106
  end
107
107
 
108
+ def refund(money, identification, options = {})
109
+ commit 'RefundTransaction', build_refund_request(money, identification, options)
110
+ end
111
+
108
112
  def credit(money, identification, options = {})
109
- commit 'RefundTransaction', build_credit_request(money, identification, options)
113
+ deprecated Gateway::CREDIT_DEPRECATION_MESSAGE
114
+ refund(money, identification, options)
110
115
  end
111
116
 
112
117
  private
@@ -141,7 +146,7 @@ module ActiveMerchant #:nodoc:
141
146
  xml.target!
142
147
  end
143
148
 
144
- def build_credit_request(money, identification, options)
149
+ def build_refund_request(money, identification, options)
145
150
  xml = Builder::XmlMarkup.new
146
151
 
147
152
  xml.tag! 'RefundTransactionReq', 'xmlns' => PAYPAL_NAMESPACE do
@@ -197,6 +202,26 @@ module ActiveMerchant #:nodoc:
197
202
  end
198
203
 
199
204
  def parse(action, xml)
205
+ legacy_hash = legacy_parse(action, xml)
206
+ xml = strip_attributes(xml)
207
+ hash = Hash.from_xml(xml)
208
+ hash = hash.fetch('Envelope').fetch('Body').fetch("#{action}Response")
209
+ hash = hash["#{action}ResponseDetails"] if hash["#{action}ResponseDetails"]
210
+
211
+ legacy_hash.merge(hash)
212
+ rescue IndexError
213
+ legacy_hash.merge(hash['Envelope']['Body'])
214
+ end
215
+
216
+ def strip_attributes(xml)
217
+ xml = REXML::Document.new(xml)
218
+ REXML::XPath.each(xml, '//SOAP-ENV:Envelope//*[@*]') do |el|
219
+ el.attributes.each_attribute { |a| a.remove }
220
+ end
221
+ xml.to_s
222
+ end
223
+
224
+ def legacy_parse(action, xml)
200
225
  response = {}
201
226
 
202
227
  error_messages = []
@@ -225,22 +250,22 @@ module ActiveMerchant #:nodoc:
225
250
  error_messages << message
226
251
  end
227
252
  else
228
- parse_element(response, node)
253
+ legacy_parse_element(response, node)
229
254
  end
230
255
  end
231
256
  response[:message] = error_messages.uniq.join(". ") unless error_messages.empty?
232
257
  response[:error_codes] = error_codes.uniq.join(",") unless error_codes.empty?
233
258
  elsif root = REXML::XPath.first(xml, "//SOAP-ENV:Fault")
234
- parse_element(response, root)
259
+ legacy_parse_element(response, root)
235
260
  response[:message] = "#{response[:faultcode]}: #{response[:faultstring]} - #{response[:detail]}"
236
261
  end
237
262
 
238
263
  response
239
264
  end
240
265
 
241
- def parse_element(response, node)
266
+ def legacy_parse_element(response, node)
242
267
  if node.has_elements?
243
- node.elements.each{|e| parse_element(response, e) }
268
+ node.elements.each{|e| legacy_parse_element(response, e) }
244
269
  else
245
270
  response[node.name.underscore.to_sym] = node.text
246
271
  node.attributes.each do |k, v|
@@ -323,4 +348,4 @@ module ActiveMerchant #:nodoc:
323
348
  end
324
349
  end
325
350
  end
326
- end
351
+ end
@@ -2,37 +2,48 @@ module ActiveMerchant #:nodoc:
2
2
  module Billing #:nodoc:
3
3
  class PaypalExpressResponse < Response
4
4
  def email
5
- @params['payer']
5
+ info['Payer']
6
+ end
7
+
8
+ def info
9
+ (@params['PayerInfo']||{})
6
10
  end
7
11
 
8
12
  def name
9
- [@params['first_name'], @params['middle_name'], @params['last_name']].compact.join(' ')
13
+ payer = (info['PayerName']||{})
14
+ [payer['FirstName'], payer['MiddleName'], payer['LastName']].compact.join(' ')
10
15
  end
11
16
 
12
17
  def token
13
- @params['token']
18
+ @params['Token']
14
19
  end
15
20
 
16
21
  def payer_id
17
- @params['payer_id']
22
+ info['PayerID']
18
23
  end
19
24
 
20
25
  def payer_country
21
- @params['payer_country']
26
+ info['PayerCountry']
27
+ end
28
+
29
+ # PayPal returns a contact telephone number only if your Merchant account profile settings require that the buyer enter one.
30
+ def contact_phone
31
+ @params['ContactPhone']
22
32
  end
23
33
 
24
34
  def address
25
- { 'name' => @params['name'],
26
- 'company' => @params['payer_business'],
27
- 'address1' => @params['street1'],
28
- 'address2' => @params['street2'],
29
- 'city' => @params['city_name'],
30
- 'state' => @params['state_or_province'],
31
- 'country' => @params['country'],
32
- 'zip' => @params['postal_code'],
33
- 'phone' => nil
35
+ address = (@params['PaymentDetails']||{})['ShipToAddress']
36
+ { 'name' => address['Name'],
37
+ 'company' => info['PayerBusiness'],
38
+ 'address1' => address['Street1'],
39
+ 'address2' => address['Street2'],
40
+ 'city' => address['CityName'],
41
+ 'state' => address['StateOrProvince'],
42
+ 'country' => address['Country'],
43
+ 'zip' => address['PostalCode'],
44
+ 'phone' => (contact_phone || address['Phone'])
34
45
  }
35
46
  end
36
47
  end
37
48
  end
38
- end
49
+ end
@@ -4,7 +4,7 @@ module ActiveMerchant #:nodoc:
4
4
  module Billing #:nodoc:
5
5
  # The PayPal gateway for PayPal Website Payments Pro Canada only supports Visa and MasterCard
6
6
  class PaypalCaGateway < PaypalGateway
7
- self.supported_cardtypes = [:visa, :master, :american_express]
7
+ self.supported_cardtypes = [:visa, :master]
8
8
  self.supported_countries = ['CA']
9
9
  self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_wp-pro-overview-outside'
10
10
  self.display_name = 'PayPal Website Payments Pro (CA)'
@@ -8,7 +8,7 @@ module ActiveMerchant #:nodoc:
8
8
  include PaypalCommonAPI
9
9
  include PaypalExpressCommon
10
10
 
11
- self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='
11
+ self.test_redirect_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
12
12
  self.supported_countries = ['US']
13
13
  self.homepage_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=xpt/merchant/ExpressCheckoutIntro-outside'
14
14
  self.display_name = 'PayPal Express Checkout'
@@ -66,18 +66,20 @@ module ActiveMerchant #:nodoc:
66
66
  xml.tag! 'n2:Token', options[:token]
67
67
  xml.tag! 'n2:PayerID', options[:payer_id]
68
68
  xml.tag! 'n2:PaymentDetails' do
69
- xml.tag! 'n2:OrderTotal', amount(money), 'currencyID' => currency_code
69
+ xml.tag! 'n2:OrderTotal', localized_amount(money, currency_code), 'currencyID' => currency_code
70
70
 
71
71
  # All of the values must be included together and add up to the order total
72
72
  if [:subtotal, :shipping, :handling, :tax].all?{ |o| options.has_key?(o) }
73
- xml.tag! 'n2:ItemTotal', amount(options[:subtotal]), 'currencyID' => currency_code
74
- xml.tag! 'n2:ShippingTotal', amount(options[:shipping]),'currencyID' => currency_code
75
- xml.tag! 'n2:HandlingTotal', amount(options[:handling]),'currencyID' => currency_code
76
- xml.tag! 'n2:TaxTotal', amount(options[:tax]), 'currencyID' => currency_code
73
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
74
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code),'currencyID' => currency_code
75
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code),'currencyID' => currency_code
76
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
77
77
  end
78
78
 
79
79
  xml.tag! 'n2:NotifyURL', options[:notify_url]
80
80
  xml.tag! 'n2:ButtonSource', application_id.to_s.slice(0,32) unless application_id.blank?
81
+ xml.tag! 'n2:InvoiceID', options[:order_id]
82
+ xml.tag! 'n2:OrderDescription', options[:description]
81
83
  end
82
84
  end
83
85
  end
@@ -87,29 +89,69 @@ module ActiveMerchant #:nodoc:
87
89
  end
88
90
 
89
91
  def build_setup_request(action, money, options)
92
+ currency_code = options[:currency] || currency(money)
93
+
90
94
  xml = Builder::XmlMarkup.new :indent => 2
91
95
  xml.tag! 'SetExpressCheckoutReq', 'xmlns' => PAYPAL_NAMESPACE do
92
96
  xml.tag! 'SetExpressCheckoutRequest', 'xmlns:n2' => EBAY_NAMESPACE do
93
97
  xml.tag! 'n2:Version', API_VERSION
94
98
  xml.tag! 'n2:SetExpressCheckoutRequestDetails' do
95
- xml.tag! 'n2:PaymentAction', action
96
- xml.tag! 'n2:OrderTotal', amount(money).to_f.zero? ? amount(100) : amount(money), 'currencyID' => options[:currency] || currency(money)
97
99
  if options[:max_amount]
98
- xml.tag! 'n2:MaxAmount', amount(options[:max_amount]), 'currencyID' => options[:currency] || currency(options[:max_amount])
100
+ xml.tag! 'n2:MaxAmount', localized_amount(options[:max_amount], currency_code), 'currencyID' => currency_code
101
+ end
102
+ if !options[:allow_note].nil?
103
+ xml.tag! 'n2:AllowNote', options[:allow_note] ? '1' : '0'
104
+ end
105
+ xml.tag! 'n2:PaymentDetails' do
106
+ xml.tag! 'n2:OrderTotal', amount(money).to_f.zero? ? localized_amount(100, currency_code) : localized_amount(money, currency_code), 'currencyID' => currency_code
107
+ # All of the values must be included together and add up to the order total
108
+ if [:subtotal, :shipping, :handling, :tax].all? { |o| options.has_key?(o) }
109
+ xml.tag! 'n2:ItemTotal', localized_amount(options[:subtotal], currency_code), 'currencyID' => currency_code
110
+ xml.tag! 'n2:ShippingTotal', localized_amount(options[:shipping], currency_code), 'currencyID' => currency_code
111
+ xml.tag! 'n2:HandlingTotal', localized_amount(options[:handling], currency_code), 'currencyID' => currency_code
112
+ xml.tag! 'n2:TaxTotal', localized_amount(options[:tax], currency_code), 'currencyID' => currency_code
113
+ end
114
+
115
+ xml.tag! 'n2:OrderDescription', options[:description]
116
+ xml.tag! 'n2:InvoiceID', options[:order_id]
117
+
118
+ if options[:items]
119
+ options[:items].each do |item|
120
+ xml.tag! 'n2:PaymentDetailsItem' do
121
+ xml.tag! 'n2:Name', item[:name]
122
+ xml.tag! 'n2:Number', item[:number]
123
+ xml.tag! 'n2:Quantity', item[:quantity]
124
+ if item[:amount]
125
+ xml.tag! 'n2:Amount', localized_amount(item[:amount], currency_code), 'currencyID' => currency_code
126
+ end
127
+ xml.tag! 'n2:Description', item[:description]
128
+ xml.tag! 'n2:ItemURL', item[:url]
129
+ end
130
+ end
131
+ end
132
+
133
+ xml.tag! 'n2:PaymentAction', action
99
134
  end
135
+
100
136
  add_address(xml, 'n2:Address', options[:shipping_address] || options[:address])
101
137
  xml.tag! 'n2:AddressOverride', options[:address_override] ? '1' : '0'
102
138
  xml.tag! 'n2:NoShipping', options[:no_shipping] ? '1' : '0'
103
139
  xml.tag! 'n2:ReturnURL', options[:return_url]
104
140
  xml.tag! 'n2:CancelURL', options[:cancel_return_url]
105
- xml.tag! 'n2:IPAddress', options[:ip]
106
- xml.tag! 'n2:OrderDescription', options[:description]
141
+ xml.tag! 'n2:IPAddress', options[:ip] unless options[:ip].blank?
107
142
  xml.tag! 'n2:BuyerEmail', options[:email] unless options[:email].blank?
108
- xml.tag! 'n2:InvoiceID', options[:order_id]
143
+
144
+ if options[:billing_agreement]
145
+ xml.tag! 'n2:BillingAgreementDetails' do
146
+ xml.tag! 'n2:BillingType', options[:billing_agreement][:type]
147
+ xml.tag! 'n2:BillingAgreementDescription', options[:billing_agreement][:description]
148
+ xml.tag! 'n2:PaymentType', options[:billing_agreement][:payment_type] || 'InstantOnly'
149
+ end
150
+ end
109
151
 
110
152
  # Customization of the payment page
111
153
  xml.tag! 'n2:PageStyle', options[:page_style] unless options[:page_style].blank?
112
- xml.tag! 'n2:cpp-image-header', options[:header_image] unless options[:header_image].blank?
154
+ xml.tag! 'n2:cpp-header-image', options[:header_image] unless options[:header_image].blank?
113
155
  xml.tag! 'n2:cpp-header-back-color', options[:header_background_color] unless options[:header_background_color].blank?
114
156
  xml.tag! 'n2:cpp-header-border-color', options[:header_border_color] unless options[:header_border_color].blank?
115
157
  xml.tag! 'n2:cpp-payflow-color', options[:background_color] unless options[:background_color].blank?
@@ -4,7 +4,7 @@ module ActiveMerchant
4
4
  def self.included(base)
5
5
  base.cattr_accessor :test_redirect_url
6
6
  base.cattr_accessor :live_redirect_url
7
- base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr?cmd=_express-checkout&token='
7
+ base.live_redirect_url = 'https://www.paypal.com/cgibin/webscr'
8
8
  end
9
9
 
10
10
  def redirect_url
@@ -12,8 +12,13 @@ module ActiveMerchant
12
12
  end
13
13
 
14
14
  def redirect_url_for(token, options = {})
15
- options = {:review => true}.update(options)
16
- options[:review] ? "#{redirect_url}#{token}" : "#{redirect_url}#{token}&useraction=commit"
15
+ options = {:review => true, :mobile => false}.update(options)
16
+
17
+ cmd = options[:mobile] ? '_express-checkout-mobile' : '_express-checkout'
18
+ url = "#{redirect_url}?cmd=#{cmd}&token=#{token}"
19
+ url += '&useraction=commit' unless options[:review]
20
+
21
+ url
17
22
  end
18
23
  end
19
24
  end
@@ -156,9 +156,8 @@ module ActiveMerchant
156
156
  add_amount(post, money, options)
157
157
 
158
158
  if identification_or_creditcard.is_a?(String)
159
- post[:orderID] = identification_or_creditcard
160
-
161
- commit(:refund, post)
159
+ deprecated CREDIT_DEPRECATION_MESSAGE
160
+ refund(money, identification_or_creditcard, options)
162
161
  else
163
162
  add_creditcard(post, identification_or_creditcard)
164
163
  add_addresses(post, options)
@@ -167,6 +166,13 @@ module ActiveMerchant
167
166
  commit(:credit, post)
168
167
  end
169
168
  end
169
+
170
+ def refund(money, reference, options = {})
171
+ post = PlugnpayPostData.new
172
+ add_amount(post, money, options)
173
+ post[:orderID] = reference
174
+ commit(:refund, post)
175
+ end
170
176
 
171
177
  private
172
178
  def commit(action, post)
@@ -79,6 +79,11 @@ module ActiveMerchant #:nodoc:
79
79
 
80
80
  # Psigate Credit
81
81
  def credit(money, authorization, options = {})
82
+ deprecated CREDIT_DEPRECATION_MESSAGE
83
+ refund(money, authorization, options)
84
+ end
85
+
86
+ def refund(money, authorization, options = {})
82
87
  options.update({ :CardAction => "3", :order_id => authorization })
83
88
  commit(money, nil, options)
84
89
  end
@@ -0,0 +1,295 @@
1
+ require 'securerandom'
2
+
3
+ module ActiveMerchant #:nodoc:
4
+ module Billing #:nodoc:
5
+ class QbmsGateway < Gateway
6
+ API_VERSION = '4.0'
7
+
8
+ class_attribute :test_url, :live_url
9
+
10
+ self.test_url = "https://webmerchantaccount.ptc.quickbooks.com/j/AppGateway"
11
+ self.live_url = "https://webmerchantaccount.quickbooks.com/j/AppGateway"
12
+
13
+ self.homepage_url = 'http://payments.intuit.com/'
14
+ self.display_name = 'QuickBooks Merchant Services'
15
+ self.default_currency = 'USD'
16
+ self.supported_cardtypes = [ :visa, :master, :discover, :american_express, :diners_club, :jcb ]
17
+ self.supported_countries = [ 'US' ]
18
+
19
+ TYPES = {
20
+ :authorize => 'CustomerCreditCardAuth',
21
+ :capture => 'CustomerCreditCardCapture',
22
+ :purchase => 'CustomerCreditCardCharge',
23
+ :refund => 'CustomerCreditCardTxnVoidOrRefund',
24
+ :void => 'CustomerCreditCardTxnVoid',
25
+ :query => 'MerchantAccountQuery',
26
+ }
27
+
28
+ # Creates a new QbmsGateway
29
+ #
30
+ # The gateway requires that a valid app id, app login, and ticket be passed
31
+ # in the +options+ hash.
32
+ #
33
+ # ==== Options
34
+ #
35
+ # * <tt>:login</tt> -- The App Login (REQUIRED)
36
+ # * <tt>:ticket</tt> -- The Connection Ticket. (REQUIRED)
37
+ # * <tt>:pem</tt> -- The PEM-encoded SSL client key and certificate. (REQUIRED)
38
+ # * <tt>:test</tt> -- +true+ or +false+. If true, perform transactions against the test server.
39
+ # Otherwise, perform transactions against the production server.
40
+ #
41
+ def initialize(options = {})
42
+ requires!(options, :login, :ticket)
43
+ test_mode = options[:test] || false
44
+ @options = options
45
+ super
46
+ end
47
+
48
+ # Performs an authorization, which reserves the funds on the customer's credit card, but does not
49
+ # charge the card.
50
+ #
51
+ # ==== Parameters
52
+ #
53
+ # * <tt>money</tt> -- The amount to be authorized as an Integer value in cents.
54
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
55
+ # * <tt>options</tt> -- A hash of optional parameters.
56
+ #
57
+ def authorize(money, creditcard, options = {})
58
+ commit(:authorize, money, options.merge(:credit_card => creditcard))
59
+ end
60
+
61
+ # Perform a purchase, which is essentially an authorization and capture in a single operation.
62
+ #
63
+ # ==== Parameters
64
+ #
65
+ # * <tt>money</tt> -- The amount to be purchased as an Integer value in cents.
66
+ # * <tt>creditcard</tt> -- The CreditCard details for the transaction.
67
+ # * <tt>options</tt> -- A hash of optional parameters.
68
+ #
69
+ def purchase(money, creditcard, options = {})
70
+ commit(:purchase, money, options.merge(:credit_card => creditcard))
71
+ end
72
+
73
+ # Captures the funds from an authorized transaction.
74
+ #
75
+ # ==== Parameters
76
+ #
77
+ # * <tt>money</tt> -- The amount to be captured as an Integer value in cents.
78
+ # * <tt>authorization</tt> -- The authorization returned from the previous authorize request.
79
+ #
80
+ def capture(money, authorization, options = {})
81
+ commit(:capture, money, options.merge(:transaction_id => authorization))
82
+ end
83
+
84
+ # Void a previous transaction
85
+ #
86
+ # ==== Parameters
87
+ #
88
+ # * <tt>authorization</tt> - The authorization returned from the previous authorize request.
89
+ #
90
+ def void(authorization, options = {})
91
+ commit(:void, nil, options.merge(:transaction_id => authorization))
92
+ end
93
+
94
+ # Credit an account.
95
+ #
96
+ # This transaction is also referred to as a Refund and indicates to the gateway that
97
+ # money should flow from the merchant to the customer.
98
+ #
99
+ # ==== Parameters
100
+ #
101
+ # * <tt>money</tt> -- The amount to be credited to the customer as an Integer value in cents.
102
+ # * <tt>identification</tt> -- The ID of the original transaction against which the credit is being issued.
103
+ # * <tt>options</tt> -- A hash of parameters.
104
+ #
105
+ #
106
+ def credit(money, identification, options = {})
107
+ deprecated CREDIT_DEPRECATION_MESSAGE
108
+ refund(money, identification, options = {})
109
+ end
110
+
111
+ def refund(money, identification, options = {})
112
+ commit(:refund, money, options.merge(:transaction_id => identification))
113
+ end
114
+
115
+ # Query the merchant account status
116
+ def query
117
+ commit(:query, nil, {})
118
+ end
119
+
120
+ private
121
+
122
+ def hosted?
123
+ @options[:pem]
124
+ end
125
+
126
+ def commit(action, money, parameters)
127
+ url = test? ? self.test_url : self.live_url
128
+
129
+ type = TYPES[action]
130
+ parameters[:trans_request_id] ||= SecureRandom.hex(10)
131
+
132
+ req = build_request(type, money, parameters)
133
+ data = ssl_post(url, req, "Content-Type" => "application/x-qbmsxml")
134
+ response = parse(type, data)
135
+ message = (response[:status_message] || '').strip
136
+
137
+ Response.new(success?(response), message, response,
138
+ :test => test?,
139
+ :authorization => response[:credit_card_trans_id],
140
+ :fraud_review => fraud_review?(response),
141
+ :avs_result => { :code => avs_result(response) },
142
+ :cvv_result => cvv_result(response)
143
+ )
144
+ end
145
+
146
+ def success?(response)
147
+ response[:status_code] == 0
148
+ end
149
+
150
+ def fraud_review?(response)
151
+ [10100, 10101].member? response[:status_code]
152
+ end
153
+
154
+ def parse(type, body)
155
+ xml = REXML::Document.new(body)
156
+
157
+ signon = REXML::XPath.first(xml, "//SignonMsgsRs/#{hosted? ? 'SignonAppCertRs' : 'SignonDesktopRs'}")
158
+ status_code = signon.attributes["statusCode"].to_i
159
+
160
+ if status_code != 0
161
+ return {
162
+ :status_code => status_code,
163
+ :status_message => signon.attributes["statusMessage"],
164
+ }
165
+ end
166
+
167
+ response = REXML::XPath.first(xml, "//QBMSXMLMsgsRs/#{type}Rs")
168
+
169
+ results = {
170
+ :status_code => response.attributes["statusCode"].to_i,
171
+ :status_message => response.attributes["statusMessage"],
172
+ }
173
+
174
+ response.elements.each do |e|
175
+ name = e.name.underscore.to_sym
176
+ value = e.text()
177
+
178
+ if old_value = results[name]
179
+ results[name] = [old_value] if !old_value.kind_of?(Array)
180
+ results[name] << value
181
+ else
182
+ results[name] = value
183
+ end
184
+ end
185
+
186
+ results
187
+ end
188
+
189
+ def build_request(type, money, parameters = {})
190
+ xml = Builder::XmlMarkup.new(:indent => 0)
191
+
192
+ xml.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
193
+ xml.instruct!(:qbmsxml, :version => API_VERSION)
194
+
195
+ xml.tag!("QBMSXML") do
196
+ xml.tag!("SignonMsgsRq") do
197
+ xml.tag!(hosted? ? "SignonAppCertRq" : "SignonDesktopRq") do
198
+ xml.tag!("ClientDateTime", Time.now.xmlschema)
199
+ xml.tag!("ApplicationLogin", @options[:login])
200
+ xml.tag!("ConnectionTicket", @options[:ticket])
201
+ end
202
+ end
203
+
204
+ xml.tag!("QBMSXMLMsgsRq") do
205
+ xml.tag!("#{type}Rq") do
206
+ method("build_#{type}").call(xml, money, parameters)
207
+ end
208
+ end
209
+ end
210
+
211
+ xml.target!
212
+ end
213
+
214
+ def build_CustomerCreditCardAuth(xml, money, parameters)
215
+ cc = parameters[:credit_card]
216
+ name = "#{cc.first_name} #{cc.last_name}"[0...30]
217
+
218
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
219
+ xml.tag!("CreditCardNumber", cc.number)
220
+ xml.tag!("ExpirationMonth", cc.month)
221
+ xml.tag!("ExpirationYear", cc.year)
222
+ xml.tag!("IsECommerce", "true")
223
+ xml.tag!("Amount", amount(money))
224
+ xml.tag!("NameOnCard", name)
225
+ add_address(xml, parameters)
226
+ xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
227
+ end
228
+
229
+ def build_CustomerCreditCardCapture(xml, money, parameters)
230
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
231
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
232
+ xml.tag!("Amount", amount(money))
233
+ end
234
+
235
+ def build_CustomerCreditCardCharge(xml, money, parameters)
236
+ cc = parameters[:credit_card]
237
+ name = "#{cc.first_name} #{cc.last_name}"[0...30]
238
+
239
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
240
+ xml.tag!("CreditCardNumber", cc.number)
241
+ xml.tag!("ExpirationMonth", cc.month)
242
+ xml.tag!("ExpirationYear", cc.year)
243
+ xml.tag!("IsECommerce", "true")
244
+ xml.tag!("Amount", amount(money))
245
+ xml.tag!("NameOnCard", name)
246
+ add_address(xml, parameters)
247
+ xml.tag!("CardSecurityCode", cc.verification_value) if cc.verification_value?
248
+ end
249
+
250
+ def build_CustomerCreditCardTxnVoidOrRefund(xml, money, parameters)
251
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
252
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
253
+ xml.tag!("Amount", amount(money))
254
+ end
255
+
256
+ def build_CustomerCreditCardTxnVoid(xml, money, parameters)
257
+ xml.tag!("TransRequestID", parameters[:trans_request_id])
258
+ xml.tag!("CreditCardTransID", parameters[:transaction_id])
259
+ end
260
+
261
+ # Called reflectively by build_request
262
+ def build_MerchantAccountQuery(xml, money, parameters)
263
+ end
264
+
265
+ def add_address(xml, parameters)
266
+ if address = parameters[:billing_address] || parameters[:address]
267
+ xml.tag!("CreditCardAddress", address[:address1][0...30])
268
+ xml.tag!("CreditCardPostalCode", address[:zip][0...9])
269
+ end
270
+ end
271
+
272
+ def cvv_result(response)
273
+ case response[:card_security_code_match]
274
+ when "Pass" then 'M'
275
+ when "Fail" then 'N'
276
+ when "NotAvailable" then 'P'
277
+ end
278
+ end
279
+
280
+ def avs_result(response)
281
+ case "#{response[:avs_street]}|#{response[:avs_zip]}"
282
+ when "Pass|Pass" then "D"
283
+ when "Pass|Fail" then "A"
284
+ when "Pass|NotAvailable" then "B"
285
+ when "Fail|Pass" then "Z"
286
+ when "Fail|Fail" then "C"
287
+ when "Fail|NotAvailable" then "N"
288
+ when "NotAvailable|Pass" then "P"
289
+ when "NotAvailable|Fail" then "N"
290
+ when "NotAvailable|NotAvailable" then "U"
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end